diff --git a/.ci/mysql_fixtures.sh b/.ci/mysql_fixtures.sh deleted file mode 100755 index aa14663ef..000000000 --- a/.ci/mysql_fixtures.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -echo "Configure MySQL test database" - -mysql --user=root --password=Password123 -e "create database laminasdb_test;" diff --git a/.ci/pgsql_fixtures.sh b/.ci/pgsql_fixtures.sh deleted file mode 100755 index 65f1db5ba..000000000 --- a/.ci/pgsql_fixtures.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -echo "Configure PostgreSQL test database" - -psql -U postgres -c 'create database laminasdb_test;' -psql -U postgres -c "alter role postgres password 'postgres'" diff --git a/.ci/sqlsrv_fixtures.sh b/.ci/sqlsrv_fixtures.sh deleted file mode 100755 index a5217c106..000000000 --- a/.ci/sqlsrv_fixtures.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -echo "Configure SQL server test database" - -sqlcmd -S localhost -U sa -P Password123 -Q "CREATE DATABASE laminasdb_test;" diff --git a/.ci/sqlsrv_install_drivers.sh b/.ci/sqlsrv_install_drivers.sh deleted file mode 100755 index 40ca7d146..000000000 --- a/.ci/sqlsrv_install_drivers.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -set -x - -# Install MSSQL client development libraries -curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - -sudo curl https://packages.microsoft.com/config/ubuntu/18.04/prod.list -o /etc/apt/sources.list.d/mssql-release.list -sudo apt-get update -sudo ACCEPT_EULA=Y apt-get install -y msodbcsql17 mssql-tools unixodbc-dev -echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile -echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc - -export PATH="$PATH:/opt/mssql-tools/bin" - -# Install SqlServer PHP extensions -pecl install sqlsrv -pecl install pdo_sqlsrv diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..6ed999277 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1 @@ +5135979f91d789b20aab12902fffd22dd558163a diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..ad78810fa --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,12 @@ +name: CI + +on: + push: + pull_request: + +jobs: + qa: + uses: php-db/phpdb-qa-tools/.github/workflows/continuous-integration.yml@0.1.x + with: + php-versions: '["8.2", "8.3", "8.4", "8.5"]' + run-integration: false diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml deleted file mode 100644 index 9253d0d48..000000000 --- a/.github/workflows/continuous-integration.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: "Continuous Integration" - -on: - pull_request: - push: - branches: - tags: - -jobs: - matrix: - name: Generate job matrix - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.matrix.outputs.matrix }} - steps: - - name: Gather CI configuration - id: matrix - uses: laminas/laminas-ci-matrix-action@v1 - - qa: - name: QA Checks - needs: [matrix] - runs-on: ${{ matrix.operatingSystem }} - strategy: - fail-fast: false - matrix: ${{ fromJSON(needs.matrix.outputs.matrix) }} - steps: - - name: ${{ matrix.name }} - uses: laminas/laminas-continuous-integration-action@v1 - with: - job: ${{ matrix.job }} - services: - mysql: - image: mysql:5.7 - env: - MYSQL_ROOT_PASSWORD: 'password' - MYSQL_ROOT_HOST: '%' - MYSQL_USER: 'gha' - MYSQL_PASSWORD: 'password' - MYSQL_DATABASE: 'laminasdb_test' - options: >- - --health-cmd="mysqladmin ping" - --health-interval 10s - --health-timeout 5s - --health-retries 3 - ports: - - 3306 - postgres: - image: postgres - env: - POSTGRES_USER: 'gha' - POSTGRES_PASSWORD: 'password' - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 3 - ports: - - 5432 - mssql: - image: mcr.microsoft.com/mssql/server:2022-latest - env: - ACCEPT_EULA: 'Y' - SA_PASSWORD: 'Password123' - ports: - - 1433 - options: >- - --health-cmd="/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P 'Password123' -Q 'SELECT 1' || exit 1" - --health-interval 10s - --health-timeout 5s - --health-retries 3 - --health-start-period 10s diff --git a/.laminas-ci.json b/.laminas-ci.json deleted file mode 100644 index c433f1331..000000000 --- a/.laminas-ci.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extensions": [ - "pdo-sqlite", - "sqlite3" - ], - "additional_checks": [ - { - "name": "PhpStan", - "job": { - "php": "8.2", - "dependencies": "latest", - "command": "composer require --dev phpstan/phpstan && vendor/bin/phpstan analyse" - } - } - ] -} \ No newline at end of file diff --git a/.laminas-ci/mysql_permissions.sql b/.laminas-ci/mysql_permissions.sql deleted file mode 100644 index 59c1c608f..000000000 --- a/.laminas-ci/mysql_permissions.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE USER 'gha'@'%' IDENTIFIED WITH mysql_native_password BY 'password'; -GRANT ALL PRIVILEGES ON *.* TO 'gha'@'%'; -FLUSH PRIVILEGES; diff --git a/.laminas-ci/pre-install.sh b/.laminas-ci/pre-install.sh deleted file mode 100755 index a3c153e4b..000000000 --- a/.laminas-ci/pre-install.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -WORKING_DIRECTORY=$2 -JOB=$3 -PHP_VERSION=$(echo "${JOB}" | jq -r '.php') - - -if [ ! -z "$GITHUB_BASE_REF" ] && [[ "$GITHUB_BASE_REF" =~ ^[0-9]+\.[0-9] ]]; then - readarray -td. TARGET_BRANCH_VERSION_PARTS <<<"${GITHUB_BASE_REF}."; - unset 'TARGET_BRANCH_VERSION_PARTS[-1]'; - declare -a TARGET_BRANCH_VERSION_PARTS - MAJOR_OF_TARGET_BRANCH=${TARGET_BRANCH_VERSION_PARTS[0]} - MINOR_OF_TARGET_BRANCH=${TARGET_BRANCH_VERSION_PARTS[1]} - - export COMPOSER_ROOT_VERISON="${MAJOR_OF_TARGET_BRANCH}.${MINOR_OF_TARGET_BRANCH}.99" - echo "Exported COMPOSER_ROOT_VERISON as ${COMPOSER_ROOT_VERISON}" -fi diff --git a/analyzer-baseline.toml b/analyzer-baseline.toml new file mode 100644 index 000000000..a6fca3889 --- /dev/null +++ b/analyzer-baseline.toml @@ -0,0 +1,6691 @@ +variant = "loose" + +[[issues]] +file = "src/Adapter/Adapter.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Adapter\Adapter` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Adapter/Adapter.php" +code = "imprecise-type" +message = "Type `array` in parameter `$initialParameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Adapter.php" +code = "imprecise-type" +message = "Type `array` in parameter `$parametersOrQueryMode` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Adapter.php" +code = "imprecise-type" +message = "Type `array` in parameter `$parameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Adapter.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteIdentifier`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Adapter/Adapter.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteValue`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Adapter/Adapter.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Adapter/Adapter.php" +code = "possibly-null-argument" +message = 'Argument #1 of method `PhpDb\Adapter\Driver\DriverInterface::createStatement` is possibly `null`, but parameter type `resource|string` does not accept it.' +count = 1 + +[[issues]] +file = "src/Adapter/AdapterAwareInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\AdapterAwareInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/AdapterAwareTrait.php" +code = "missing-api-or-internal" +message = 'Trait `PhpDb\Adapter\AdapterAwareTrait` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/AdapterInterface.php" +code = "imprecise-type" +message = "Type `array` in parameter `$initialParameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/AdapterInterface.php" +code = "imprecise-type" +message = "Type `array` in parameter `$parametersOrQueryMode` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/AdapterInterface.php" +code = "imprecise-type" +message = "Type `array` in parameter `$parameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/AdapterInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\AdapterInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/AdapterInterface.php" +code = "missing-constant-type" +message = "Class constant `FUNCTION_FORMAT_PARAMETER_NAME` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/AdapterInterface.php" +code = "missing-constant-type" +message = "Class constant `FUNCTION_QUOTE_IDENTIFIER` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/AdapterInterface.php" +code = "missing-constant-type" +message = "Class constant `FUNCTION_QUOTE_VALUE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/AdapterInterface.php" +code = "missing-constant-type" +message = "Class constant `PREPARE_TYPE_NAMED` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/AdapterInterface.php" +code = "missing-constant-type" +message = "Class constant `PREPARE_TYPE_POSITIONAL` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/AdapterInterface.php" +code = "missing-constant-type" +message = "Class constant `QUERY_MODE_EXECUTE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/AdapterInterface.php" +code = "missing-constant-type" +message = "Class constant `QUERY_MODE_PREPARE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/AdapterInterface.php" +code = "missing-constant-type" +message = "Class constant `VALUE_QUOTE_SEPARATOR` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/AdapterInterface.php" +code = "missing-return-type" +message = "Function `getHelpers` is missing a return type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/AbstractConnection.php" +code = "imprecise-type" +message = "Type `array` in parameter `$connectionParameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/AbstractConnection.php" +code = "imprecise-type" +message = "Type `array` in property `$connectionParameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/AbstractConnection.php" +code = "imprecise-type" +message = "Type `array` in return type of `getConnectionParameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/AbstractConnection.php" +code = "incompatible-return-type" +message = 'Return type `null|resource` of `PhpDb\Adapter\Driver\AbstractConnection::getresource()` is incompatible with parent return type `resource` of `PhpDb\Adapter\Driver\ConnectionInterface::getresource()`' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/AbstractConnection.php" +code = "incompatible-return-type" +message = 'Return type `null|resource` of `PhpDb\Adapter\Driver\Pdo\AbstractPdoConnection::getresource()` is incompatible with parent return type `resource` of `PhpDb\Adapter\Driver\ConnectionInterface::getresource()`' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/AbstractConnection.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\Adapter\Driver\AbstractConnection` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/AbstractConnection.php" +code = "missing-property-type" +message = "Property `$resource` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/AbstractConnection.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\Adapter\Driver\AbstractConnection::getResource`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/ConnectionInterface.php" +code = "imprecise-type" +message = "Type `array` in parameter `$connectionParameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/ConnectionInterface.php" +code = "imprecise-type" +message = "Type `array` in return type of `getConnectionParameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/ConnectionInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\Driver\ConnectionInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/ConnectionInterface.php" +code = "missing-return-type" +message = "Function `getResource` is missing a return type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/DriverAwareInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\Driver\DriverAwareInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/DriverInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\Driver\DriverInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/DriverInterface.php" +code = "missing-constant-type" +message = "Class constant `PARAMETERIZATION_NAMED` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/DriverInterface.php" +code = "missing-constant-type" +message = "Class constant `PARAMETERIZATION_POSITIONAL` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/DriverInterface.php" +code = "missing-parameter-type" +message = "Parameter `$resource` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/DriverInterface.php" +code = "missing-parameter-type" +message = "Parameter `$sqlOrResource` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Feature/AbstractFeature.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\Adapter\Driver\Feature\AbstractFeature` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Feature/DriverFeatureInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\Driver\Feature\DriverFeatureInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Feature/DriverFeatureProviderInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\Driver\Feature\DriverFeatureProviderInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php" +code = "imprecise-type" +message = "Type `array` in parameter `$features` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php" +code = "invalid-override-attribute" +message = 'Invalid `#[Override]` attribute on `PhpDb\Adapter\Driver\Feature\DriverFeatureProviderTrait::addfeature`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php" +code = "invalid-override-attribute" +message = 'Invalid `#[Override]` attribute on `PhpDb\Adapter\Driver\Feature\DriverFeatureProviderTrait::addfeatures`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php" +code = "invalid-override-attribute" +message = 'Invalid `#[Override]` attribute on `PhpDb\Adapter\Driver\Feature\DriverFeatureProviderTrait::getfeature`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Adapter\Driver\Feature\DriverFeatureProviderTrait::addFeature`: expected `PhpDb\Adapter\Driver\Feature\DriverFeatureProviderInterface`, but found `$this(phpdb\adapter\driver\feature\driverfeatureprovidertrait&PhpDb\Adapter\Driver\DriverInterface)`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Adapter\Driver\Feature\DriverFeatureProviderTrait::addFeatures`: expected `PhpDb\Adapter\Driver\Feature\DriverFeatureProviderInterface`, but found `$this(phpdb\adapter\driver\feature\driverfeatureprovidertrait)`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php" +code = "missing-api-or-internal" +message = 'Trait `PhpDb\Adapter\Driver\Feature\DriverFeatureProviderTrait` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Driver\Feature\DriverFeatureProviderTrait::addFeature`: expected `PhpDb\Adapter\Driver\Feature\DriverFeatureInterface`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\Adapter\Exception\RuntimeException` in `PhpDb\Adapter\Driver\Feature\DriverFeatureProviderTrait::addFeature`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "impossible-condition" +message = "This condition (type `false`) will always evaluate to false." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "impossible-type-comparison" +message = "Impossible type assertion: `$name` of type `string` can never be `int`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "incompatible-parameter-type" +message = 'Parameter `$sqlOrResource` of `PhpDb\Adapter\Driver\Pdo\AbstractPdo::createstatement()` expects type `PDOStatement|string` but parent `PhpDb\Adapter\Driver\DriverInterface::createstatement()` expects type `resource|string`' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Adapter\Driver\Pdo\AbstractPdo::getConnection`: expected `PhpDb\Adapter\Driver\PdoConnectionInterface`, but found `(PhpDb\Adapter\Driver\PdoConnectionInterface&PhpDb\Adapter\Driver\AbstractConnection&PhpDb\Adapter\Driver\PdoDriverAwareInterface)|PDO`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\Adapter\Driver\Pdo\AbstractPdo` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\Adapter\Driver\Pdo\AbstractPdo::getLastGeneratedValue`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "non-existent-method" +message = "Method `getlastgeneratedvalue` does not exist on type `PDO`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "non-existent-method" +message = "Method `getresource` does not exist on type `PDO`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "non-existent-method" +message = "Method `isconnected` does not exist on type `PDO`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "overly-wide-return-type" +message = 'Declared return type `PhpDb\Adapter\Driver\ResultInterface|null` for `getresultprototype` has unused branches: `null`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "redundant-condition" +message = "This condition (type `true`) will always evaluate to true." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "redundant-logical-operation" +message = "Redundant `&&` operation: left operand is always falsy and right operand is not evaluated." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "redundant-type-comparison" +message = "Redundant type assertion: `$sqlOrResource` is already `string`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "too-few-arguments" +message = "Too few arguments provided for method `PDO::connect`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PDOException` in `PhpDb\Adapter\Driver\Pdo\AbstractPdo::createStatement`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\Adapter\Exception\RuntimeException` in `PhpDb\Adapter\Driver\Pdo\AbstractPdo::formatParameterName`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "imprecise-type" +message = "Type `array` in parameter `$connectionParameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "incompatible-property-type" +message = 'Property `PhpDb\Adapter\Driver\Pdo\AbstractPdoConnection::$resource` has an incompatible type declaration from docblock.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\Adapter\Driver\Pdo\AbstractPdoConnection` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `Exception::__construct`: expected `string`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Driver\DriverInterface::createResult`: expected `resource`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `strtolower`: expected `string`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "mixed-array-access" +message = "Unsafe array access on type `mixed`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\Adapter\Driver\Pdo\AbstractPdoConnection::execute`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\Adapter\Driver\Pdo\AbstractPdoConnection::prepare`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 7 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "possibly-null-argument" +message = 'Argument #1 of method `PhpDb\Adapter\Driver\DriverInterface::createStatement` is possibly `null`, but parameter type `resource|string` does not accept it.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "too-many-arguments" +message = 'Too many arguments provided for method `PhpDb\Adapter\Driver\DriverInterface::createResult`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PDOException` in `PhpDb\Adapter\Driver\Pdo\AbstractPdoConnection::beginTransaction`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PDOException` in `PhpDb\Adapter\Driver\Pdo\AbstractPdoConnection::commit`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PDOException` in `PhpDb\Adapter\Driver\Pdo\AbstractPdoConnection::execute`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PDOException` in `PhpDb\Adapter\Driver\Pdo\AbstractPdoConnection::rollback`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdoConnection.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PDOException` in `PhpDb\Adapter\Driver\Pdo\AbstractPdoConnection::setResource`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Adapter\Driver\Pdo\Result` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "incompatible-return-type" +message = 'Return type `mixed` of `PhpDb\Adapter\Driver\Pdo\Result::next()` is incompatible with parent return type `void` of `Iterator::next()`' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "missing-constant-type" +message = "Class constant `STATEMENT_MODE_FORWARD` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "missing-constant-type" +message = "Class constant `STATEMENT_MODE_SCROLLABLE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "missing-constant-type" +message = "Class constant `VALID_FETCH_MODES` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "missing-parameter-type" +message = "Parameter `$generatedValue` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "missing-property-type" +message = "Property `$currentComplete` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "missing-property-type" +message = "Property `$currentData` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "missing-property-type" +message = "Property `$fetchMode` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "missing-property-type" +message = "Property `$generatedValue` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "missing-property-type" +message = "Property `$options` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "missing-property-type" +message = "Property `$position` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "missing-property-type" +message = "Property `$resource` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "missing-template-parameter" +message = "Too few template arguments for `Iterator`: expected at least 2, but found 0." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `Exception` in `PhpDb\Adapter\Driver\Pdo\Result::getQueryResult`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PDOException` in `PhpDb\Adapter\Driver\Pdo\Result::count`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PDOException` in `PhpDb\Adapter\Driver\Pdo\Result::current`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PDOException` in `PhpDb\Adapter\Driver\Pdo\Result::getAffectedRows`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PDOException` in `PhpDb\Adapter\Driver\Pdo\Result::getFieldCount`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PDOException` in `PhpDb\Adapter\Driver\Pdo\Result::isQueryResult`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PDOException` in `PhpDb\Adapter\Driver\Pdo\Result::next`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PDOException` in `PhpDb\Adapter\Driver\Pdo\Result::rewind`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Adapter\Driver\Pdo\Statement` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "docblock-type-mismatch" +message = "Docblock return type `false|null|resource` is incompatible with native return type `PDOStatement|false|null`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "imprecise-type" +message = "Type `array` in parameter `$options` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "imprecise-type" +message = "Type `array` in parameter `$parameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "invalid-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Driver\DriverInterface::createResult`: expected `resource`, but found `PDOStatement|false|null`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "invalid-method-access" +message = "Attempting to access a method on a non-object type (`false`)." +count = 3 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "invalid-property-assignment-value" +message = "Invalid type for property `$sql`: expected `string`, but got `null|string`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Adapter\Driver\Pdo\Statement::getResource`: expected `false|null|resource`, but found `PDOStatement|false|null`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "mixed-argument" +message = "Invalid argument type for argument #2 of `implode`: expected `array|null`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 3 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "possibly-false-argument" +message = 'Argument #1 of method `PhpDb\Adapter\Driver\DriverInterface::createResult` is possibly `false`, but parameter type `resource` does not accept it.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "possibly-null-argument" +message = 'Argument #1 of method `PhpDb\Adapter\Driver\DriverInterface::createResult` is possibly `null`, but parameter type `resource` does not accept it.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PDOException` in `PhpDb\Adapter\Driver\Pdo\Statement::bindParametersFromContainer`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PDOException` in `PhpDb\Adapter\Driver\Pdo\Statement::prepare`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\Adapter\Exception\RuntimeException` in `PhpDb\Adapter\Driver\Pdo\Statement::bindParametersFromContainer`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\Adapter\Exception\RuntimeException` in `PhpDb\Adapter\Driver\Pdo\Statement::execute`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "uninitialized-property" +message = 'Property `$driver` is not initialized in the constructor of class `PhpDb\Adapter\Driver\Pdo\Statement`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "uninitialized-property" +message = 'Property `$isQuery` is not initialized in the constructor of class `PhpDb\Adapter\Driver\Pdo\Statement`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "uninitialized-property" +message = 'Property `$pdo` is not initialized in the constructor of class `PhpDb\Adapter\Driver\Pdo\Statement`.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/PdoConnectionInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\Driver\PdoConnectionInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/PdoDriverAwareInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\Driver\PdoDriverAwareInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/PdoDriverInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\Driver\PdoDriverInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/ResultInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\Driver\ResultInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/ResultInterface.php" +code = "missing-template-parameter" +message = "Too few template arguments for `Iterator`: expected at least 2, but found 0." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/StatementInterface.php" +code = "imprecise-type" +message = "Type `array` in parameter `$parameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/StatementInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\Driver\StatementInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Driver/StatementInterface.php" +code = "missing-return-type" +message = "Function `getResource` is missing a return type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Exception/ErrorException.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Adapter\Exception\ErrorException` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Adapter/Exception/ExceptionInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\Exception\ExceptionInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Exception/InvalidArgumentException.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Adapter\Exception\InvalidArgumentException` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Adapter/Exception/InvalidConnectionParametersException.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Adapter\Exception\InvalidConnectionParametersException` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Adapter/Exception/InvalidConnectionParametersException.php" +code = "imprecise-type" +message = "Type `array` in parameter `$parameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Exception/InvalidConnectionParametersException.php" +code = "imprecise-type" +message = "Type `array` in property `$parameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Exception/InvalidQueryException.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Adapter\Exception\InvalidQueryException` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Adapter\ParameterContainer` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "imprecise-type" +message = "Type `array` in parameter `$data` is imprecise, equivalent to `array`." +count = 2 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "incompatible-parameter-name" +message = 'Parameter #1 of `PhpDb\Adapter\ParameterContainer::offsetunset()` is named `$name` but parent `ArrayAccess::offsetunset()` names it `$offset`' +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "incompatible-parameter-type" +message = 'Parameter `$name` of `PhpDb\Adapter\ParameterContainer::offsetexists()` expects type `int|string` but parent `ArrayAccess::offsetexists()` expects type `mixed`' +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "incompatible-parameter-type" +message = 'Parameter `$name` of `PhpDb\Adapter\ParameterContainer::offsetget()` expects type `int|string` but parent `ArrayAccess::offsetget()` expects type `mixed`' +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "incompatible-parameter-type" +message = 'Parameter `$name` of `PhpDb\Adapter\ParameterContainer::offsetset()` expects type `int|null|string` but parent `ArrayAccess::offsetset()` expects type `mixed`' +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "missing-constant-type" +message = "Class constant `TYPE_AUTO` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "missing-constant-type" +message = "Class constant `TYPE_BINARY` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "missing-constant-type" +message = "Class constant `TYPE_DOUBLE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "missing-constant-type" +message = "Class constant `TYPE_INTEGER` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "missing-constant-type" +message = "Class constant `TYPE_LOB` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "missing-constant-type" +message = "Class constant `TYPE_NULL` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "missing-constant-type" +message = "Class constant `TYPE_STRING` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "missing-template-parameter" +message = "Too few template arguments for `ArrayAccess`: expected at least 2, but found 0." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "missing-template-parameter" +message = "Too few template arguments for `Iterator`: expected at least 2, but found 0." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "possibly-invalid-argument" +message = "Possible argument type mismatch for argument #1 of `ltrim`: expected `string`, but possibly received `int|string`." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "possibly-null-argument" +message = "Argument #1 of function `array_key_exists` is possibly `null`, but parameter type `bool|float|int|string` does not accept it." +count = 4 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "possibly-null-array-index" +message = "Possibly using `null` as an array index to access element." +count = 2 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "possibly-null-array-index" +message = "Possibly using `null` as an array index to access elementof variable $this->data." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "possibly-null-array-index" +message = "Possibly using `null` as an array index to access elementof variable $this->errata." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "possibly-null-array-index" +message = "Possibly using `null` as an array index to access elementof variable $this->maxLength." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `int` accessed on `array`." +count = 8 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `int|string` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string` accessed on `array`." +count = 2 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "redundant-comparison" +message = "Redundant `===` comparison: left-hand side is always identical to right-hand side." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "redundant-condition" +message = "This condition (type `true`) will always evaluate to true." +count = 1 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "imprecise-type" +message = "Type `array` in parameter `$additionalSafeWords` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Adapter\Platform\AbstractPlatform::getQuoteIdentifierSymbol`: expected `string`, but found `null|string`.' +count = 1 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\Adapter\Platform\AbstractPlatform` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "missing-constant-type" +message = "Class constant `SAFE_WORDS` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `strtolower`: expected `string`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "nullable-return-statement" +message = 'Function `PhpDb\Adapter\Platform\AbstractPlatform::getQuoteIdentifierSymbol` is declared to return `string` but possibly returns a nullable value (inferred as `null|string`).' +count = 1 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "possibly-false-iterator" +message = "Expression being iterated (type `false|list`) might be `false` at runtime." +count = 1 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "possibly-null-argument" +message = "Argument #1 of function `str_replace` is possibly `null`, but parameter type `array|string` does not accept it." +count = 2 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "possibly-null-operand" +message = "Possibly null left operand used in string concatenation (type `null|string`)." +count = 1 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "possibly-null-operand" +message = "Possibly null middle operand used in string concatenation (type `null|string`)." +count = 1 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "possibly-null-operand" +message = "Possibly null right operand used in string concatenation (type `null|string`)." +count = 2 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `int(0)` accessed on `array`." +count = 4 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `int(1)` accessed on `array`." +count = 2 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\Adapter\Exception\VunerablePlatformQuoteException` in `PhpDb\Adapter\Platform\AbstractPlatform::quoteValue`.' +count = 1 + +[[issues]] +file = "src/Adapter/Platform/PlatformInterface.php" +code = "imprecise-type" +message = "Type `array` in parameter `$additionalSafeWords` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Adapter/Platform/PlatformInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\Platform\PlatformInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Platform/Sql92.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Adapter\Platform\Sql92` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Adapter/Platform/Sql92.php" +code = "missing-constant-type" +message = "Class constant `PLATFORM_NAME` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Platform/Sql92.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\Adapter\Exception\VunerablePlatformQuoteException` in `PhpDb\Adapter\Platform\Sql92::quoteValue`.' +count = 1 + +[[issues]] +file = "src/Adapter/Profiler/Profiler.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Adapter\Profiler\Profiler` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Adapter/Profiler/Profiler.php" +code = "falsable-return-statement" +message = '''Function `PhpDb\Adapter\Profiler\Profiler::getLastProfile` is declared to return `array{'elapse': float|null, 'end': float|null, 'parameters': PhpDb\Adapter\ParameterContainer|null, 'sql': string, 'start': float}|null` but possibly returns 'false' (inferred as `array{'elapse': float|null, 'end': float|null, 'parameters': PhpDb\Adapter\ParameterContainer|null, 'sql': string, 'start': float}|false`).''' +count = 1 + +[[issues]] +file = "src/Adapter/Profiler/Profiler.php" +code = "invalid-property-assignment-value" +message = '''Invalid type for property `$profiles`: expected `array`, but got `array`.''' +count = 1 + +[[issues]] +file = "src/Adapter/Profiler/Profiler.php" +code = "invalid-return-statement" +message = '''Invalid return type for function `PhpDb\Adapter\Profiler\Profiler::getLastProfile`: expected `array{'elapse': float|null, 'end': float|null, 'parameters': PhpDb\Adapter\ParameterContainer|null, 'sql': string, 'start': float}|null`, but found `array{'elapse': float|null, 'end': float|null, 'parameters': PhpDb\Adapter\ParameterContainer|null, 'sql': string, 'start': float}|false`.''' +count = 1 + +[[issues]] +file = "src/Adapter/Profiler/Profiler.php" +code = "missing-property-type" +message = "Property `$currentIndex` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Profiler/Profiler.php" +code = "missing-property-type" +message = "Property `$profiles` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Adapter/Profiler/Profiler.php" +code = "possibly-null-operand" +message = "Right operand in arithmetic operation might be `null` (type `float|null`)." +count = 1 + +[[issues]] +file = "src/Adapter/Profiler/Profiler.php" +code = "possibly-undefined-int-array-index" +message = '''Possibly undefined array key `int` accessed on `array`.''' +count = 1 + +[[issues]] +file = "src/Adapter/Profiler/Profiler.php" +code = "possibly-undefined-string-array-index" +message = '''Possibly undefined array key accessed on `array{'elapse': float|null, 'end': float, 'parameters': PhpDb\Adapter\ParameterContainer|null, 'sql': string, 'start': float}|array{'end': float}`.''' +count = 1 + +[[issues]] +file = "src/Adapter/Profiler/Profiler.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\Adapter\Exception\RuntimeException` in `PhpDb\Adapter\Profiler\Profiler::profilerFinish`.' +count = 1 + +[[issues]] +file = "src/Adapter/Profiler/ProfilerAwareInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\Profiler\ProfilerAwareInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/Profiler/ProfilerInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\Profiler\ProfilerInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/SchemaAwareInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\SchemaAwareInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Adapter/StatementContainer.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Adapter\StatementContainer` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Adapter/StatementContainer.php" +code = "incompatible-parameter-type" +message = 'Parameter `$sql` of `PhpDb\Adapter\StatementContainer::setsql()` expects type `string` but parent `PhpDb\Adapter\StatementContainerInterface::setsql()` expects type `null|string`' +count = 1 + +[[issues]] +file = "src/Adapter/StatementContainerInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Adapter\StatementContainerInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/ConfigProvider.php" +code = "imprecise-type" +message = "Type `array` in return type of `__invoke` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/ConfigProvider.php" +code = "imprecise-type" +message = "Type `array` in return type of `getDependencies` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/ConfigProvider.php" +code = "missing-constant-type" +message = "Class constant `NAMED_ADAPTER_KEY` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "imprecise-type" +message = "Type `array` in parameter `$options` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "imprecise-type" +message = "Type `array` in property `$config` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "imprecise-type" +message = "Type `array` in return type of `getConfig` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "incompatible-parameter-type" +message = 'Parameter `$container` of `PhpDb\Container\AbstractAdapterInterfaceFactory::__invoke()` expects type `Psr\Container\ContainerInterface&Laminas\ServiceManager\ServiceManager` but parent `Laminas\ServiceManager\Factory\FactoryInterface::__invoke()` expects type `Psr\Container\ContainerInterface`' +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Container\AbstractAdapterInterfaceFactory::getConfig`: expected `array`, but found `array|null`.' +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `Laminas\ServiceManager\ServiceManager::build`: expected `array|null`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "mixed-property-type-coercion" +message = "A value with a less specific type `nonnull` is being assigned to property `$config` (array|null)." +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "nullable-return-statement" +message = 'Function `PhpDb\Container\AbstractAdapterInterfaceFactory::getConfig` is declared to return `array` but possibly returns a nullable value (inferred as `array|null`).' +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "possibly-null-argument" +message = 'Argument #3 of method `PhpDb\Adapter\Adapter::__construct` is possibly `null`, but parameter type `PhpDb\ResultSet\ResultSetInterface` does not accept it.' +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "possibly-null-array-access" +message = "Cannot perform array access on possibly `null` value." +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "redundant-docblock-type" +message = "Redundant docblock type for variable `$driver`." +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "redundant-docblock-type" +message = "Redundant docblock type for variable `$platform`." +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "redundant-docblock-type" +message = "Redundant docblock type for variable `$profiler`." +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `Psr\Container\ContainerExceptionInterface` in `PhpDb\Container\AbstractAdapterInterfaceFactory::getConfig`.' +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `Psr\Container\NotFoundExceptionInterface` in `PhpDb\Container\AbstractAdapterInterfaceFactory::getConfig`.' +count = 1 + +[[issues]] +file = "src/Container/AdapterInterfaceDelegator.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Container\AdapterInterfaceDelegator` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Container/AdapterInterfaceDelegator.php" +code = "imprecise-type" +message = "Type `array` in parameter `$options` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Container/AdapterInterfaceDelegator.php" +code = "imprecise-type" +message = "Type `array` in parameter `$state` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Container/AdapterInterfaceDelegator.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Container\AdapterInterfaceDelegator::__construct`: expected `string`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Container/AdapterInterfaceDelegator.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/Container/AdapterInterfaceDelegator.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\Exception\RuntimeException` in `PhpDb\Container\AdapterInterfaceDelegator::__invoke`.' +count = 1 + +[[issues]] +file = "src/Container/AdapterInterfaceDelegator.php" +code = "unused-parameter" +message = "Parameter `$options` is never used." +count = 1 + +[[issues]] +file = "src/Container/AdapterInterfaceFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `Laminas\ServiceManager\ServiceManager::build`: expected `array|null`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Container/AdapterInterfaceFactory.php" +code = "mixed-assignment" +message = "Assigning `nonnull` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/Container/AdapterInterfaceFactory.php" +code = "possibly-null-argument" +message = 'Argument #1 of method `Laminas\ServiceManager\ServiceManager::build` is possibly `null`, but parameter type `class-string|class-string|string` does not accept it.' +count = 1 + +[[issues]] +file = "src/Container/AdapterInterfaceFactory.php" +code = "possibly-null-argument" +message = 'Argument #3 of method `PhpDb\Adapter\Adapter::__construct` is possibly `null`, but parameter type `PhpDb\ResultSet\ResultSetInterface` does not accept it.' +count = 1 + +[[issues]] +file = "src/Container/AdapterInterfaceFactory.php" +code = "redundant-docblock-type" +message = "Redundant docblock type for variable `$adapterPlatform`." +count = 1 + +[[issues]] +file = "src/Container/AdapterInterfaceFactory.php" +code = "redundant-docblock-type" +message = "Redundant docblock type for variable `$profilerInterface`." +count = 1 + +[[issues]] +file = "src/Container/AdapterInterfaceFactory.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `Laminas\ServiceManager\Exception\ServiceNotCreatedException` in `PhpDb\Container\AdapterInterfaceFactory::__invoke`.' +count = 1 + +[[issues]] +file = "src/Container/AdapterInterfaceFactory.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\Exception\ContainerException` in `PhpDb\Container\AdapterInterfaceFactory::__invoke`.' +count = 1 + +[[issues]] +file = "src/Container/AdapterInterfaceFactory.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `Psr\Container\ContainerExceptionInterface` in `PhpDb\Container\AdapterInterfaceFactory::__invoke`.' +count = 1 + +[[issues]] +file = "src/Container/AdapterInterfaceFactory.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `Psr\Container\NotFoundExceptionInterface` in `PhpDb\Container\AdapterInterfaceFactory::__invoke`.' +count = 1 + +[[issues]] +file = "src/Exception/ExceptionInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Exception\ExceptionInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Feature/FeatureInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Feature\FeatureInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Metadata/MetadataInterface.php" +code = "imprecise-type" +message = "Type `array` in return type of `getColumnNames` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Metadata/MetadataInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Metadata\MetadataInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Metadata/Object/AbstractTableObject.php" +code = "imprecise-type" +message = "Type `array` in parameter `$columns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Metadata/Object/AbstractTableObject.php" +code = "imprecise-type" +message = "Type `array` in parameter `$constraints` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Metadata/Object/AbstractTableObject.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\Metadata\Object\AbstractTableObject` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Metadata/Object/ColumnObject.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Metadata\Object\ColumnObject` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Metadata/Object/ColumnObject.php" +code = "imprecise-type" +message = "Type `array` in parameter `$erratas` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Metadata/Object/ColumnObject.php" +code = "imprecise-type" +message = "Type `array` in property `$errata` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Metadata/Object/ColumnObject.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Metadata\Object\ColumnObject::getColumnDefault`: expected `null|string`, but found `bool|int|null|string`.' +count = 1 + +[[issues]] +file = "src/Metadata/Object/ColumnObject.php" +code = "less-specific-argument" +message = 'Argument type mismatch for argument #1 of `PhpDb\Metadata\Object\ColumnObject::setErrata`: expected `string`, but provided type `array-key` is less specific.' +count = 1 + +[[issues]] +file = "src/Metadata/Object/ColumnObject.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Metadata/Object/ColumnObject.php" +code = "uninitialized-property" +message = 'Property `$name` is not initialized in the constructor of class `PhpDb\Metadata\Object\ColumnObject`.' +count = 1 + +[[issues]] +file = "src/Metadata/Object/ColumnObject.php" +code = "uninitialized-property" +message = 'Property `$tableName` is not initialized in the constructor of class `PhpDb\Metadata\Object\ColumnObject`.' +count = 1 + +[[issues]] +file = "src/Metadata/Object/ConstraintKeyObject.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Metadata\Object\ConstraintKeyObject` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Metadata/Object/ConstraintKeyObject.php" +code = "missing-constant-type" +message = "Class constant `FK_CASCADE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Metadata/Object/ConstraintKeyObject.php" +code = "missing-constant-type" +message = "Class constant `FK_NO_ACTION` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Metadata/Object/ConstraintKeyObject.php" +code = "missing-constant-type" +message = "Class constant `FK_RESTRICT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Metadata/Object/ConstraintKeyObject.php" +code = "missing-constant-type" +message = "Class constant `FK_SET_DEFAULT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Metadata/Object/ConstraintKeyObject.php" +code = "missing-constant-type" +message = "Class constant `FK_SET_NULL` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Metadata/Object/ConstraintObject.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Metadata\Object\ConstraintObject` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Metadata/Object/ConstraintObject.php" +code = "uninitialized-property" +message = 'Property `$name` is not initialized in the constructor of class `PhpDb\Metadata\Object\ConstraintObject`.' +count = 1 + +[[issues]] +file = "src/Metadata/Object/ConstraintObject.php" +code = "uninitialized-property" +message = 'Property `$tableName` is not initialized in the constructor of class `PhpDb\Metadata\Object\ConstraintObject`.' +count = 1 + +[[issues]] +file = "src/Metadata/Object/TableObject.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Metadata\Object\TableObject` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Metadata/Object/TriggerObject.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Metadata\Object\TriggerObject` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Metadata/Object/ViewObject.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Metadata\Object\ViewObject` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "imprecise-type" +message = "Type `array` in property `$data` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "imprecise-type" +message = "Type `array` in return type of `getColumnNames` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "invalid-docblock" +message = "Unexpected token in PHPDoc type" +count = 2 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "invalid-iterator" +message = "The expression provided to `foreach` is not iterable. It resolved to type `mixed`, which is not iterable." +count = 4 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Metadata\Source\AbstractSource::getTables`: expected `array`, but found `list`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Metadata\Source\AbstractSource::getViews`: expected `array`, but found `list`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "less-specific-argument" +message = 'Argument type mismatch for argument #1 of `PhpDb\Metadata\Source\AbstractSource::getConstraint`: expected `string`, but provided type `array-key` is less specific.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "less-specific-nested-return-statement" +message = '''Returned type `array{}|list` is less specific than the declared return type `array` for function `PhpDb\Metadata\Source\AbstractSource::getTableNames` due to nested 'mixed'.''' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "less-specific-nested-return-statement" +message = '''Returned type `array{}|list` is less specific than the declared return type `array` for function `PhpDb\Metadata\Source\AbstractSource::getViewNames` due to nested 'mixed'.''' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "less-specific-return-statement" +message = 'Returned type `list` is less specific than the declared return type `array` for function `PhpDb\Metadata\Source\AbstractSource::getTableNames`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "less-specific-return-statement" +message = 'Returned type `list` is less specific than the declared return type `array` for function `PhpDb\Metadata\Source\AbstractSource::getTriggerNames`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "malformed-docblock-comment" +message = "Failed to parse class-like docblock comment." +count = 2 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\Metadata\Source\AbstractSource` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "missing-constant-type" +message = "Class constant `DEFAULT_SCHEMA` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ColumnObject::setColumnDefault`: expected `bool|int|null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ColumnObject::setDataType`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ColumnObject::setErratas`: expected `array`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ColumnObject::setIsNullable`: expected `bool|null`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ColumnObject::setNumericUnsigned`: expected `bool|null`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ConstraintKeyObject::__construct`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ConstraintKeyObject::setForeignKeyDeleteRule`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ConstraintKeyObject::setForeignKeyUpdateRule`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ConstraintKeyObject::setOrdinalPosition`: expected `int`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ConstraintKeyObject::setReferencedColumnName`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ConstraintKeyObject::setReferencedTableName`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ConstraintObject::setCheckClause`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ConstraintObject::setColumns`: expected `array`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ConstraintObject::setDeleteRule`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ConstraintObject::setMatchOption`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ConstraintObject::setReferencedColumns`: expected `array`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ConstraintObject::setReferencedTableName`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ConstraintObject::setReferencedTableSchema`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ConstraintObject::setType`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ConstraintObject::setUpdateRule`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\TriggerObject::setActionCondition`: expected `null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\TriggerObject::setActionOrder`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\TriggerObject::setActionOrientation`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\TriggerObject::setActionReferenceNewRow`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\TriggerObject::setActionReferenceNewTable`: expected `null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\TriggerObject::setActionReferenceOldRow`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\TriggerObject::setActionReferenceOldTable`: expected `null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\TriggerObject::setActionStatement`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\TriggerObject::setActionTiming`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\TriggerObject::setCreated`: expected `DateTime|null`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\TriggerObject::setEventManipulation`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\TriggerObject::setEventObjectCatalog`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\TriggerObject::setEventObjectSchema`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\TriggerObject::setEventObjectTable`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ViewObject::setCheckOption`: expected `null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ViewObject::setIsUpdatable`: expected `bool|null`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Object\ViewObject::setViewDefinition`: expected `null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\Source\AbstractSource::getColumn`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `array_keys`: expected `array<('K.array_keys() extends array-key), ('V.array_keys() extends mixed)>`, but found `mixed`." +count = 4 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-array-access" +message = "Unsafe array access on type `mixed`." +count = 47 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-array-access" +message = "Unsafe array access on type `nonnull`." +count = 4 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 13 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-assignment" +message = "Assigning `nonnull` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-operand" +message = "Invalid middle operand: type `mixed` cannot be reliably used in string concatenation." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\Metadata\Source\AbstractSource::getSchemas`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('constraint_keys')` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('constraint_references')` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('constraints')` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('schemas')` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('table_names')` accessed on `array`." +count = 4 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('triggers')` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "too-many-arguments" +message = 'Too many arguments provided for method `PhpDb\Metadata\Source\AbstractSource::prepareDataHierarchy`.' +count = 6 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `Exception` in `PhpDb\Metadata\Source\AbstractSource::getColumnNames`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `Exception` in `PhpDb\Metadata\Source\AbstractSource::getColumn`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `Exception` in `PhpDb\Metadata\Source\AbstractSource::getConstraint`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `Exception` in `PhpDb\Metadata\Source\AbstractSource::getTable`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `Exception` in `PhpDb\Metadata\Source\AbstractSource::getTrigger`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `Exception` in `PhpDb\Metadata\Source\AbstractSource::getView`.' +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "unused-parameter" +message = "Parameter `$table` is never used." +count = 2 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "ambiguous-object-method-access" +message = "Cannot statically verify method call on a generic `object` type." +count = 3 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "imprecise-type" +message = "Type `array` in property `$buffer` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "imprecise-type" +message = "Type `array` in return type of `current` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "imprecise-type" +message = "Type `iterable` in parameter `$dataSource` is imprecise, equivalent to `iterable`." +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "incompatible-return-type" +message = 'Return type `int|null` of `PhpDb\ResultSet\AbstractResultSet::count()` is incompatible with parent return type `int` of `Countable::count()`' +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "invalid-property-assignment-value" +message = 'Invalid type for property `$dataSource`: expected `Iterator|IteratorAggregate|PhpDb\Adapter\Driver\ResultInterface|null`, but got `iterable`.' +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "invalid-type-cast" +message = "Casting `mixed` to `array`." +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "less-specific-nested-return-statement" +message = '''Returned type `array{}|non-empty-list` is less specific than the declared return type `array|PhpDb\ResultSet\RowPrototypeInterface|array>` for function `PhpDb\ResultSet\AbstractResultSet::toArray` due to nested 'mixed'.''' +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\ResultSet\AbstractResultSet` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `count`: expected `Countable|array`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 3 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\ResultSet\AbstractResultSet::current`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\ResultSet\AbstractResultSet::current`. Saw type `nonnull`.' +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\ResultSet\AbstractResultSet::valid`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "non-existent-method" +message = "Method `current` does not exist on type `IteratorAggregate`." +count = 3 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "non-existent-method" +message = "Method `key` does not exist on type `IteratorAggregate`." +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "non-existent-method" +message = "Method `next` does not exist on type `IteratorAggregate`." +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "non-existent-method" +message = "Method `rewind` does not exist on type `IteratorAggregate`." +count = 3 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "non-existent-method" +message = "Method `valid` does not exist on type `IteratorAggregate`." +count = 2 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 6 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `count`: expected `Countable|array`, but possibly received `Iterator|IteratorAggregate|PhpDb\Adapter\Driver\ResultInterface`.' +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "property-type-coercion" +message = 'A value of a less specific type `Traversable` is being assigned to property `$dataSource` (Iterator|IteratorAggregate|PhpDb\Adapter\Driver\ResultInterface|null).' +count = 1 + +[[issues]] +file = "src/ResultSet/Exception/ExceptionInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\ResultSet\Exception\ExceptionInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/ResultSet/Exception/InvalidArgumentException.php" +code = "class-must-be-final" +message = 'Class `PhpDb\ResultSet\Exception\InvalidArgumentException` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/ResultSet/Exception/RuntimeException.php" +code = "class-must-be-final" +message = 'Class `PhpDb\ResultSet\Exception\RuntimeException` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/ResultSet/HydratingResultSet.php" +code = "class-must-be-final" +message = 'Class `PhpDb\ResultSet\HydratingResultSet` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/ResultSet/HydratingResultSet.php" +code = "less-specific-argument" +message = 'Argument type mismatch for argument #1 of `PhpDb\ResultSet\HydratingResultSet::setRowPrototype`: expected `ArrayObject|PhpDb\ResultSet\RowPrototypeInterface`, but provided type `object` is less specific.' +count = 1 + +[[issues]] +file = "src/ResultSet/HydratingResultSet.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/ResultSet/HydratingResultSet.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\ResultSet\HydratingResultSet::current`. Saw type `nonnull`.' +count = 1 + +[[issues]] +file = "src/ResultSet/HydratingResultSet.php" +code = "non-existent-method" +message = "Method `current` does not exist on type `IteratorAggregate`." +count = 1 + +[[issues]] +file = "src/ResultSet/HydratingResultSet.php" +code = "overly-wide-return-type" +message = "Declared return type `null|object` for `getobjectprototype` has unused branches: `null`." +count = 1 + +[[issues]] +file = "src/ResultSet/HydratingResultSet.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 1 + +[[issues]] +file = "src/ResultSet/HydratingResultSet.php" +code = "possibly-null-argument" +message = 'Argument #1 of method `Laminas\Hydrator\ExtractionInterface::extract` is possibly `null`, but parameter type `object` does not accept it.' +count = 1 + +[[issues]] +file = "src/ResultSet/ResultSet.php" +code = "class-must-be-final" +message = 'Class `PhpDb\ResultSet\ResultSet` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/ResultSet/ResultSet.php" +code = "imprecise-type" +message = "Type `array` in return type of `current` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/ResultSet/ResultSet.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\ResultSet\ResultSet::getReturnType`: expected `enum(PhpDb\ResultSet\ResultSetReturnType)`, but found `enum(PhpDb\ResultSet\ResultSetReturnType)|string`.' +count = 1 + +[[issues]] +file = "src/ResultSet/ResultSet.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\ResultSet\ResultSet::getRowPrototype`: expected `ArrayObject|PhpDb\ResultSet\RowPrototypeInterface`, but found `ArrayObject|PhpDb\ResultSet\RowPrototypeInterface|null`.' +count = 1 + +[[issues]] +file = "src/ResultSet/ResultSet.php" +code = "less-specific-return-statement" +message = 'Returned type `array|null|object` is less specific than the declared return type `ArrayObject|PhpDb\ResultSet\RowPrototypeInterface|array|null` for function `PhpDb\ResultSet\ResultSet::current`.' +count = 1 + +[[issues]] +file = "src/ResultSet/ResultSet.php" +code = "missing-constant-type" +message = "Class constant `TYPE_ARRAYOBJECT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/ResultSet/ResultSet.php" +code = "missing-constant-type" +message = "Class constant `TYPE_ARRAY` is missing a type hint." +count = 1 + +[[issues]] +file = "src/ResultSet/ResultSet.php" +code = "nullable-return-statement" +message = 'Function `PhpDb\ResultSet\ResultSet::getRowPrototype` is declared to return `ArrayObject|PhpDb\ResultSet\RowPrototypeInterface` but possibly returns a nullable value (inferred as `ArrayObject|PhpDb\ResultSet\RowPrototypeInterface|null`).' +count = 1 + +[[issues]] +file = "src/ResultSet/ResultSetInterface.php" +code = "imprecise-type" +message = "Type `iterable` in parameter `$dataSource` is imprecise, equivalent to `iterable`." +count = 1 + +[[issues]] +file = "src/ResultSet/ResultSetInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\ResultSet\ResultSetInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/ResultSet/ResultSetInterface.php" +code = "missing-template-parameter" +message = "Too few template arguments for `Iterator`: expected at least 2, but found 0." +count = 1 + +[[issues]] +file = "src/ResultSet/RowPrototypeInterface.php" +code = "imprecise-type" +message = "Type `array` in parameter `$array` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/ResultSet/RowPrototypeInterface.php" +code = "imprecise-type" +message = "Type `array` in return type of `exchangeArray` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/ResultSet/RowPrototypeInterface.php" +code = "imprecise-type" +message = "Type `array` in return type of `toArray` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/ResultSet/RowPrototypeInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\ResultSet\RowPrototypeInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "imprecise-type" +message = "Type `array` in parameter `$array` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "imprecise-type" +message = "Type `array` in parameter `$rowData` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "imprecise-type" +message = "Type `array` in property `$data` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "imprecise-type" +message = "Type `array` in property `$primaryKeyColumn` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "imprecise-type" +message = "Type `array` in property `$primaryKeyData` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "imprecise-type" +message = "Type `array` in return type of `toArray` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "incompatible-parameter-type" +message = 'Parameter `$offset` of `PhpDb\RowGateway\AbstractRowGateway::offsetexists()` expects type `string` but parent `ArrayAccess::offsetexists()` expects type `mixed`' +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "incompatible-parameter-type" +message = 'Parameter `$offset` of `PhpDb\RowGateway\AbstractRowGateway::offsetget()` expects type `string` but parent `ArrayAccess::offsetget()` expects type `mixed`' +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "incompatible-parameter-type" +message = 'Parameter `$offset` of `PhpDb\RowGateway\AbstractRowGateway::offsetset()` expects type `string` but parent `ArrayAccess::offsetset()` expects type `mixed`' +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "incompatible-parameter-type" +message = 'Parameter `$offset` of `PhpDb\RowGateway\AbstractRowGateway::offsetunset()` expects type `string` but parent `ArrayAccess::offsetunset()` expects type `mixed`' +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "invalid-array-element-key" +message = "Invalid array key type." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "less-specific-return-statement" +message = 'Returned type `array` is less specific than the declared return type `array` for function `PhpDb\RowGateway\AbstractRowGateway::exchangeArray`.' +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\RowGateway\AbstractRowGateway` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "missing-template-parameter" +message = "Too few template arguments for `ArrayAccess`: expected at least 2, but found 0." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\RowGateway\AbstractRowGateway::populate`: expected `array`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Sql::prepareStatementForSqlObject`: expected `PhpDb\Sql\PreparableSqlInterface`, but found `mixed`.' +count = 4 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 19 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "mixed-method-access" +message = "Attempting to access a method on a non-object type (`mixed`)." +count = 14 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "mixed-operand" +message = "Left operand in `&&` operation has `mixed` type." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\RowGateway\AbstractRowGateway::delete`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\RowGateway\AbstractRowGateway::save`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 10 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "possibly-null-argument" +message = "Argument #1 of function `count` is possibly `null`, but parameter type `Countable|array` does not accept it." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "possibly-null-array-access" +message = "Cannot perform array access on possibly `null` value." +count = 4 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "possibly-null-iterator" +message = "Expression being iterated (type `array|null`) might be `null` at runtime." +count = 5 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `int(0)` accessed on `non-empty-array`." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `mixed` accessed on `array`." +count = 5 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string` accessed on `array`." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "redundant-comparison" +message = "Avoid direct comparison with boolean literals." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\RowGateway\Exception\RuntimeException` in `PhpDb\RowGateway\AbstractRowGateway::initialize`.' +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\RowGateway\Exception\RuntimeException` in `PhpDb\RowGateway\AbstractRowGateway::populate`.' +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\RowGateway\Exception\RuntimeException` in `PhpDb\RowGateway\AbstractRowGateway::save`.' +count = 1 + +[[issues]] +file = "src/RowGateway/Exception/ExceptionInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\RowGateway\Exception\ExceptionInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/RowGateway/Exception/InvalidArgumentException.php" +code = "class-must-be-final" +message = 'Class `PhpDb\RowGateway\Exception\InvalidArgumentException` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/RowGateway/Exception/RuntimeException.php" +code = "class-must-be-final" +message = 'Class `PhpDb\RowGateway\Exception\RuntimeException` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/RowGateway/Feature/AbstractFeature.php" +code = "imprecise-type" +message = "Type `array` in property `$sharedData` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/RowGateway/Feature/AbstractFeature.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\RowGateway\Feature\AbstractFeature` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/RowGateway/Feature/FeatureInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\RowGateway\Feature\FeatureInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/RowGateway/Feature/FeatureSet.php" +code = "imprecise-type" +message = "Type `array` in parameter `$args` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/RowGateway/Feature/FeatureSet.php" +code = "imprecise-type" +message = "Type `array` in parameter `$arguments` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/RowGateway/Feature/FeatureSet.php" +code = "imprecise-type" +message = "Type `array` in parameter `$features` is imprecise, equivalent to `array`." +count = 2 + +[[issues]] +file = "src/RowGateway/Feature/FeatureSet.php" +code = "imprecise-type" +message = "Type `array` in property `$magicSpecifications` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/RowGateway/Feature/FeatureSet.php" +code = "missing-constant-type" +message = "Class constant `APPLY_HALT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/RowGateway/Feature/FeatureSet.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\RowGateway\Feature\FeatureSet::addFeature`: expected `PhpDb\RowGateway\Feature\FeatureInterface`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/RowGateway/Feature/FeatureSet.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/RowGateway/Feature/FeatureSet.php" +code = "string-member-selector" +message = "This member selector uses a non-literal string type (`string`); its specific value cannot be statically determined." +count = 1 + +[[issues]] +file = "src/RowGateway/Feature/FeatureSet.php" +code = "unused-parameter" +message = "Parameter `$arguments` is never used." +count = 1 + +[[issues]] +file = "src/RowGateway/Feature/FeatureSet.php" +code = "unused-parameter" +message = "Parameter `$method` is never used." +count = 2 + +[[issues]] +file = "src/RowGateway/Feature/FeatureSet.php" +code = "unused-parameter" +message = "Parameter `$property` is never used." +count = 4 + +[[issues]] +file = "src/RowGateway/Feature/FeatureSet.php" +code = "unused-parameter" +message = "Parameter `$value` is never used." +count = 1 + +[[issues]] +file = "src/RowGateway/Feature/FeatureSet.php" +code = "unused-property" +message = "Property `$magicSpecifications` is never used." +count = 1 + +[[issues]] +file = "src/RowGateway/RowGateway.php" +code = "class-must-be-final" +message = 'Class `PhpDb\RowGateway\RowGateway` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/RowGateway/RowGateway.php" +code = "imprecise-type" +message = "Type `array` in parameter `$primaryKeyColumn` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/RowGateway/RowGatewayInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\RowGateway\RowGatewayInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractExpression.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\Sql\AbstractExpression` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractPreparableSql.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\Sql\AbstractPreparableSql` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "imprecise-type" +message = "Type `array` in parameter `$column` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "imprecise-type" +message = "Type `array` in parameter `$parameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "imprecise-type" +message = "Type `array` in parameter `$specifications` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "imprecise-type" +message = "Type `array` in property `$instanceParameterIndex` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "imprecise-type" +message = "Type `array` in return type of `resolveTable` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "invalid-iterator" +message = "The expression provided to `foreach` is not iterable. It resolved to type `mixed`, which is not iterable." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "invalid-member-selector" +message = "Invalid type `array-key` used as a member selector; a string is expected." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "invalid-operand" +message = "Invalid type for left operand." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "less-specific-argument" +message = 'Argument type mismatch for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteIdentifier`: expected `string`, but provided type `array-key|null` is less specific.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "less-specific-argument" +message = 'Argument type mismatch for argument #3 of `PhpDb\Sql\AbstractSql::processExpressionOrSelect`: expected `int`, but provided type `array-key` is less specific.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "less-specific-nested-argument-type" +message = "Argument type mismatch for argument #2 of `implode`: expected `array|null`, but provided type `array` is less specific." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "less-specific-nested-argument-type" +message = "Argument type mismatch for argument #2 of `vsprintf`: expected `array`, but provided type `array` is less specific." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "less-specific-nested-argument-type" +message = "Argument type mismatch for argument #2 of `vsprintf`: expected `array`, but provided type `array|list{mixed}` is less specific." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "less-specific-nested-argument-type" +message = "Argument type mismatch for argument #2 of `vsprintf`: expected `array`, but provided type `array{}|non-empty-list` is less specific." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\Sql\AbstractSql` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteIdentifierInFragment`: expected `string`, but found `mixed`.' +count = 2 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteIdentifierInFragment`: expected `string`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteIdentifier`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteValue`: expected `string`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `count`: expected `Countable|array`, but found `mixed`." +count = 2 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `implode`: expected `array|string`, but found `nonnull`." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `strtoupper`: expected `string`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `vsprintf`: expected `string`, but found `mixed`." +count = 2 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `vsprintf`: expected `string`, but found `nonnull`." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-argument" +message = "Invalid argument type for argument #2 of `vsprintf`: expected `array`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #3 of `PhpDb\Sql\AbstractSql::processExpressionParameterName`: expected `int`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-array-access" +message = "Unsafe array access on type `mixed`." +count = 4 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 11 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-assignment" +message = "Assigning `nonnull` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-operand" +message = "Casting `mixed` to `bool`." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-operand" +message = "Invalid left operand: type `nonnull` cannot be reliably used in string concatenation." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "mixed-operand" +message = "Invalid middle operand: type `mixed` cannot be reliably used in string concatenation." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "non-iterable-object-iteration" +message = 'Iterating over object of type `PhpDb\Sql\ExpressionInterface` which does not implement `Iterator` or `IteratorAggregate`.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "non-iterable-object-iteration" +message = 'Iterating over object of type `PhpDb\Sql\SqlInterface` which does not implement `Iterator` or `IteratorAggregate`.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "overly-wide-return-type" +message = "Declared return type `array|null|string` for `resolvetable` has unused branches: `array`." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "overly-wide-return-type" +message = "Declared return type `null|string` for `processexpressionparametername` has unused branches: `null`." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "possibly-invalid-iterator" +message = 'The expression provided to `foreach` (type `PhpDb\Sql\ExpressionInterface|PhpDb\Sql\SqlInterface|array|null|scalar`) might not be iterable at runtime.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "possibly-null-argument" +message = "Argument #1 of function `get_object_vars` is possibly `null`, but parameter type `object` does not accept it." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "possibly-null-argument" +message = 'Argument #1 of method `PhpDb\Adapter\Platform\PlatformInterface::quoteIdentifier` is possibly `null`, but parameter type `string` does not accept it.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "possibly-null-argument" +message = 'Argument #1 of method `PhpDb\Sql\AbstractSql::processExpressionParameterName` is possibly `null`, but parameter type `bool|float|int|string` does not accept it.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "possibly-null-argument" +message = 'Argument #4 of method `PhpDb\Sql\AbstractSql::processExpressionParameterName` is possibly `null`, but parameter type `PhpDb\Adapter\Driver\DriverInterface` does not accept it.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "possibly-null-iterator" +message = 'Expression being iterated (type `PhpDb\Sql\ExpressionInterface|PhpDb\Sql\SqlInterface|array|null|scalar`) might be `null` at runtime.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('column')` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "possibly-undefined-variable" +message = "Variable `$paramSpecs` might not have been defined on all execution paths leading to this point." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "string-member-selector" +message = "This member selector uses a non-literal string type (`non-empty-string`); its specific value cannot be statically determined." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\Sql\Exception\RuntimeException` in `PhpDb\Sql\AbstractSql::buildSqlString`.' +count = 1 + +[[issues]] +file = "src/Sql/Argument.php" +code = "imprecise-type" +message = "Type `array` in parameter `$values` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Argument.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\Argument\Values::__construct`: expected `list`, but possibly received `array`.' +count = 1 + +[[issues]] +file = "src/Sql/ArgumentInterface.php" +code = "imprecise-type" +message = "Type `array` in return type of `getValue` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/ArgumentInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Sql\ArgumentInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/ArgumentType.php" +code = "invalid-enum-case-value" +message = '''Invalid case value for `PhpDb\Sql\ArgumentType::Identifier`. Expected `string`, but got `class-string('PhpDb\Sql\Argument\Identifier')`.''' +count = 1 + +[[issues]] +file = "src/Sql/ArgumentType.php" +code = "invalid-enum-case-value" +message = '''Invalid case value for `PhpDb\Sql\ArgumentType::Identifiers`. Expected `string`, but got `class-string('PhpDb\Sql\Argument\Identifiers')`.''' +count = 1 + +[[issues]] +file = "src/Sql/ArgumentType.php" +code = "invalid-enum-case-value" +message = '''Invalid case value for `PhpDb\Sql\ArgumentType::Literal`. Expected `string`, but got `class-string('PhpDb\Sql\Argument\Literal')`.''' +count = 1 + +[[issues]] +file = "src/Sql/ArgumentType.php" +code = "invalid-enum-case-value" +message = '''Invalid case value for `PhpDb\Sql\ArgumentType::Select`. Expected `string`, but got `class-string('PhpDb\Sql\Argument\Select')`.''' +count = 1 + +[[issues]] +file = "src/Sql/ArgumentType.php" +code = "invalid-enum-case-value" +message = '''Invalid case value for `PhpDb\Sql\ArgumentType::Value`. Expected `string`, but got `class-string('PhpDb\Sql\Argument\Value')`.''' +count = 1 + +[[issues]] +file = "src/Sql/ArgumentType.php" +code = "invalid-enum-case-value" +message = '''Invalid case value for `PhpDb\Sql\ArgumentType::Values`. Expected `string`, but got `class-string('PhpDb\Sql\Argument\Values')`.''' +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "array-to-string-conversion" +message = "Potential array in right operand of string concatenation." +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Combine` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "imprecise-type" +message = "Type `array` in parameter `$select` is imprecise, equivalent to `array`." +count = 5 + +[[issues]] +file = "src/Sql/Combine.php" +code = "incompatible-property-type" +message = 'Property `PhpDb\Sql\Combine::$specifications` has an incompatible type declaration from docblock.' +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "invalid-array-element" +message = "Cannot use spread operator on non-iterable type `mixed`." +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "missing-constant-type" +message = "Class constant `COLUMNS` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "missing-constant-type" +message = "Class constant `COMBINE_EXCEPT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "missing-constant-type" +message = "Class constant `COMBINE_INTERSECT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "missing-constant-type" +message = "Class constant `COMBINE_UNION` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "missing-constant-type" +message = "Class constant `COMBINE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Combine::combine`: expected `PhpDb\Sql\Select|array`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `PhpDb\Sql\Combine::combine`: expected `string`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #3 of `PhpDb\Sql\Combine::combine`: expected `string`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "mixed-array-access" +message = "Unsafe array access on type `mixed`." +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/Sql/Combine.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "possibly-null-argument" +message = "Argument #3 of function `str_replace` is possibly `null`, but parameter type `array|string` does not accept it." +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "possibly-null-array-access" +message = "Cannot perform array access on possibly `null` value." +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "possibly-undefined-int-array-index" +message = '''Possibly undefined array key `int(0)` accessed on `non-empty-array`.''' +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('combine')` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\AlterTable` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "imprecise-type" +message = "Type `array` in parameter `$options` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "imprecise-type" +message = "Type `array` in property `$addColumns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "imprecise-type" +message = "Type `array` in property `$addConstraints` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "imprecise-type" +message = "Type `array` in property `$changeColumns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "imprecise-type" +message = "Type `array` in property `$dropColumns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "imprecise-type" +message = "Type `array` in property `$dropConstraints` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "imprecise-type" +message = "Type `array` in property `$dropIndexes` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "imprecise-type" +message = "Type `array` in property `$options` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "imprecise-type" +message = "Type `array` in return type of `getOptions` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "imprecise-type" +message = "Type `array` in return type of `getRawState` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "imprecise-type" +message = "Type `array` in return type of `processAddColumns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "imprecise-type" +message = "Type `array` in return type of `processAddConstraints` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "imprecise-type" +message = "Type `array` in return type of `processChangeColumns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "imprecise-type" +message = "Type `array` in return type of `processDropColumns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "imprecise-type" +message = "Type `array` in return type of `processDropConstraints` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "imprecise-type" +message = "Type `array` in return type of `processDropIndexes` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "invalid-property-default-value" +message = 'Default value for property `PhpDb\Sql\Ddl\AlterTable::$specifications` is not assignable to its declared type.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "invalid-return-statement" +message = '''Invalid return type for function `PhpDb\Sql\Ddl\AlterTable::getRawState`: expected `array|string`, but found `PhpDb\Sql\TableIdentifier|array|array{'addColumns': mixed, 'addConstraints': mixed, 'changeColumns': mixed, 'dropColumns': mixed, 'dropConstraints': mixed, 'dropIndexes': mixed, 'table': mixed, 'tableOptions': mixed}|string`.''' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Sql\Ddl\AlterTable::processTable`: expected `array`, but found `list{array|null|string}`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "invalid-return-tag" +message = "Failed to resolve `@return` type string." +count = 6 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "less-specific-argument" +message = 'Argument type mismatch for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteIdentifier`: expected `string`, but provided type `array-key` is less specific.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "less-specific-argument" +message = "Argument type mismatch for argument #1 of `strtoupper`: expected `string`, but provided type `array-key` is less specific." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "missing-constant-type" +message = "Class constant `ADD_COLUMNS` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "missing-constant-type" +message = "Class constant `ADD_CONSTRAINTS` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "missing-constant-type" +message = "Class constant `CHANGE_COLUMNS` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "missing-constant-type" +message = "Class constant `DROP_COLUMNS` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "missing-constant-type" +message = "Class constant `DROP_CONSTRAINTS` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "missing-constant-type" +message = "Class constant `DROP_INDEXES` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "missing-constant-type" +message = "Class constant `TABLE_OPTIONS` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "missing-constant-type" +message = "Class constant `TABLE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteIdentifier`: expected `string`, but found `mixed`.' +count = 3 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteTrustedValue`: expected `bool|float|int|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\AbstractSql::processExpression`: expected `PhpDb\Sql\ExpressionInterface`, but found `mixed`.' +count = 3 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 8 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 5 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "possibly-null-argument" +message = 'Argument #2 of method `PhpDb\Sql\AbstractSql::processExpression` is possibly `null`, but parameter type `PhpDb\Adapter\Platform\PlatformInterface` does not accept it.' +count = 3 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "possibly-null-argument" +message = 'Argument #2 of method `PhpDb\Sql\AbstractSql::resolveTable` is possibly `null`, but parameter type `PhpDb\Adapter\Platform\PlatformInterface` does not accept it.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/AbstractLengthColumn.php" +code = "imprecise-type" +message = "Type `array` in parameter `$options` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/AbstractLengthColumn.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\Sql\Ddl\Column\AbstractLengthColumn` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/AbstractPrecisionColumn.php" +code = "imprecise-type" +message = "Type `array` in parameter `$options` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/AbstractPrecisionColumn.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\Sql\Ddl\Column\AbstractPrecisionColumn` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/AbstractTimestampColumn.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\Sql\Ddl\Column\AbstractTimestampColumn` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/BigInteger.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\BigInteger` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Binary.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\Binary` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Blob.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\Blob` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Boolean.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\Boolean` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Char.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\Char` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Column.php" +code = "imprecise-type" +message = "Type `array` in parameter `$options` is imprecise, equivalent to `array`." +count = 2 + +[[issues]] +file = "src/Sql/Ddl/Column/Column.php" +code = "imprecise-type" +message = "Type `array` in property `$options` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Column.php" +code = "imprecise-type" +message = "Type `array` in return type of `getOptions` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Column.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Ddl\Column\Column::setDefault`: expected `PhpDb\Sql\Argument\Literal|PhpDb\Sql\Argument\Value|null|scalar`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Column.php" +code = "redundant-comparison" +message = "Avoid direct comparison with boolean literals." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Column.php" +code = "uninitialized-property" +message = 'Property `$default` is not initialized in the constructor of class `PhpDb\Sql\Ddl\Column\Column`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/ColumnInterface.php" +code = "imprecise-type" +message = "Type `array` in return type of `getOptions` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/ColumnInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Sql\Ddl\Column\ColumnInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Date.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\Date` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Datetime.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\Datetime` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Decimal.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\Decimal` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Double.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\Double` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Floating.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\Floating` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Json.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\Json` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/SmallInteger.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\SmallInteger` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Text.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\Text` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Time.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\Time` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Timestamp.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\Timestamp` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Varbinary.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\Varbinary` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Varchar.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Column\Varchar` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/AbstractConstraint.php" +code = "imprecise-type" +message = "Type `array` in parameter `$columns` is imprecise, equivalent to `array`." +count = 2 + +[[issues]] +file = "src/Sql/Ddl/Constraint/AbstractConstraint.php" +code = "imprecise-type" +message = "Type `array` in property `$columns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/AbstractConstraint.php" +code = "imprecise-type" +message = "Type `array` in return type of `getColumns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/AbstractConstraint.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\Sql\Ddl\Constraint\AbstractConstraint` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/AbstractConstraint.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Argument\Identifier::__construct`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/AbstractConstraint.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `non-negative-int` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/Check.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Constraint\Check` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/Check.php" +code = "missing-parameter-type" +message = "Parameter `$expression` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/Check.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\Argument\Literal::__construct`: expected `string`, but possibly received `PhpDb\Sql\ExpressionInterface|non-empty-string`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/ConstraintInterface.php" +code = "imprecise-type" +message = "Type `array` in return type of `getColumns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/ConstraintInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Sql\Ddl\Constraint\ConstraintInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/ForeignKey.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Constraint\ForeignKey` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/ForeignKey.php" +code = "imprecise-type" +message = "Type `array` in parameter `$columns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/ForeignKey.php" +code = "imprecise-type" +message = "Type `array` in return type of `getReferenceColumn` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/ForeignKey.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `int(0)` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/ForeignKey.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `int(1)` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/PrimaryKey.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Constraint\PrimaryKey` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/UniqueKey.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Constraint\UniqueKey` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\CreateTable` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "imprecise-type" +message = "Type `array` in parameter `$options` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "imprecise-type" +message = "Type `array` in property `$columns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "imprecise-type" +message = "Type `array` in property `$constraints` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "imprecise-type" +message = "Type `array` in property `$options` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "imprecise-type" +message = "Type `array` in return type of `getOptions` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "invalid-operand" +message = "Left operand in `&&` operation is an `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "invalid-operand" +message = "Right operand in `&&` operation is an `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "invalid-property-default-value" +message = 'Default value for property `PhpDb\Sql\Ddl\CreateTable::$specifications` is not assignable to its declared type.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Sql\Ddl\CreateTable::processCombinedby`: expected `null|string`, but found `array|null|string`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "invalid-return-statement" +message = '''Invalid return type for function `PhpDb\Sql\Ddl\CreateTable::processTable`: expected `array`, but found `list{string('')|string('TEMPORARY '), string('')|string('IF NOT EXISTS '), array|null|string}`.''' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "less-specific-argument" +message = "Argument type mismatch for argument #1 of `strtoupper`: expected `string`, but provided type `array-key` is less specific." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "less-specific-nested-return-statement" +message = '''Returned type `PhpDb\Sql\TableIdentifier|array|array{'columns': mixed, 'constraints': mixed, 'table': mixed, 'tableOptions': mixed}|string` is less specific than the declared return type `array|string>|string` for function `PhpDb\Sql\Ddl\CreateTable::getRawState` due to nested 'mixed'.''' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "missing-constant-type" +message = "Class constant `COLUMNS` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "missing-constant-type" +message = "Class constant `CONSTRAINTS` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "missing-constant-type" +message = "Class constant `TABLE_OPTIONS` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "missing-constant-type" +message = "Class constant `TABLE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteTrustedValue`: expected `bool|float|int|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\AbstractSql::processExpression`: expected `PhpDb\Sql\ExpressionInterface`, but found `mixed`.' +count = 2 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 4 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "possibly-null-argument" +message = 'Argument #2 of method `PhpDb\Sql\AbstractSql::processExpression` is possibly `null`, but parameter type `PhpDb\Adapter\Platform\PlatformInterface` does not accept it.' +count = 2 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "possibly-null-argument" +message = 'Argument #2 of method `PhpDb\Sql\AbstractSql::resolveTable` is possibly `null`, but parameter type `PhpDb\Adapter\Platform\PlatformInterface` does not accept it.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('combinedBy')` accessed on `array|string>`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "unused-parameter" +message = "Parameter `$adapterPlatform` is never used." +count = 2 + +[[issues]] +file = "src/Sql/Ddl/DropTable.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\DropTable` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/DropTable.php" +code = "invalid-return-statement" +message = '''Invalid return type for function `PhpDb\Sql\Ddl\DropTable::processTable`: expected `array`, but found `list{string('')|string('IF EXISTS '), array|null|string}`.''' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/DropTable.php" +code = "missing-constant-type" +message = "Class constant `TABLE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/DropTable.php" +code = "possibly-null-argument" +message = 'Argument #2 of method `PhpDb\Sql\AbstractSql::resolveTable` is possibly `null`, but parameter type `PhpDb\Adapter\Platform\PlatformInterface` does not accept it.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Index/AbstractIndex.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\Sql\Ddl\Index\AbstractIndex` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Index/Index.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Ddl\Index\Index` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Index/Index.php" +code = "imprecise-type" +message = "Type `array` in parameter `$columns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Index/Index.php" +code = "imprecise-type" +message = "Type `array` in parameter `$lengths` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Index/Index.php" +code = "imprecise-type" +message = "Type `array` in property `$lengths` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Index/Index.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Argument\Identifier::__construct`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Index/Index.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `non-negative-int` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Delete` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "impossible-null-type-comparison" +message = 'Impossible condition: variable `$this->where` (type `PhpDb\Sql\Where`) will always be `null`.' +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "imprecise-type" +message = "Type `array` in parameter `$predicate` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "imprecise-type" +message = "Type `array` in parameter `$table` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "imprecise-type" +message = "Type `array` in property `$table` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Sql\Delete::processDelete`: expected `string`, but found `array|string`.' +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Sql\Delete::processWhere`: expected `null|string`, but found `array|string`.' +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "less-specific-nested-argument-type" +message = "Argument type mismatch for argument #2 of `str_replace`: expected `array|string`, but provided type `array|null|string` is less specific." +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "less-specific-nested-argument-type" +message = "Argument type mismatch for argument #3 of `str_replace`: expected `array|string`, but provided type `array|null|string` is less specific." +count = 2 + +[[issues]] +file = "src/Sql/Delete.php" +code = "missing-constant-type" +message = "Class constant `SPECIFICATION_DELETE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "missing-constant-type" +message = "Class constant `SPECIFICATION_WHERE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\AbstractSql::resolveTable`: expected `PhpDb\Sql\Select|PhpDb\Sql\TableIdentifier|null|string`, but possibly received `PhpDb\Sql\TableIdentifier|array|string`.' +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "possibly-null-argument" +message = "Argument #2 of function `str_replace` is possibly `null`, but parameter type `array|string` does not accept it." +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "possibly-null-argument" +message = "Argument #3 of function `str_replace` is possibly `null`, but parameter type `array|string` does not accept it." +count = 2 + +[[issues]] +file = "src/Sql/Delete.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('delete')` accessed on `array>`." +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('delete')` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('where')` accessed on `array>`." +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('where')` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "redundant-comparison" +message = "Redundant `===` comparison: left-hand side is never identical to right-hand side." +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "redundant-logical-operation" +message = "Redundant `||` operation: left operand is always false and right operand is evaluated." +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "redundant-null-coalesce" +message = "Redundant null coalesce: left-hand side can never be `null` or undefined." +count = 1 + +[[issues]] +file = "src/Sql/Exception/ExceptionInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Sql\Exception\ExceptionInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/Exception/InvalidArgumentException.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Exception\InvalidArgumentException` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Exception/RuntimeException.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Exception\RuntimeException` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Expression.php" +code = "imprecise-type" +message = "Type `array` in parameter `$parameters` is imprecise, equivalent to `array`." +count = 2 + +[[issues]] +file = "src/Sql/Expression.php" +code = "imprecise-type" +message = "Type `array` in return type of `getParameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Expression.php" +code = "missing-constant-type" +message = "Class constant `PLACEHOLDER` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Expression.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Argument\Value::__construct`: expected `bool|float|int|null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Expression.php" +code = "mixed-argument" +message = "Invalid argument type for argument #4 of `str_replace`: expected `int|null`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Sql/Expression.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Sql/Expression.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\Argument\Values::__construct`: expected `list`, but possibly received `array`.' +count = 1 + +[[issues]] +file = "src/Sql/Expression.php" +code = "possibly-null-argument" +message = "Argument #1 of function `array_unique` is possibly `null`, but parameter type `array` does not accept it." +count = 1 + +[[issues]] +file = "src/Sql/Expression.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array index accessed on `list>`." +count = 1 + +[[issues]] +file = "src/Sql/Expression.php" +code = "reference-to-undefined-variable" +message = "Reference created from a previously undefined variable `$count`." +count = 1 + +[[issues]] +file = "src/Sql/Expression.php" +code = "reference-to-undefined-variable" +message = "Reference created from a previously undefined variable `$matches`." +count = 1 + +[[issues]] +file = "src/Sql/ExpressionInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Sql\ExpressionInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/Having.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Having` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "imprecise-type" +message = "Type `array` in parameter `$array` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "imprecise-type" +message = "Type `array` in parameter `$columns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "imprecise-type" +message = "Type `array` in parameter `$table` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "imprecise-type" +message = "Type `array` in parameter `$values` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "imprecise-type" +message = "Type `array` in property `$columns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "imprecise-type" +message = "Type `array` in property `$select` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "imprecise-type" +message = "Type `array` in property `$table` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "imprecise-type" +message = "Type `array` in return type of `getRawState` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Sql\Insert::processInsert`: expected `null|string`, but found `array|string`.' +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Sql\Insert::processSelect`: expected `null|string`, but found `array|string`.' +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "less-specific-argument" +message = 'Argument type mismatch for argument #1 of `PhpDb\Adapter\Driver\DriverInterface::formatParameterName`: expected `string`, but provided type `array-key` is less specific.' +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "less-specific-argument" +message = 'Argument type mismatch for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteIdentifier`: expected `string`, but provided type `array-key` is less specific.' +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "less-specific-nested-argument-type" +message = "Argument type mismatch for argument #1 of `array_flip`: expected `array`, but provided type `array` is less specific." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "less-specific-nested-argument-type" +message = "Argument type mismatch for argument #2 of `implode`: expected `array|null`, but provided type `non-empty-list` is less specific." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "less-specific-nested-argument-type" +message = "Argument type mismatch for argument #3 of `str_replace`: expected `array|string`, but provided type `array|null|string` is less specific." +count = 2 + +[[issues]] +file = "src/Sql/Insert.php" +code = "missing-constant-type" +message = "Class constant `SPECIFICATION_INSERT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "missing-constant-type" +message = "Class constant `SPECIFICATION_SELECT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "missing-constant-type" +message = "Class constant `VALUES_MERGE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "missing-constant-type" +message = "Class constant `VALUES_SET` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "missing-return-type" +message = "Function `__isset` is missing a return type hint." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "missing-return-type" +message = "Function `__unset` is missing a return type hint." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\AbstractSql::resolveColumnValue`: expected `PhpDb\Sql\ExpressionInterface|PhpDb\Sql\Select|array|bool|int|null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/Sql/Insert.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\Sql\Insert::__get`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\AbstractSql::processSubSelect`: expected `PhpDb\Sql\Select`, but possibly received `PhpDb\Sql\Select|non-empty-array`.' +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\AbstractSql::resolveTable`: expected `PhpDb\Sql\Select|PhpDb\Sql\TableIdentifier|null|string`, but possibly received `PhpDb\Sql\TableIdentifier|array|string`.' +count = 2 + +[[issues]] +file = "src/Sql/Insert.php" +code = "possibly-invalid-argument" +message = '''Possible argument type mismatch for argument #1 of `array_map`: expected `(callable(array-key): string)|(callable(array-key, ('S.array_map() extends mixed)): string)|null`, but possibly received `list{PhpDb\Adapter\Platform\PlatformInterface, string('quoteIdentifier')}`.''' +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "possibly-invalid-argument" +message = "Possible argument type mismatch for argument #2 of `str_replace`: expected `array|string`, but possibly received `list{array|null|string, non-empty-string|string(''), string}`." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "possibly-invalid-argument" +message = "Possible argument type mismatch for argument #2 of `str_replace`: expected `array|string`, but possibly received `list{array|null|string, string, string}`." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "possibly-null-argument" +message = "Argument #3 of function `str_replace` is possibly `null`, but parameter type `array|string` does not accept it." +count = 2 + +[[issues]] +file = "src/Sql/Insert.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('insert')` accessed on `array>`." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('insert')` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('select')` accessed on `array>`." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('select')` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "template-constraint-violation" +message = "Argument type mismatch for template `V`." +count = 1 + +[[issues]] +file = "src/Sql/InsertIgnore.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\InsertIgnore` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Join.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Join` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Join.php" +code = "imprecise-type" +message = "Type `array` in property `$joins` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Join.php" +code = "imprecise-type" +message = "Type `array` in return type of `current` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Join.php" +code = "imprecise-type" +message = "Type `array` in return type of `getJoins` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Join.php" +code = "missing-constant-type" +message = "Class constant `JOIN_FULL_OUTER` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Join.php" +code = "missing-constant-type" +message = "Class constant `JOIN_INNER` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Join.php" +code = "missing-constant-type" +message = "Class constant `JOIN_LEFT_OUTER` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Join.php" +code = "missing-constant-type" +message = "Class constant `JOIN_LEFT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Join.php" +code = "missing-constant-type" +message = "Class constant `JOIN_OUTER` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Join.php" +code = "missing-constant-type" +message = "Class constant `JOIN_RIGHT_OUTER` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Join.php" +code = "missing-constant-type" +message = "Class constant `JOIN_RIGHT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Join.php" +code = "missing-template-parameter" +message = "Too few template arguments for `Iterator`: expected at least 2, but found 0." +count = 1 + +[[issues]] +file = "src/Sql/Join.php" +code = "mixed-argument" +message = "Invalid argument type for argument #2 of `sprintf`: expected `Stringable|null|scalar`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Sql/Join.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\Sql\Join::current`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Join.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `int` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Sql/Platform/AbstractPlatform.php" +code = "imprecise-type" +message = "Type `array` in property `$decorators` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Platform/AbstractPlatform.php" +code = "invalid-property-assignment-value" +message = 'Invalid type for property `$subject`: expected `PhpDb\Sql\PreparableSqlInterface|PhpDb\Sql\SqlInterface`, but got `PhpDb\Sql\PreparableSqlInterface|PhpDb\Sql\SqlInterface|null`.' +count = 1 + +[[issues]] +file = "src/Sql/Platform/AbstractPlatform.php" +code = "missing-constructor" +message = 'Class `PhpDb\Sql\Platform\AbstractPlatform` has typed properties without default values but no constructor to initialize them.' +count = 1 + +[[issues]] +file = "src/Sql/Platform/AbstractPlatform.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Sql/Platform/AbstractPlatform.php" +code = "mixed-method-access" +message = "Attempting to access a method on a non-object type (`mixed`)." +count = 1 + +[[issues]] +file = "src/Sql/Platform/AbstractPlatform.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\Sql\Platform\AbstractPlatform::getSqlString`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Platform/AbstractPlatform.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\Sql\Platform\AbstractPlatform::getTypeDecorator`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Platform/AbstractPlatform.php" +code = "non-existent-method" +message = 'Method `getsqlstring` does not exist on type `PhpDb\Sql\Platform\PlatformDecoratorInterface`.' +count = 1 + +[[issues]] +file = "src/Sql/Platform/AbstractPlatform.php" +code = "non-existent-method" +message = 'Method `getsqlstring` does not exist on type `PhpDb\Sql\PreparableSqlInterface`.' +count = 1 + +[[issues]] +file = "src/Sql/Platform/AbstractPlatform.php" +code = "non-existent-method" +message = 'Method `preparestatement` does not exist on type `PhpDb\Sql\Platform\PlatformDecoratorInterface`.' +count = 1 + +[[issues]] +file = "src/Sql/Platform/AbstractPlatform.php" +code = "non-existent-method" +message = 'Method `preparestatement` does not exist on type `PhpDb\Sql\SqlInterface`.' +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Platform\Platform` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "imprecise-type" +message = "Type `array` in return type of `getDecorators` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "invalid-iterator" +message = "The expression provided to `foreach` is not iterable. It resolved to type `nonnull`, which is not iterable." +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "mixed-argument" +message = "Invalid argument type for argument #2 of `is_a`: expected `class-string`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "mixed-array-assignment" +message = "Unsafe array assignment on type `mixed`." +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "mixed-method-access" +message = "Attempting to access a method on a non-object type (`mixed`)." +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\Sql\Platform\Platform::getDecorators`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\Sql\Platform\Platform::getSqlString`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\Sql\Platform\Platform::getTypeDecorator`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "non-existent-method" +message = 'Method `getsqlstring` does not exist on type `PhpDb\Sql\Platform\PlatformDecoratorInterface`.' +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "non-existent-method" +message = 'Method `getsqlstring` does not exist on type `PhpDb\Sql\PreparableSqlInterface`.' +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "non-existent-method" +message = 'Method `preparestatement` does not exist on type `PhpDb\Sql\Platform\PlatformDecoratorInterface`.' +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "non-existent-method" +message = 'Method `preparestatement` does not exist on type `PhpDb\Sql\SqlInterface`.' +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Sql/Platform/PlatformDecoratorInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Sql\Platform\PlatformDecoratorInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Expression.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Predicate\Expression` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/In.php" +code = "imprecise-type" +message = "Type `array` in parameter `$valueSet` is imprecise, equivalent to `array`." +count = 2 + +[[issues]] +file = "src/Sql/Predicate/In.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\Argument\Values::__construct`: expected `list`, but possibly received `array`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/IsNotNull.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Predicate\IsNotNull` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Literal.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Predicate\Literal` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/NotBetween.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Predicate\NotBetween` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/NotIn.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Predicate\NotIn` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/NotLike.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Predicate\NotLike` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Operator.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Predicate\Operator` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Operator.php" +code = "missing-constant-type" +message = "Class constant `OPERATOR_EQUAL_TO` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Operator.php" +code = "missing-constant-type" +message = "Class constant `OPERATOR_GREATER_THAN_OR_EQUAL_TO` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Operator.php" +code = "missing-constant-type" +message = "Class constant `OPERATOR_GREATER_THAN` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Operator.php" +code = "missing-constant-type" +message = "Class constant `OPERATOR_LESS_THAN_OR_EQUAL_TO` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Operator.php" +code = "missing-constant-type" +message = "Class constant `OPERATOR_LESS_THAN` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Operator.php" +code = "missing-constant-type" +message = "Class constant `OPERATOR_NOT_EQUAL_TO` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Operator.php" +code = "missing-constant-type" +message = "Class constant `OP_EQ` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Operator.php" +code = "missing-constant-type" +message = "Class constant `OP_GTE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Operator.php" +code = "missing-constant-type" +message = "Class constant `OP_GT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Operator.php" +code = "missing-constant-type" +message = "Class constant `OP_LTE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Operator.php" +code = "missing-constant-type" +message = "Class constant `OP_LT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Operator.php" +code = "missing-constant-type" +message = "Class constant `OP_NE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "impossible-condition" +message = "This condition (type `false`) will always evaluate to false." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "imprecise-type" +message = "Type `array` in parameter `$identifier` is imprecise, equivalent to `array`." +count = 2 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "imprecise-type" +message = "Type `array` in parameter `$maxValue` is imprecise, equivalent to `array`." +count = 2 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "imprecise-type" +message = "Type `array` in parameter `$minValue` is imprecise, equivalent to `array`." +count = 2 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "imprecise-type" +message = "Type `array` in parameter `$parameters` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "imprecise-type" +message = "Type `array` in parameter `$valueSet` is imprecise, equivalent to `array`." +count = 2 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "invalid-property-assignment-value" +message = 'Invalid type for property `$unnest`: expected `PhpDb\Sql\Predicate\Predicate`, but got `PhpDb\Sql\Predicate\Predicate|null`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "invalid-property-assignment-value" +message = 'Invalid type for property `$unnest`: expected `PhpDb\Sql\Predicate\Predicate`, but got `null`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\Predicate\Between::__construct`: expected `PhpDb\Sql\ArgumentInterface|null|string`, but possibly received `PhpDb\Sql\ArgumentInterface|array|float|int|null|string`.' +count = 2 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\Predicate\In::__construct`: expected `PhpDb\Sql\ArgumentInterface|null|string`, but possibly received `PhpDb\Sql\ArgumentInterface|float|int|string`.' +count = 2 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\Predicate\IsNull::__construct`: expected `PhpDb\Sql\ArgumentInterface|null|string`, but possibly received `PhpDb\Sql\ArgumentInterface|float|int|string`.' +count = 2 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\Predicate\Like::__construct`: expected `PhpDb\Sql\ArgumentInterface|null|string`, but possibly received `PhpDb\Sql\ArgumentInterface|float|int|null|string`.' +count = 2 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\Predicate\Operator::__construct`: expected `PhpDb\Sql\ArgumentInterface|PhpDb\Sql\ExpressionInterface|PhpDb\Sql\SqlInterface|null|string`, but possibly received `PhpDb\Sql\ArgumentInterface|float|int|null|string`.' +count = 6 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #2 of `PhpDb\Sql\Predicate\Between::__construct`: expected `PhpDb\Sql\ArgumentInterface|float|int|null|string`, but possibly received `PhpDb\Sql\ArgumentInterface|array|float|int|null|string`.' +count = 2 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #3 of `PhpDb\Sql\Predicate\Between::__construct`: expected `PhpDb\Sql\ArgumentInterface|float|int|null|string`, but possibly received `PhpDb\Sql\ArgumentInterface|array|float|int|null|string`.' +count = 2 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\Sql\Exception\RuntimeException` in `PhpDb\Sql\Predicate\Predicate::unnest`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Sql\Predicate\PredicateInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "imprecise-type" +message = "Type `array` in parameter `$predicates` is imprecise, equivalent to `array`." +count = 2 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "imprecise-type" +message = "Type `array` in property `$predicates` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "imprecise-type" +message = "Type `array` in return type of `getPredicates` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "invalid-destructuring-source" +message = "Invalid destructuring assignment: Cannot unpack type `mixed` into variables." +count = 2 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "invalid-iterator" +message = "The expression provided to `foreach` is not iterable. It resolved to type `mixed`, which is not iterable." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "less-specific-nested-argument-type" +message = "Argument type mismatch for argument #2 of `implode`: expected `array|null`, but provided type `array{}|non-empty-list` is less specific." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "less-specific-nested-return-statement" +message = '''Returned type `array{'spec': non-empty-string, 'values': mixed}` is less specific than the declared return type `array{'spec': string, 'values': array}` for function `PhpDb\Sql\Predicate\PredicateSet::getExpressionData` due to nested 'mixed'.''' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "less-specific-nested-return-statement" +message = '''Returned type `array{'spec': string, 'values': array{}|list}` is less specific than the declared return type `array{'spec': string, 'values': array}` for function `PhpDb\Sql\Predicate\PredicateSet::getExpressionData` due to nested 'mixed'.''' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "missing-constant-type" +message = "Class constant `COMBINED_BY_AND` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "missing-constant-type" +message = "Class constant `COMBINED_BY_OR` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "missing-constant-type" +message = "Class constant `OP_AND` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "missing-constant-type" +message = "Class constant `OP_OR` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Expression::__construct`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Literal::__construct`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Predicate\PredicateSet::addPredicate`: expected `PhpDb\Sql\Predicate\PredicateInterface`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `str_contains`: expected `string`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `PhpDb\Sql\Expression::__construct`: expected `PhpDb\Sql\ArgumentInterface|PhpDb\Sql\ExpressionInterface|array|null|scalar`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #3 of `PhpDb\Sql\Predicate\Operator::__construct`: expected `PhpDb\Sql\ArgumentInterface|PhpDb\Sql\ExpressionInterface|PhpDb\Sql\SqlInterface|null|scalar`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "mixed-array-access" +message = "Unsafe array access on type `mixed`." +count = 5 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 11 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "mixed-method-access" +message = "Attempting to access a method on a non-object type (`mixed`)." +count = 2 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\Sql\Predicate\PredicateSet::getExpressionData`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\Predicate\PredicateSet::addPredicate`: expected `PhpDb\Sql\Predicate\PredicateInterface`, but possibly received `PhpDb\Sql\Expression|PhpDb\Sql\Predicate\Expression|PhpDb\Sql\Predicate\In|PhpDb\Sql\Predicate\IsNull|PhpDb\Sql\Predicate\Literal|PhpDb\Sql\Predicate\Operator|PhpDb\Sql\Predicate\PredicateInterface`.' +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `int(0)` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Sql/PreparableSqlInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Sql\PreparableSqlInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "array-to-string-conversion" +message = "Potential array in left operand of string concatenation." +count = 2 + +[[issues]] +file = "src/Sql/Select.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Select` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "impossible-null-type-comparison" +message = 'Impossible condition: variable `$this->having` (type `PhpDb\Sql\Having`) will always be `null`.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "impossible-null-type-comparison" +message = 'Impossible condition: variable `$this->where` (type `PhpDb\Sql\Where`) will always be `null`.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in parameter `$columns` is imprecise, equivalent to `array`." +count = 2 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in parameter `$name` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in parameter `$order` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in parameter `$predicate` is imprecise, equivalent to `array`." +count = 2 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in parameter `$table` is imprecise, equivalent to `array`." +count = 3 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in property `$columns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in property `$combine` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in property `$group` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in property `$order` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in property `$table` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in return type of `processCombine` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in return type of `processGroup` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in return type of `processHaving` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in return type of `processLimit` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in return type of `processOffset` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in return type of `processOrder` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in return type of `processSelect` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "imprecise-type" +message = "Type `array` in return type of `processWhere` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "invalid-iterator" +message = "The expression provided to `foreach` is not iterable. It resolved to type `mixed`, which is not iterable." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "invalid-property-assignment-value" +message = 'Invalid type for property `$having`: expected `PhpDb\Sql\Having`, but got `null`.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "invalid-property-assignment-value" +message = 'Invalid type for property `$joins`: expected `PhpDb\Sql\Join`, but got `null`.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "invalid-property-assignment-value" +message = "Invalid type for property `$specifications`: expected `array>|array`, but got `array|string>`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "invalid-property-assignment-value" +message = 'Invalid type for property `$where`: expected `PhpDb\Sql\Where`, but got `null`.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "invalid-property-default-value" +message = 'Default value for property `PhpDb\Sql\Select::$specifications` is not assignable to its declared type.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Sql\Select::resolveTable`: expected `array{0: null|string, 1: string}`, but found `list{array|null|string, string}`.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "less-specific-argument" +message = 'Argument type mismatch for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteIdentifier`: expected `string`, but provided type `array-key` is less specific.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "less-specific-argument" +message = 'Argument type mismatch for argument #2 of `PhpDb\Sql\Join::join`: expected `PhpDb\Sql\Predicate\Expression|string`, but provided type `PhpDb\Sql\Predicate\PredicateInterface|string` is less specific.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "less-specific-nested-argument-type" +message = 'Argument type mismatch for argument #3 of `PhpDb\Sql\Join::join`: expected `array|string`, but provided type `array|string` is less specific.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `COLUMNS` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `COMBINE_EXCEPT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `COMBINE_INTERSECT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `COMBINE_UNION` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `COMBINE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `GROUP` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `HAVING` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `JOINS` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `JOIN_FULL_OUTER` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `JOIN_INNER` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `JOIN_LEFT_OUTER` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `JOIN_LEFT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `JOIN_OUTER` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `JOIN_RIGHT_OUTER` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `JOIN_RIGHT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `LIMIT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `OFFSET` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `ORDER_ASCENDING` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `ORDER_DESCENDING` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `ORDER` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `QUANTIFIER_ALL` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `QUANTIFIER_DISTINCT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `QUANTIFIER` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `SELECT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `SQL_STAR` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `TABLE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-constant-type" +message = "Class constant `WHERE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "missing-return-type" +message = "Function `__clone` is missing a return type hint." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteIdentifierInFragment`: expected `string`, but found `mixed`.' +count = 2 + +[[issues]] +file = "src/Sql/Select.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteIdentifier`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\AbstractSql::processSubSelect`: expected `PhpDb\Sql\Select`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\AbstractSql::resolveTable`: expected `PhpDb\Sql\Select|PhpDb\Sql\TableIdentifier|null|string`, but found `mixed`.' +count = 2 + +[[issues]] +file = "src/Sql/Select.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `str_contains`: expected `string`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `strtoupper`: expected `string`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `trim`: expected `string`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "mixed-argument" +message = "Invalid argument type for argument #2 of `explode`: expected `string`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "mixed-array-access" +message = "Unsafe array access on type `mixed`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 12 + +[[issues]] +file = "src/Sql/Select.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 2 + +[[issues]] +file = "src/Sql/Select.php" +code = "possibly-false-iterator" +message = 'Expression being iterated (type `array|false|list`) might be `false` at runtime.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Adapter\Platform\PlatformInterface::quoteValue`: expected `string`, but possibly received `int|string`.' +count = 2 + +[[issues]] +file = "src/Sql/Select.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\AbstractSql::renderTable`: expected `string`, but possibly received `array|null|string`.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "possibly-null-argument" +message = 'Argument #1 of method `PhpDb\Sql\AbstractSql::renderTable` is possibly `null`, but parameter type `string` does not accept it.' +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "possibly-null-operand" +message = "Possibly null left operand used in string concatenation (type `array|null|string`)." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array index accessed on `non-empty-list`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('modifier')` accessed on `non-empty-array`." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "redundant-comparison" +message = "Redundant `!==` comparison: left-hand side is always not identical to right-hand side." +count = 3 + +[[issues]] +file = "src/Sql/Select.php" +code = "redundant-comparison" +message = "Redundant `===` comparison: left-hand side is never identical to right-hand side." +count = 2 + +[[issues]] +file = "src/Sql/Select.php" +code = "redundant-condition" +message = "This condition (type `true`) will always evaluate to true." +count = 3 + +[[issues]] +file = "src/Sql/Select.php" +code = "redundant-logical-operation" +message = "Redundant `||` operation: left operand is always false and right operand is evaluated." +count = 2 + +[[issues]] +file = "src/Sql/Select.php" +code = "redundant-null-coalesce" +message = "Redundant null coalesce: left-hand side can never be `null` or undefined." +count = 3 + +[[issues]] +file = "src/Sql/Sql.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Sql` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Sql.php" +code = "imprecise-type" +message = "Type `array` in parameter `$table` is imprecise, equivalent to `array`." +count = 2 + +[[issues]] +file = "src/Sql/Sql.php" +code = "imprecise-type" +message = "Type `array` in property `$table` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Sql.php" +code = "imprecise-type" +message = "Type `array` in return type of `getTable` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Sql.php" +code = "overly-wide-return-type" +message = 'Declared return type `PhpDb\Adapter\AdapterInterface|null` for `getadapter` has unused branches: `null`.' +count = 1 + +[[issues]] +file = "src/Sql/Sql.php" +code = "overly-wide-return-type" +message = 'Declared return type `PhpDb\Sql\Platform\PlatformDecoratorInterface|null` for `getsqlplatform` has unused branches: `null`.' +count = 1 + +[[issues]] +file = "src/Sql/Sql.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\Delete::__construct`: expected `PhpDb\Sql\TableIdentifier|null|string`, but possibly received `PhpDb\Sql\TableIdentifier|array|null|string`.' +count = 1 + +[[issues]] +file = "src/Sql/Sql.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\Insert::__construct`: expected `PhpDb\Sql\TableIdentifier|null|string`, but possibly received `PhpDb\Sql\TableIdentifier|array|null|string`.' +count = 1 + +[[issues]] +file = "src/Sql/Sql.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\Update::__construct`: expected `PhpDb\Sql\TableIdentifier|null|string`, but possibly received `PhpDb\Sql\TableIdentifier|array|null|string`.' +count = 1 + +[[issues]] +file = "src/Sql/Sql.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #2 of `sprintf`: expected `Stringable|null|scalar`, but possibly received `PhpDb\Sql\TableIdentifier|array|string`.' +count = 4 + +[[issues]] +file = "src/Sql/Sql.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\Sql\Exception\RuntimeException` in `PhpDb\Sql\Sql::buildSqlString`.' +count = 1 + +[[issues]] +file = "src/Sql/Sql.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\Sql\Exception\RuntimeException` in `PhpDb\Sql\Sql::prepareStatementForSqlObject`.' +count = 1 + +[[issues]] +file = "src/Sql/SqlInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\Sql\SqlInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/Sql/SqlInterface.php" +code = "missing-constant-type" +message = "Class constant `TYPE_IDENTIFIER` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/SqlInterface.php" +code = "missing-constant-type" +message = "Class constant `TYPE_LITERAL` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/SqlInterface.php" +code = "missing-constant-type" +message = "Class constant `TYPE_SELECT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/SqlInterface.php" +code = "missing-constant-type" +message = "Class constant `TYPE_VALUE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/TableIdentifier.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\TableIdentifier` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Update` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "docblock-parameter-narrowing" +message = "Docblock type `string` for parameter `$flag` drops part of native type `int|string`." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "impossible-null-type-comparison" +message = 'Impossible condition: variable `$this->where` (type `PhpDb\Sql\Where`) will always be `null`.' +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "imprecise-type" +message = "Type `array` in parameter `$name` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "imprecise-type" +message = "Type `array` in parameter `$predicate` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "imprecise-type" +message = "Type `array` in parameter `$table` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "imprecise-type" +message = "Type `array` in property `$table` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "incompatible-property-type" +message = 'Property `PhpDb\Sql\Update::$specifications` has an incompatible type declaration from docblock.' +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "invalid-property-default-value" +message = 'Default value for property `PhpDb\Sql\Update::$specifications` is not assignable to its declared type.' +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Sql\Update::processSet`: expected `string`, but found `array|string`.' +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Sql\Update::processUpdate`: expected `string`, but found `array|string`.' +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\Sql\Update::processWhere`: expected `null|string`, but found `array|string`.' +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "less-specific-nested-argument-type" +message = "Argument type mismatch for argument #2 of `str_replace`: expected `array|string`, but provided type `array|null|string` is less specific." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "less-specific-nested-argument-type" +message = "Argument type mismatch for argument #3 of `str_replace`: expected `array|string`, but provided type `array|null|string` is less specific." +count = 3 + +[[issues]] +file = "src/Sql/Update.php" +code = "missing-constant-type" +message = "Class constant `SPECIFICATION_JOIN` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "missing-constant-type" +message = "Class constant `SPECIFICATION_SET` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "missing-constant-type" +message = "Class constant `SPECIFICATION_UPDATE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "missing-constant-type" +message = "Class constant `SPECIFICATION_WHERE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "missing-constant-type" +message = "Class constant `VALUES_MERGE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "missing-constant-type" +message = "Class constant `VALUES_SET` is missing a type hint." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "missing-return-type" +message = "Function `__clone` is missing a return type hint." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\AbstractSql::resolveColumnValue`: expected `PhpDb\Sql\ExpressionInterface|PhpDb\Sql\Select|array|bool|int|null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/Sql/Update.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #1 of `PhpDb\Sql\AbstractSql::resolveTable`: expected `PhpDb\Sql\Select|PhpDb\Sql\TableIdentifier|null|string`, but possibly received `PhpDb\Sql\TableIdentifier|array|string`.' +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #3 of `Laminas\Stdlib\PriorityList::insert`: expected `int`, but possibly received `int(0)|numeric-string`.' +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "possibly-null-argument" +message = "Argument #2 of function `str_replace` is possibly `null`, but parameter type `array|string` does not accept it." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "possibly-null-argument" +message = "Argument #3 of function `str_replace` is possibly `null`, but parameter type `array|string` does not accept it." +count = 3 + +[[issues]] +file = "src/Sql/Update.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('set')` accessed on `array>`." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('set')` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('update')` accessed on `array>`." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('update')` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('where')` accessed on `array>`." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('where')` accessed on `array`." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "redundant-comparison" +message = "Redundant `!==` comparison: left-hand side is always not identical to right-hand side." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "redundant-comparison" +message = "Redundant `===` comparison: left-hand side is never identical to right-hand side." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "redundant-condition" +message = "This condition (type `true`) will always evaluate to true." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "redundant-logical-operation" +message = "Redundant `||` operation: left operand is always false and right operand is evaluated." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "redundant-null-coalesce" +message = "Redundant null coalesce: left-hand side can never be `null` or undefined." +count = 1 + +[[issues]] +file = "src/Sql/Where.php" +code = "class-must-be-final" +message = 'Class `PhpDb\Sql\Where` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "clone-inside-loop" +message = "Cloning variable `$tableObject` onto itself inside a loop might not have the intended effect." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "impossible-condition" +message = "This condition (type `false`) will always evaluate to false." +count = 7 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "impossible-type-comparison" +message = "Impossible type assertion: `$deleteState['table']` of type `string` can never be `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "impossible-type-comparison" +message = "Impossible type assertion: `$insertState['table']` of type `string` can never be `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "impossible-type-comparison" +message = "Impossible type assertion: `$this->table` of type `string` can never be `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "impossible-type-comparison" +message = "Impossible type assertion: `$this->table` of type `string` can never be `object`." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "impossible-type-comparison" +message = "Impossible type assertion: `$updateState['table']` of type `string` can never be `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "imprecise-type" +message = "Type `array` in parameter `$arguments` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "imprecise-type" +message = "Type `array` in parameter `$joins` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "imprecise-type" +message = "Type `array` in parameter `$where` is imprecise, equivalent to `array`." +count = 3 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "imprecise-type" +message = "Type `array` in property `$columns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "imprecise-type" +message = "Type `array` in property `$table` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "imprecise-type" +message = "Type `array` in return type of `getColumns` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "imprecise-type" +message = "Type `array` in return type of `getTable` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "invalid-array-access" +message = 'Cannot access array index on object `PhpDb\Sql\TableIdentifier` that does not implement `ArrayAccess`.' +count = 2 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "invalid-clone" +message = "Invalid `clone` operation on non-cloneable type `never`." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "invalid-property-assignment-value" +message = "Invalid type for property `$lastInsertValue`: expected `int`, but got `false|int|null|string`." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\TableGateway\AbstractTableGateway::executeSelect`: expected `PhpDb\ResultSet\ResultSetInterface`, but found `PhpDb\ResultSet\ResultSetInterface|null`.' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\TableGateway\AbstractTableGateway::getFeatureSet`: expected `PhpDb\TableGateway\Feature\FeatureSet`, but found `PhpDb\TableGateway\Feature\FeatureSet|null`.' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\TableGateway\AbstractTableGateway::getResultSetPrototype`: expected `PhpDb\ResultSet\ResultSetInterface`, but found `PhpDb\ResultSet\ResultSetInterface|null`.' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\TableGateway\AbstractTableGateway::getSql`: expected `PhpDb\Sql\Sql`, but found `PhpDb\Sql\Sql|null`.' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\TableGateway\AbstractTableGateway` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\ResultSet\ResultSetInterface::initialize`: expected `iterable`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Delete::from`: expected `PhpDb\Sql\TableIdentifier|array|string`, but found `mixed`.' +count = 2 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Insert::into`: expected `PhpDb\Sql\TableIdentifier|array|string`, but found `mixed`.' +count = 2 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Sql\Update::table`: expected `PhpDb\Sql\TableIdentifier|array|string`, but found `mixed`.' +count = 2 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\TableGateway\AbstractTableGateway::executeDelete`: expected `PhpDb\Sql\Delete`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\TableGateway\AbstractTableGateway::executeInsert`: expected `PhpDb\Sql\Insert`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\TableGateway\AbstractTableGateway::executeUpdate`: expected `PhpDb\Sql\Update`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\TableGateway\AbstractTableGateway::selectWith`: expected `PhpDb\Sql\Select`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "mixed-array-access" +message = "Unsafe array access on type `mixed`." +count = 6 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 21 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "mixed-assignment" +message = "Assigning `nonnull` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "mixed-clone" +message = "Cannot statically verify `clone` on a `mixed` type." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "mixed-method-access" +message = "Attempting to access a method on a non-object type (`mixed`)." +count = 13 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\TableGateway\AbstractTableGateway::executeDelete`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\TableGateway\AbstractTableGateway::executeInsert`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\TableGateway\AbstractTableGateway::executeUpdate`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "no-value" +message = "Argument #1 passed to function `array_values` has type `never`, meaning it cannot produce a value." +count = 3 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "no-value" +message = "Argument #1 passed to function `count` has type `never`, meaning it cannot produce a value." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "no-value" +message = "Argument #1 passed to function `reset` has type `never`, meaning it cannot produce a value." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "nullable-return-statement" +message = 'Function `PhpDb\TableGateway\AbstractTableGateway::executeSelect` is declared to return `PhpDb\ResultSet\ResultSetInterface` but possibly returns a nullable value (inferred as `PhpDb\ResultSet\ResultSetInterface|null`).' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "nullable-return-statement" +message = 'Function `PhpDb\TableGateway\AbstractTableGateway::getFeatureSet` is declared to return `PhpDb\TableGateway\Feature\FeatureSet` but possibly returns a nullable value (inferred as `PhpDb\TableGateway\Feature\FeatureSet|null`).' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "nullable-return-statement" +message = 'Function `PhpDb\TableGateway\AbstractTableGateway::getResultSetPrototype` is declared to return `PhpDb\ResultSet\ResultSetInterface` but possibly returns a nullable value (inferred as `PhpDb\ResultSet\ResultSetInterface|null`).' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "nullable-return-statement" +message = 'Function `PhpDb\TableGateway\AbstractTableGateway::getSql` is declared to return `PhpDb\Sql\Sql` but possibly returns a nullable value (inferred as `PhpDb\Sql\Sql|null`).' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "overly-wide-return-type" +message = "Declared return type `false|int|null|string` for `getlastinsertvalue` has unused branches: `false`, `string`, `null`." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 25 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "possibly-invalid-clone" +message = 'Expression of type `PhpDb\ResultSet\ResultSetInterface|null` might not be a cloneable object.' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "possibly-invalid-clone" +message = 'Expression of type `PhpDb\Sql\Sql|null` might not be a cloneable object.' +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "possibly-undefined-string-array-index" +message = "Possibly undefined array key `string('table')` accessed on `array`." +count = 2 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "redundant-comparison" +message = "Redundant `===` comparison: left-hand side is never identical to right-hand side." +count = 2 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "redundant-logical-operation" +message = "Redundant `&&` operation: left operand is always falsy and right operand is not evaluated." +count = 2 + +[[issues]] +file = "src/TableGateway/Exception/ExceptionInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\TableGateway\Exception\ExceptionInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/TableGateway/Exception/InvalidArgumentException.php" +code = "class-must-be-final" +message = 'Class `PhpDb\TableGateway\Exception\InvalidArgumentException` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Exception/RuntimeException.php" +code = "class-must-be-final" +message = 'Class `PhpDb\TableGateway\Exception\RuntimeException` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/AbstractFeature.php" +code = "imprecise-type" +message = "Type `array` in property `$sharedData` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/AbstractFeature.php" +code = "missing-api-or-internal" +message = 'Abstract class `PhpDb\TableGateway\Feature\AbstractFeature` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/AbstractFeature.php" +code = "uninitialized-property" +message = 'Property `$tableGateway` is not initialized in the constructor of class `PhpDb\TableGateway\Feature\EventFeature`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/AbstractFeature.php" +code = "uninitialized-property" +message = 'Property `$tableGateway` is not initialized in the constructor of class `PhpDb\TableGateway\Feature\MasterSlaveFeature`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/AbstractFeature.php" +code = "uninitialized-property" +message = 'Property `$tableGateway` is not initialized in the constructor of class `PhpDb\TableGateway\Feature\MetadataFeature`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/AbstractFeature.php" +code = "uninitialized-property" +message = 'Property `$tableGateway` is not initialized in the constructor of class `PhpDb\TableGateway\Feature\RowGatewayFeature`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/AbstractFeature.php" +code = "uninitialized-property" +message = 'Property `$tableGateway` is not initialized in the constructor of class `PhpDb\TableGateway\Feature\SequenceFeature`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeature.php" +code = "class-must-be-final" +message = 'Class `PhpDb\TableGateway\Feature\EventFeature` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeature.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\TableGateway\Feature\EventFeature::getEvent`: expected `PhpDb\TableGateway\Feature\EventFeature\TableGatewayEvent`, but found `PhpDb\TableGateway\Feature\EventFeature\TableGatewayEvent|null`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeature.php" +code = "nullable-return-statement" +message = 'Function `PhpDb\TableGateway\Feature\EventFeature::getEvent` is declared to return `PhpDb\TableGateway\Feature\EventFeature\TableGatewayEvent` but possibly returns a nullable value (inferred as `PhpDb\TableGateway\Feature\EventFeature\TableGatewayEvent|null`).' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeature.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 19 + +[[issues]] +file = "src/TableGateway/Feature/EventFeature.php" +code = "possibly-null-argument" +message = 'Argument #1 of method `Laminas\EventManager\EventManagerInterface::triggerEvent` is possibly `null`, but parameter type `Laminas\EventManager\EventInterface|array|object>` does not accept it.' +count = 10 + +[[issues]] +file = "src/TableGateway/Feature/EventFeature/TableGatewayEvent.php" +code = "class-must-be-final" +message = 'Class `PhpDb\TableGateway\Feature\EventFeature\TableGatewayEvent` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeature/TableGatewayEvent.php" +code = "imprecise-type" +message = "Type `array` in property `$params` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeature/TableGatewayEvent.php" +code = "missing-template-parameter" +message = 'Too few template arguments for `Laminas\EventManager\EventInterface`: expected at least 2, but found 0.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeature/TableGatewayEvent.php" +code = "possibly-invalid-array-access" +message = "Cannot perform array access on value of type `object`." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeature/TableGatewayEvent.php" +code = "property-type-coercion" +message = 'A value of a less specific type `null|object|string` is being assigned to property `$target` (PhpDb\TableGateway\AbstractTableGateway|null).' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeature/TableGatewayEvent.php" +code = "unused-template-parameter" +message = 'Template parameter `NewTParams` is never used in method `PhpDb\TableGateway\Feature\EventFeature\TableGatewayEvent::setParams`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeature/TableGatewayEvent.php" +code = "unused-template-parameter" +message = 'Template parameter `NewTTarget` is never used in method `PhpDb\TableGateway\Feature\EventFeature\TableGatewayEvent::setTarget`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\TableGateway\Feature\EventFeatureEventsInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" +code = "missing-constant-type" +message = "Class constant `EVENT_POST_DELETE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" +code = "missing-constant-type" +message = "Class constant `EVENT_POST_INITIALIZE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" +code = "missing-constant-type" +message = "Class constant `EVENT_POST_INSERT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" +code = "missing-constant-type" +message = "Class constant `EVENT_POST_SELECT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" +code = "missing-constant-type" +message = "Class constant `EVENT_POST_UPDATE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" +code = "missing-constant-type" +message = "Class constant `EVENT_PRE_DELETE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" +code = "missing-constant-type" +message = "Class constant `EVENT_PRE_INITIALIZE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" +code = "missing-constant-type" +message = "Class constant `EVENT_PRE_INSERT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" +code = "missing-constant-type" +message = "Class constant `EVENT_PRE_SELECT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeatureEventsInterface.php" +code = "missing-constant-type" +message = "Class constant `EVENT_PRE_UPDATE` is missing a type hint." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/FeatureInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\TableGateway\Feature\FeatureInterface` is missing an `@api` or `@internal` annotation.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/FeatureSet.php" +code = "class-must-be-final" +message = 'Class `PhpDb\TableGateway\Feature\FeatureSet` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/FeatureSet.php" +code = "imprecise-type" +message = "Type `array` in parameter `$args` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/FeatureSet.php" +code = "imprecise-type" +message = "Type `array` in parameter `$arguments` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/FeatureSet.php" +code = "imprecise-type" +message = "Type `array` in parameter `$features` is imprecise, equivalent to `array`." +count = 2 + +[[issues]] +file = "src/TableGateway/Feature/FeatureSet.php" +code = "imprecise-type" +message = "Type `array` in property `$magicSpecifications` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/FeatureSet.php" +code = "missing-constant-type" +message = "Class constant `APPLY_HALT` is missing a type hint." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/FeatureSet.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\TableGateway\Feature\FeatureSet::addFeature`: expected `PhpDb\TableGateway\Feature\FeatureInterface`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/FeatureSet.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/TableGateway/Feature/FeatureSet.php" +code = "string-member-selector" +message = "This member selector uses a non-literal string type (`string`); its specific value cannot be statically determined." +count = 2 + +[[issues]] +file = "src/TableGateway/Feature/FeatureSet.php" +code = "unused-parameter" +message = "Parameter `$property` is never used." +count = 4 + +[[issues]] +file = "src/TableGateway/Feature/FeatureSet.php" +code = "unused-parameter" +message = "Parameter `$value` is never used." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/GlobalAdapterFeature.php" +code = "class-must-be-final" +message = 'Class `PhpDb\TableGateway\Feature\GlobalAdapterFeature` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/GlobalAdapterFeature.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `PhpDb\TableGateway\Feature\GlobalAdapterFeature::getStaticAdapter`: expected `PhpDb\Adapter\AdapterInterface`, but found `PhpDb\Adapter\AdapterInterface|null`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/GlobalAdapterFeature.php" +code = "missing-constructor" +message = 'Class `PhpDb\TableGateway\Feature\GlobalAdapterFeature` has typed properties without default values but no constructor to initialize them.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/GlobalAdapterFeature.php" +code = "nullable-return-statement" +message = 'Function `PhpDb\TableGateway\Feature\GlobalAdapterFeature::getStaticAdapter` is declared to return `PhpDb\Adapter\AdapterInterface` but possibly returns a nullable value (inferred as `PhpDb\Adapter\AdapterInterface|null`).' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/GlobalAdapterFeature.php" +code = "possibly-undefined-string-array-index" +message = '''Possibly undefined array key `class-string('PhpDb\TableGateway\Feature\GlobalAdapterFeature')` accessed on `array`.''' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MasterSlaveFeature.php" +code = "class-must-be-final" +message = 'Class `PhpDb\TableGateway\Feature\MasterSlaveFeature` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MasterSlaveFeature.php" +code = "invalid-property-assignment-value" +message = 'Invalid type for property `$masterSql`: expected `PhpDb\Sql\Sql`, but got `PhpDb\Sql\Sql|null`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MasterSlaveFeature.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `PhpDb\Sql\Sql::__construct`: expected `PhpDb\Sql\TableIdentifier|array|null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MasterSlaveFeature.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MasterSlaveFeature.php" +code = "uninitialized-property" +message = 'Property `$masterSql` is not initialized in the constructor of class `PhpDb\TableGateway\Feature\MasterSlaveFeature`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MetadataFeature.php" +code = "class-must-be-final" +message = 'Class `PhpDb\TableGateway\Feature\MetadataFeature` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MetadataFeature.php" +code = "impossible-condition" +message = "Redundant ternary operator: condition is always falsy." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MetadataFeature.php" +code = "impossible-condition" +message = "This condition (type `false`) will always evaluate to false." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MetadataFeature.php" +code = "impossible-type-comparison" +message = "Impossible type assertion: `$t->table` of type `string` can never be `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MetadataFeature.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\MetadataInterface::getColumnNames`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MetadataFeature.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\MetadataInterface::getConstraints`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MetadataFeature.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\Metadata\MetadataInterface::getTable`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MetadataFeature.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `PhpDb\Metadata\MetadataInterface::getColumnNames`: expected `null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MetadataFeature.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `PhpDb\Metadata\MetadataInterface::getConstraints`: expected `null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MetadataFeature.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `PhpDb\Metadata\MetadataInterface::getTable`: expected `null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MetadataFeature.php" +code = "mixed-array-assignment" +message = "Unsafe array assignment on type `mixed`." +count = 2 + +[[issues]] +file = "src/TableGateway/Feature/MetadataFeature.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/TableGateway/Feature/MetadataFeature.php" +code = "no-value" +message = "Argument #1 passed to function `current` has type `never`, meaning it cannot produce a value." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/MetadataFeature.php" +code = "possibly-undefined-int-array-index" +message = "Possibly undefined array key `int(0)` accessed on `non-empty-array`." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/RowGatewayFeature.php" +code = "class-must-be-final" +message = 'Class `PhpDb\TableGateway\Feature\RowGatewayFeature` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/RowGatewayFeature.php" +code = "deprecated-method" +message = 'Call to deprecated method: `PhpDb\ResultSet\ResultSet::setArrayObjectPrototype`.' +count = 3 + +[[issues]] +file = "src/TableGateway/Feature/RowGatewayFeature.php" +code = "imprecise-type" +message = "Type `array` in property `$constructorArguments` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/RowGatewayFeature.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpDb\RowGateway\RowGateway::__construct`: expected `array|null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/RowGatewayFeature.php" +code = "mixed-array-access" +message = "Unsafe array access on type `nonnull`." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/RowGatewayFeature.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/TableGateway/Feature/RowGatewayFeature.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/SequenceFeature.php" +code = "class-must-be-final" +message = 'Class `PhpDb\TableGateway\Feature\SequenceFeature` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/SequenceFeature.php" +code = "invalid-array-access" +message = 'Cannot access array index on object `PhpDb\Sql\TableIdentifier` that does not implement `ArrayAccess`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/SequenceFeature.php" +code = "mixed-array-access" +message = "Unsafe array access on type `mixed`." +count = 2 + +[[issues]] +file = "src/TableGateway/Feature/SequenceFeature.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/TableGateway/Feature/SequenceFeature.php" +code = "mixed-property-type-coercion" +message = "A value with a less specific type `mixed` is being assigned to property `$sequenceValue` (int|null)." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/SequenceFeature.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\TableGateway\Feature\SequenceFeature::lastSequenceId`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/SequenceFeature.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `PhpDb\TableGateway\Feature\SequenceFeature::nextSequenceId`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/SequenceFeature.php" +code = "possible-method-access-on-null" +message = "Attempting to call a method on `null`." +count = 2 + +[[issues]] +file = "src/TableGateway/Feature/SequenceFeature.php" +code = "possibly-invalid-argument" +message = 'Possible argument type mismatch for argument #2 of `array_search`: expected `array`, but possibly received `PhpDb\Sql\TableIdentifier|array|string`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/SequenceFeature.php" +code = "unhandled-thrown-type" +message = 'Potentially unhandled exception `PhpDb\Exception\RuntimeException` in `PhpDb\TableGateway\Feature\SequenceFeature::preInsert`.' +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/SequenceFeature.php" +code = "unused-parameter" +message = "Parameter `$result` is never used." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/SequenceFeature.php" +code = "unused-parameter" +message = "Parameter `$statement` is never used." +count = 1 + +[[issues]] +file = "src/TableGateway/TableGateway.php" +code = "class-must-be-final" +message = 'Class `PhpDb\TableGateway\TableGateway` should be declared `final`.' +count = 1 + +[[issues]] +file = "src/TableGateway/TableGateway.php" +code = "imprecise-type" +message = "Type `array` in parameter `$features` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/TableGateway.php" +code = "imprecise-type" +message = "Type `array` in parameter `$table` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/TableGateway.php" +code = "invalid-property-assignment-value" +message = 'Invalid type for property `$table`: expected `string`, but got `PhpDb\Sql\TableIdentifier|array|string`.' +count = 1 + +[[issues]] +file = "src/TableGateway/TableGatewayInterface.php" +code = "imprecise-type" +message = "Type `array` in parameter `$where` is imprecise, equivalent to `array`." +count = 3 + +[[issues]] +file = "src/TableGateway/TableGatewayInterface.php" +code = "imprecise-type" +message = "Type `array` in return type of `getTable` is imprecise, equivalent to `array`." +count = 1 + +[[issues]] +file = "src/TableGateway/TableGatewayInterface.php" +code = "missing-api-or-internal" +message = 'Interface `PhpDb\TableGateway\TableGatewayInterface` is missing an `@api` or `@internal` annotation.' +count = 1 diff --git a/composer.json b/composer.json index 1a2322c13..eaa391683 100644 --- a/composer.json +++ b/composer.json @@ -18,9 +18,6 @@ "sort-packages": true, "platform": { "php": "8.2.99" - }, - "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true } }, "extra": { @@ -34,12 +31,10 @@ "laminas/laminas-stdlib": "^3.20.0" }, "require-dev": { - "laminas/laminas-coding-standard": "^3.0.1", "laminas/laminas-eventmanager": "^3.14.0", "laminas/laminas-hydrator": "^4.6.0", + "php-db/phpdb-qa-tools": "0.1.x-dev", "phpbench/phpbench": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^11.5.42", "rector/rector": "^2.0" }, @@ -63,18 +58,21 @@ "scripts": { "check": [ "@cs-check", - "@sa", - "@test", - "@test-integration" + "@static-analysis", + "@test" ], - "cs-check": "phpcs", - "cs-fix": "phpcbf", + "cs-check": [ + "mago format --check", + "mago lint" + ], + "cs-fix": [ + "mago format", + "mago lint --fix" + ], + "static-analysis": "mago analyze", "test": "phpunit --colors=always --testsuite \"unit test\"", "test-coverage": "phpunit --colors=always --coverage-clover clover.xml", "test-integration": "phpunit --colors=always --testsuite \"integration test\"", - "sa": "vendor/bin/phpstan analyse --memory-limit=256M", - "sa-gen-baseline": "vendor/bin/phpstan analyse --memory-limit=256M --generate-baseline", - "sa-verbose": "vendor/bin/phpstan analyse --memory-limit=256M -vv", "upload-coverage": "coveralls -v" }, "conflict": { diff --git a/composer.lock b/composer.lock index 02a39ab0c..a4a79fbb7 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": "682e90f2c192330895122fe20f5988d4", + "content-hash": "abc7c3577be8528422816192cc5068fa", "packages": [ { "name": "brick/varexporter", @@ -312,102 +312,6 @@ } ], "packages-dev": [ - { - "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v1.2.0", - "source": { - "type": "git", - "url": "https://github.com/PHPCSStandards/composer-installer.git", - "reference": "845eb62303d2ca9b289ef216356568ccc075ffd1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/845eb62303d2ca9b289ef216356568ccc075ffd1", - "reference": "845eb62303d2ca9b289ef216356568ccc075ffd1", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^2.2", - "php": ">=5.4", - "squizlabs/php_codesniffer": "^3.1.0 || ^4.0" - }, - "require-dev": { - "composer/composer": "^2.2", - "ext-json": "*", - "ext-zip": "*", - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpcompatibility/php-compatibility": "^9.0 || ^10.0.0@dev", - "yoast/phpunit-polyfills": "^1.0" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" - }, - "autoload": { - "psr-4": { - "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Franck Nijhof", - "email": "opensource@frenck.dev", - "homepage": "https://frenck.dev", - "role": "Open source developer" - }, - { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "keywords": [ - "PHPCodeSniffer", - "PHP_CodeSniffer", - "code quality", - "codesniffer", - "composer", - "installer", - "phpcbf", - "phpcs", - "plugin", - "qa", - "quality", - "standard", - "standards", - "style guide", - "stylecheck", - "tests" - ], - "support": { - "issues": "https://github.com/PHPCSStandards/composer-installer/issues", - "security": "https://github.com/PHPCSStandards/composer-installer/security/policy", - "source": "https://github.com/PHPCSStandards/composer-installer" - }, - "funding": [ - { - "url": "https://github.com/PHPCSStandards", - "type": "github" - }, - { - "url": "https://github.com/jrfnl", - "type": "github" - }, - { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" - }, - { - "url": "https://thanks.dev/u/gh/phpcsstandards", - "type": "thanks_dev" - } - ], - "time": "2025-11-11T04:32:07+00:00" - }, { "name": "doctrine/annotations", "version": "2.0.2", @@ -562,59 +466,6 @@ ], "time": "2024-02-05T11:56:58+00:00" }, - { - "name": "laminas/laminas-coding-standard", - "version": "3.1.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-coding-standard.git", - "reference": "d4412caba9ed16c93cdcf301759f5ee71f9d9aea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-coding-standard/zipball/d4412caba9ed16c93cdcf301759f5ee71f9d9aea", - "reference": "d4412caba9ed16c93cdcf301759f5ee71f9d9aea", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0", - "php": "^7.4 || ^8.0", - "slevomat/coding-standard": "^8.15.0", - "squizlabs/php_codesniffer": "^3.10", - "webimpress/coding-standard": "^1.3" - }, - "type": "phpcodesniffer-standard", - "autoload": { - "psr-4": { - "LaminasCodingStandard\\": "src/LaminasCodingStandard/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Laminas Coding Standard", - "homepage": "https://laminas.dev", - "keywords": [ - "Coding Standard", - "laminas" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-coding-standard/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-coding-standard/issues", - "rss": "https://github.com/laminas/laminas-coding-standard/releases.atom", - "source": "https://github.com/laminas/laminas-coding-standard" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2025-05-13T08:37:04+00:00" - }, { "name": "laminas/laminas-eventmanager", "version": "3.15.0", @@ -941,6 +792,50 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "php-db/phpdb-qa-tools", + "version": "0.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/php-db/phpdb-qa-tools.git", + "reference": "f2323423deac77dd719f3f2ca7a9b2a7e21c93f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-db/phpdb-qa-tools/zipball/f2323423deac77dd719f3f2ca7a9b2a7e21c93f6", + "reference": "f2323423deac77dd719f3f2ca7a9b2a7e21c93f6", + "shasum": "" + }, + "require": { + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" + }, + "suggest": { + "phpunit/phpunit": "To run the shared test configuration (^11.5 || ^12.0)" + }, + "default-branch": true, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Shared QA toolchain for php-db components: Mago formatting, linting and static analysis, plus the PHPUnit baseline and CI workflow.", + "homepage": "https://github.com/php-db/phpdb-qa-tools", + "keywords": [ + "coding-standards", + "formatter", + "linter", + "mago", + "phpdb", + "qa", + "static-analysis" + ], + "support": { + "forum": "https://github.com/php-db/phpdb-qa-tools/discussions", + "issues": "https://github.com/php-db/phpdb-qa-tools/issues", + "source": "https://github.com/php-db/phpdb-qa-tools" + }, + "time": "2026-08-10T01:10:13+00:00" + }, { "name": "phpbench/container", "version": "2.2.3", @@ -1090,60 +985,13 @@ ], "time": "2025-11-06T19:07:31+00:00" }, - { - "name": "phpstan/phpdoc-parser", - "version": "2.3.1", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "16dbf9937da8d4528ceb2145c9c7c0bd29e26374" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/16dbf9937da8d4528ceb2145c9c7c0bd29e26374", - "reference": "16dbf9937da8d4528ceb2145c9c7c0bd29e26374", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "doctrine/annotations": "^2.0", - "nikic/php-parser": "^5.3.0", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^9.6", - "symfony/process": "^5.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "PHPStan\\PhpDocParser\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPDoc parser with support for nullable, intersection and generic types", - "support": { - "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.1" - }, - "time": "2026-01-12T11:33:04+00:00" - }, { "name": "phpstan/phpstan", - "version": "2.1.33", + "version": "2.2.7", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f", - "reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/692db47b9dddb0487934e5236e77d48594aef921", + "reference": "692db47b9dddb0487934e5236e77d48594aef921", "shasum": "" }, "require": { @@ -1166,6 +1014,17 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ "dev", @@ -1188,60 +1047,7 @@ "type": "github" } ], - "time": "2025-12-05T10:24:31+00:00" - }, - { - "name": "phpstan/phpstan-phpunit", - "version": "2.0.11", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "5e30669bef866eff70db8b58d72a5c185aa82414" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/5e30669bef866eff70db8b58d72a5c185aa82414", - "reference": "5e30669bef866eff70db8b58d72a5c185aa82414", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.1.32" - }, - "conflict": { - "phpunit/phpunit": "<7.0" - }, - "require-dev": { - "nikic/php-parser": "^5", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpstan-deprecation-rules": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^9.6" - }, - "type": "phpstan-extension", - "extra": { - "phpstan": { - "includes": [ - "extension.neon", - "rules.neon" - ] - } - }, - "autoload": { - "psr-4": { - "PHPStan\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPUnit extensions and rules for PHPStan", - "support": { - "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.11" - }, - "time": "2025-12-19T09:05:35+00:00" + "time": "2026-07-29T17:39:32+00:00" }, { "name": "phpunit/php-code-coverage", @@ -2896,150 +2702,6 @@ ], "time": "2024-07-11T14:55:45+00:00" }, - { - "name": "slevomat/coding-standard", - "version": "8.22.1", - "source": { - "type": "git", - "url": "https://github.com/slevomat/coding-standard.git", - "reference": "1dd80bf3b93692bedb21a6623c496887fad05fec" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/1dd80bf3b93692bedb21a6623c496887fad05fec", - "reference": "1dd80bf3b93692bedb21a6623c496887fad05fec", - "shasum": "" - }, - "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.1.2", - "php": "^7.4 || ^8.0", - "phpstan/phpdoc-parser": "^2.3.0", - "squizlabs/php_codesniffer": "^3.13.4" - }, - "require-dev": { - "phing/phing": "3.0.1|3.1.0", - "php-parallel-lint/php-parallel-lint": "1.4.0", - "phpstan/phpstan": "2.1.24", - "phpstan/phpstan-deprecation-rules": "2.0.3", - "phpstan/phpstan-phpunit": "2.0.7", - "phpstan/phpstan-strict-rules": "2.0.6", - "phpunit/phpunit": "9.6.8|10.5.48|11.4.4|11.5.36|12.3.10" - }, - "type": "phpcodesniffer-standard", - "extra": { - "branch-alias": { - "dev-master": "8.x-dev" - } - }, - "autoload": { - "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", - "keywords": [ - "dev", - "phpcs" - ], - "support": { - "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.22.1" - }, - "funding": [ - { - "url": "https://github.com/kukulich", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", - "type": "tidelift" - } - ], - "time": "2025-09-13T08:53:30+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.13.5", - "source": { - "type": "git", - "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", - "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" - }, - "bin": [ - "bin/phpcbf", - "bin/phpcs" - ], - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "Former lead" - }, - { - "name": "Juliette Reinders Folmer", - "role": "Current lead" - }, - { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards", - "static analysis" - ], - "support": { - "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", - "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", - "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" - }, - "funding": [ - { - "url": "https://github.com/PHPCSStandards", - "type": "github" - }, - { - "url": "https://github.com/jrfnl", - "type": "github" - }, - { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" - }, - { - "url": "https://thanks.dev/u/gh/phpcsstandards", - "type": "thanks_dev" - } - ], - "time": "2025-11-04T16:30:35+00:00" - }, { "name": "staabm/side-effects-detector", "version": "1.0.5", @@ -4094,61 +3756,6 @@ ], "time": "2025-11-17T20:03:58+00:00" }, - { - "name": "webimpress/coding-standard", - "version": "1.4.0", - "source": { - "type": "git", - "url": "https://github.com/webimpress/coding-standard.git", - "reference": "6f6a1a90bd9e18fc8bee0660dd1d1ce68cf9fc53" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webimpress/coding-standard/zipball/6f6a1a90bd9e18fc8bee0660dd1d1ce68cf9fc53", - "reference": "6f6a1a90bd9e18fc8bee0660dd1d1ce68cf9fc53", - "shasum": "" - }, - "require": { - "php": "^7.3 || ^8.0", - "squizlabs/php_codesniffer": "^3.10.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.6.15" - }, - "type": "phpcodesniffer-standard", - "extra": { - "dev-master": "1.2.x-dev", - "dev-develop": "1.3.x-dev" - }, - "autoload": { - "psr-4": { - "WebimpressCodingStandard\\": "src/WebimpressCodingStandard/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "description": "Webimpress Coding Standard", - "keywords": [ - "Coding Standard", - "PSR-2", - "phpcs", - "psr-12", - "webimpress" - ], - "support": { - "issues": "https://github.com/webimpress/coding-standard/issues", - "source": "https://github.com/webimpress/coding-standard/tree/1.4.0" - }, - "funding": [ - { - "url": "https://github.com/michalbundyra", - "type": "github" - } - ], - "time": "2024-10-16T06:55:17+00:00" - }, { "name": "webmozart/assert", "version": "2.1.2", @@ -4263,7 +3870,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": { + "php-db/phpdb-qa-tools": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/lint-baseline.toml b/lint-baseline.toml new file mode 100644 index 000000000..d1758f9ec --- /dev/null +++ b/lint-baseline.toml @@ -0,0 +1,10177 @@ +variant = "loose" + +[[issues]] +file = "src/Adapter/Adapter.php" +code = "prefer-first-class-callable" +message = "Use first-class callable syntax `...` instead of a closure." +count = 2 + +[[issues]] +file = "src/Adapter/AdapterAwareInterface.php" +code = "strict-types" +message = "Missing `declare(strict_types=1);` statement at the beginning of the file." +count = 1 + +[[issues]] +file = "src/Adapter/AdapterAwareTrait.php" +code = "strict-types" +message = "Missing `declare(strict_types=1);` statement at the beginning of the file." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/ConnectionInterface.php" +code = "too-many-methods" +message = "Interface has too many methods." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "literal-named-argument" +message = "Literal argument `':'` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/AbstractPdo.php" +code = "tagged-todo" +message = "TODO should be tagged with (@username) or (#issue)." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "halstead" +message = "Method has a high halstead difficulty" +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "literal-named-argument" +message = "Literal argument `true` should be passed as a named argument for clarity." +count = 2 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Result.php" +code = "too-many-methods" +message = "Class has too many methods." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "kan-defect" +message = "Class has a high kan defect score." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "literal-named-argument" +message = "Literal argument `':'` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 2 + +[[issues]] +file = "src/Adapter/Driver/Pdo/Statement.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 1 + +[[issues]] +file = "src/Adapter/Exception/InvalidArgumentException.php" +code = "strict-types" +message = "Missing `declare(strict_types=1);` statement at the beginning of the file." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "cyclomatic-complexity" +message = "Class has high complexity." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "halstead" +message = "Method has a high halstead difficulty" +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "kan-defect" +message = "Class has a high kan defect score." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "literal-named-argument" +message = "Literal argument `':'` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "literal-named-argument" +message = "Literal argument `1` should be passed as a named argument for clarity." +count = 2 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 2 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "no-else-clause" +message = "Avoid `elseif` clauses." +count = 3 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 11 + +[[issues]] +file = "src/Adapter/ParameterContainer.php" +code = "too-many-methods" +message = "Class has too many methods." +count = 1 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "literal-named-argument" +message = '''Literal argument `"\x00\n\r\\'\"\x1a"` should be passed as a named argument for clarity.''' +count = 2 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "literal-named-argument" +message = '''Literal argument `'\\"'` should be passed as a named argument for clarity.''' +count = 1 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 1 + +[[issues]] +file = "src/Adapter/Platform/AbstractPlatform.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 2 + +[[issues]] +file = "src/Adapter/Platform/PlatformInterface.php" +code = "too-many-methods" +message = "Interface has too many methods." +count = 1 + +[[issues]] +file = "src/Adapter/Platform/Sql92.php" +code = "literal-named-argument" +message = '''Literal argument `"\x00\n\r\\'\"\x1a"` should be passed as a named argument for clarity.''' +count = 1 + +[[issues]] +file = "src/Adapter/Platform/Sql92.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 1 + +[[issues]] +file = "src/Adapter/Profiler/Profiler.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 1 + +[[issues]] +file = "src/Adapter/Profiler/Profiler.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 1 + +[[issues]] +file = "src/Adapter/SchemaAwareInterface.php" +code = "tagged-todo" +message = "TODO should be tagged with (@username) or (#issue)." +count = 1 + +[[issues]] +file = "src/Adapter/StatementContainer.php" +code = "strict-types" +message = "Missing `declare(strict_types=1);` statement at the beginning of the file." +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "no-empty" +message = "Use of the `empty` construct." +count = 1 + +[[issues]] +file = "src/Container/AbstractAdapterInterfaceFactory.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 1 + +[[issues]] +file = "src/Exception/InvalidArgumentException.php" +code = "no-fully-qualified-global-class-like" +message = "Fully-qualified class-like reference detected." +count = 1 + +[[issues]] +file = "src/Exception/RuntimeException.php" +code = "no-fully-qualified-global-class-like" +message = "Fully-qualified class-like reference detected." +count = 1 + +[[issues]] +file = "src/Exception/UnexpectedValueException.php" +code = "no-fully-qualified-global-class-like" +message = "Fully-qualified class-like reference detected." +count = 1 + +[[issues]] +file = "src/Metadata/MetadataInterface.php" +code = "no-boolean-flag-parameter" +message = "Avoid boolean flag parameters." +count = 2 + +[[issues]] +file = "src/Metadata/MetadataInterface.php" +code = "too-many-methods" +message = "Interface has too many methods." +count = 1 + +[[issues]] +file = "src/Metadata/Object/ColumnObject.php" +code = "too-many-properties" +message = "Class has too many properties." +count = 1 + +[[issues]] +file = "src/Metadata/Object/ConstraintObject.php" +code = "too-many-properties" +message = "Class has too many properties." +count = 1 + +[[issues]] +file = "src/Metadata/Object/TriggerObject.php" +code = "too-many-properties" +message = "Class has too many properties." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "cyclomatic-complexity" +message = "Class has high complexity." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "kan-defect" +message = "Class has a high kan defect score." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "no-boolean-flag-parameter" +message = "Avoid boolean flag parameters." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 15 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "no-multi-assignments" +message = "Avoid multiple assignments in a single statement." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "no-shorthand-ternary" +message = "Use of the shorthand ternary operator." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "string-style" +message = "String concatenation can be replaced with interpolation." +count = 5 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "too-many-methods" +message = "Class has too many methods." +count = 1 + +[[issues]] +file = "src/Metadata/Source/AbstractSource.php" +code = "valid-docblock" +message = "Unexpected token in PHPDoc type" +count = 2 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "cyclomatic-complexity" +message = "Class has high complexity." +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "halstead" +message = "Method has a high halstead difficulty" +count = 2 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "kan-defect" +message = "Class has a high kan defect score." +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 1 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "no-else-clause" +message = "Avoid `elseif` clauses." +count = 2 + +[[issues]] +file = "src/ResultSet/AbstractResultSet.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 2 + +[[issues]] +file = "src/ResultSet/HydratingResultSet.php" +code = "halstead" +message = "Method has a high halstead difficulty" +count = 1 + +[[issues]] +file = "src/ResultSet/HydratingResultSet.php" +code = "no-else-clause" +message = "Avoid `elseif` clauses." +count = 1 + +[[issues]] +file = "src/ResultSet/HydratingResultSet.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "cyclomatic-complexity" +message = "Class has high complexity." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "halstead" +message = "Method has a high halstead difficulty" +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "kan-defect" +message = "Class has a high kan defect score." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "no-boolean-flag-parameter" +message = "Avoid boolean flag parameters." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "no-dead-store" +message = "Variable `$rowsAffected` is overwritten before its value is read." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 4 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 1 + +[[issues]] +file = "src/RowGateway/AbstractRowGateway.php" +code = "too-many-methods" +message = "Class has too many methods." +count = 1 + +[[issues]] +file = "src/RowGateway/Feature/FeatureSet.php" +code = "too-many-methods" +message = "Class has too many methods." +count = 1 + +[[issues]] +file = "src/RowGateway/RowGateway.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 1 + +[[issues]] +file = "src/RowGateway/RowGateway.php" +code = "no-negated-ternary" +message = "Negated ternary condition." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "cyclomatic-complexity" +message = "Class has high complexity." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "excessive-parameter-list" +message = "Parameter list is too long." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "kan-defect" +message = "Class has a high kan defect score." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "literal-named-argument" +message = 'Literal argument `"\n ,"` should be passed as a named argument for clarity.' +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "literal-named-argument" +message = "Literal argument `'%'` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "literal-named-argument" +message = "Literal argument `'__'` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 8 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "no-else-clause" +message = "Avoid `elseif` clauses." +count = 5 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 5 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "no-shorthand-ternary" +message = "Use of the shorthand ternary operator." +count = 1 + +[[issues]] +file = "src/Sql/AbstractSql.php" +code = "too-many-methods" +message = "Class has too many methods." +count = 1 + +[[issues]] +file = "src/Sql/Argument/Identifiers.php" +code = "literal-named-argument" +message = "Literal argument `'%s'` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/Argument/Values.php" +code = "literal-named-argument" +message = "Literal argument `'%s'` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "cyclomatic-complexity" +message = "Class has high complexity." +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "kan-defect" +message = "Class has a high kan defect score." +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "literal-named-argument" +message = "Literal argument `' '` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "no-array-accumulation-in-loop" +message = "Array spread in reassignment inside a loop causes O(n²) array copying." +count = 1 + +[[issues]] +file = "src/Sql/Combine.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "kan-defect" +message = "Class has a high kan defect score." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "no-else-clause" +message = "Avoid `elseif` clauses." +count = 2 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/AlterTable.php" +code = "too-many-methods" +message = "Class has too many methods." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/AbstractLengthColumn.php" +code = "literal-named-argument" +message = "Literal argument `0` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/AbstractLengthColumn.php" +code = "literal-named-argument" +message = "Literal argument `2` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/AbstractPrecisionColumn.php" +code = "excessive-parameter-list" +message = "Parameter list is too long." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/AbstractTimestampColumn.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Column.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Column.php" +code = "no-else-clause" +message = "Avoid `elseif` clauses." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Column/Integer.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/AbstractConstraint.php" +code = "literal-named-argument" +message = "Literal argument `'%s'` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/ForeignKey.php" +code = "excessive-parameter-list" +message = "Parameter list is too long." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Constraint/ForeignKey.php" +code = "literal-named-argument" +message = "Literal argument `'%s'` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "cyclomatic-complexity" +message = "Class has high complexity." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "no-else-clause" +message = "Avoid `elseif` clauses." +count = 2 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/CreateTable.php" +code = "too-many-methods" +message = "Class has too many methods." +count = 1 + +[[issues]] +file = "src/Sql/Ddl/Index/Index.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 1 + +[[issues]] +file = "src/Sql/Delete.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 1 + +[[issues]] +file = "src/Sql/Expression.php" +code = "halstead" +message = "Method has a high halstead difficulty" +count = 1 + +[[issues]] +file = "src/Sql/Expression.php" +code = "literal-named-argument" +message = "Literal argument `'%%'` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/Expression.php" +code = "literal-named-argument" +message = "Literal argument `'%s'` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/Expression.php" +code = "literal-named-argument" +message = "Literal argument `1` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/Expression.php" +code = "no-else-clause" +message = "Avoid `elseif` clauses." +count = 2 + +[[issues]] +file = "src/Sql/Insert.php" +code = "cyclomatic-complexity" +message = "Class has high complexity." +count = 1 + +[[issues]] +file = "src/Sql/Insert.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 2 + +[[issues]] +file = "src/Sql/Insert.php" +code = "too-many-methods" +message = "Class has too many methods." +count = 1 + +[[issues]] +file = "src/Sql/Join.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 1 + +[[issues]] +file = "src/Sql/Literal.php" +code = "literal-named-argument" +message = "Literal argument `'%%'` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "literal-named-argument" +message = "Literal argument `''` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "literal-named-argument" +message = "Literal argument `true` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/Platform/Platform.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 2 + +[[issues]] +file = "src/Sql/Predicate/In.php" +code = "halstead" +message = "Method has a high halstead difficulty" +count = 1 + +[[issues]] +file = "src/Sql/Predicate/In.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/In.php" +code = "no-else-clause" +message = "Avoid `elseif` clauses." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Operator.php" +code = "halstead" +message = "Method has a high halstead difficulty" +count = 2 + +[[issues]] +file = "src/Sql/Predicate/Operator.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 2 + +[[issues]] +file = "src/Sql/Predicate/Operator.php" +code = "no-else-clause" +message = "Avoid `elseif` clauses." +count = 2 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/Predicate.php" +code = "too-many-methods" +message = "Class has too many methods." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "cyclomatic-complexity" +message = "Class has high complexity." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "halstead" +message = "Method has a high halstead difficulty" +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "kan-defect" +message = "Class has a high kan defect score." +count = 1 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 2 + +[[issues]] +file = "src/Sql/Predicate/PredicateSet.php" +code = "no-else-clause" +message = "Avoid `elseif` clauses." +count = 5 + +[[issues]] +file = "src/Sql/Select.php" +code = "cyclomatic-complexity" +message = "Class has high complexity." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "halstead" +message = "Method has a high halstead difficulty" +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "halstead" +message = "Method has a high halstead effort" +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "kan-defect" +message = "Class has a high kan defect score." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "literal-named-argument" +message = "Literal argument `' as '` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "literal-named-argument" +message = "Literal argument `2` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 8 + +[[issues]] +file = "src/Sql/Select.php" +code = "no-else-clause" +message = "Avoid `elseif` clauses." +count = 3 + +[[issues]] +file = "src/Sql/Select.php" +code = "no-empty" +message = "Use of the `empty` construct." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 2 + +[[issues]] +file = "src/Sql/Select.php" +code = "no-negated-ternary" +message = "Negated ternary condition." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "too-many-methods" +message = "Class has too many methods." +count = 1 + +[[issues]] +file = "src/Sql/Select.php" +code = "too-many-properties" +message = "Class has too many properties." +count = 1 + +[[issues]] +file = "src/Sql/Sql.php" +code = "no-shorthand-ternary" +message = "Use of the shorthand ternary operator." +count = 4 + +[[issues]] +file = "src/Sql/Update.php" +code = "cyclomatic-complexity" +message = "Class has high complexity." +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "halstead" +message = "Method has a high halstead difficulty" +count = 1 + +[[issues]] +file = "src/Sql/Update.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 2 + +[[issues]] +file = "src/Sql/Update.php" +code = "too-many-methods" +message = "Class has too many methods." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "cyclomatic-complexity" +message = "Class has high complexity." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "kan-defect" +message = "Class has a high kan defect score." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 1 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "no-else-clause" +message = "Avoid `elseif` clauses." +count = 2 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 3 + +[[issues]] +file = "src/TableGateway/AbstractTableGateway.php" +code = "too-many-methods" +message = "Class has too many methods." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeature.php" +code = "no-shorthand-ternary" +message = "Use of the shorthand ternary operator." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/EventFeature.php" +code = "too-many-methods" +message = "Class has too many methods." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/FeatureSet.php" +code = "kan-defect" +message = "Class has a high kan defect score." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/FeatureSet.php" +code = "too-many-methods" +message = "Class has too many methods." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/GlobalAdapterFeature.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 2 + +[[issues]] +file = "src/TableGateway/Feature/MetadataFeature.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 2 + +[[issues]] +file = "src/TableGateway/Feature/RowGatewayFeature.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/RowGatewayFeature.php" +code = "no-else-clause" +message = "Avoid `elseif` clauses." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/RowGatewayFeature.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 2 + +[[issues]] +file = "src/TableGateway/Feature/SequenceFeature.php" +code = "strict-behavior" +message = "Call to `array_search` must enforce strict comparison." +count = 1 + +[[issues]] +file = "src/TableGateway/Feature/SequenceFeature.php" +code = "string-style" +message = "String concatenation can be replaced with interpolation." +count = 3 + +[[issues]] +file = "src/TableGateway/Feature/SequenceFeature.php" +code = "tagged-todo" +message = "TODO should be tagged with (@username) or (#issue)." +count = 1 + +[[issues]] +file = "src/TableGateway/TableGateway.php" +code = "no-shorthand-ternary" +message = "Use of the shorthand ternary operator." +count = 1 + +[[issues]] +file = "test/integration/Adapter/Driver/Pdo/AbstractAdapterTestCase.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 9 + +[[issues]] +file = "test/integration/Adapter/Driver/Pdo/AbstractAdapterTestCase.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConnection`." +count = 1 + +[[issues]] +file = "test/integration/Adapter/Driver/Pdo/AbstractAdapterTestCase.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDriverDisconnectAfterQuoteWithPlatform`." +count = 1 + +[[issues]] +file = "test/integration/Adapter/Driver/Pdo/AbstractAdapterTestCase.php" +code = "strict-types" +message = "Missing `declare(strict_types=1);` statement at the beginning of the file." +count = 1 + +[[issues]] +file = "test/integration/Adapter/Driver/Pdo/AdapterTrait.php" +code = "strict-types" +message = "Missing `declare(strict_types=1);` statement at the beginning of the file." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterAwareTraitTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 3 + +[[issues]] +file = "test/unit/Adapter/AdapterAwareTraitTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetDbAdapterSetsProperty`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterAwareTraitTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetDbAdapter`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 33 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "no-assign-in-argument" +message = "Avoid assignments in function call arguments." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithProfilerDelegatesToSetProfiler`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCreateStatementDelegatesToDriver`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteQueryReturnsRawResultWithoutWrappingQueryResults`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteQueryThrowsWhenExecutionDoesNotProduceAResult`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteQueryWithRawSqlDelegatesToConnectionExecute`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteQueryWithStatementExecutesStatement`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFluentSetProfiler`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetCurrentSchemaDelegatesToConnection`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetDriverReturnsDriver`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetHelpersQuoteIdentifierClosureCallsPlatform`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetHelpersQuoteValueClosureCallsPlatform`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetHelpersReturnsBothFunctions`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetHelpersReturnsQuoteIdentifierFunction`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetHelpersReturnsQuoteValueFunction`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetPlatformReturnsPlatform`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetProfilerReturnsProfiler`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetQueryResultSetPrototypeReturnsResultSet`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicGetReturnsDriverAndPlatformCaseInsensitively`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareQueryBindsParameterArray`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareQueryBindsParameterContainerDirectly`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareQueryPreparesStatementWithoutExecuting`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProducedResultSetPrototypeIsDifferentForEachQuery`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQueryThrowsOnInvalidParameterType`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQueryWhenExecutedProducesAResultSetObjectWhenResultIsQuery`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQueryWhenExecutedProducesAResult`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQueryWhenPreparedProducesStatement`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQueryWhenPreparedWithParameterArrayProducesResult`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQueryWhenPreparedWithParameterContainerProducesResult`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetProfilerDelegatesToDriverWhenProfilerAware`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/AdapterTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AbstractAdapterInterfaceFactoryTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 6 + +[[issues]] +file = "test/unit/Adapter/Container/AbstractAdapterInterfaceFactoryTest.php" +code = "literal-named-argument" +message = '''Literal argument `'PhpDb\Adapter\NoDriver'` should be passed as a named argument for clarity.''' +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AbstractAdapterInterfaceFactoryTest.php" +code = "no-literal-namespace-string" +message = "Use `::class` notation instead of a hardcoded namespace string." +count = 9 + +[[issues]] +file = "test/unit/Adapter/Container/AbstractAdapterInterfaceFactoryTest.php" +code = "prefer-arrow-function" +message = "This closure can be simplified to a more concise arrow function." +count = 2 + +[[issues]] +file = "test/unit/Adapter/Container/AbstractAdapterInterfaceFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCreateReturnsFalseForEmptyConfig`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AbstractAdapterInterfaceFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetConfigCachesResult`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AbstractAdapterInterfaceFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetConfigReturnsEmptyWhenContainerHasNoConfig`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AbstractAdapterInterfaceFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvalidService`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AbstractAdapterInterfaceFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeThrowsWhenDriverNotConfigured`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AbstractAdapterInterfaceFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeUsesResultSetFromContainer`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AbstractAdapterInterfaceFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValidService`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 10 + +[[issues]] +file = "test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php" +code = "no-redundant-variable" +message = "Variable `$container` is assigned but never used." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php" +code = "no-redundant-variable" +message = "Variable `$pluginManagerConfig` is assigned but never used." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDelegatorWithPluginManager`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDelegatorWithServiceManagerAndCustomAdapterName`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDelegatorWithServiceManager`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeReturnsInstanceWhenAdapterIsNotAdapterInterface`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetAdapterShouldBeCalledForExistingAdapter`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetAdapterShouldBeCalledForOnlyConcreteAdapter`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetAdapterShouldNotBeCalledForMissingAdapter`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetAdapterShouldNotBeCalledForWrongClassInstance`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetStateWithCustomAdapterName`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetStateWithDefaultAdapterName`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/AbstractConnectionTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 15 + +[[issues]] +file = "test/unit/Adapter/Driver/AbstractConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDisconnectIsNoOpWhenNotConnected`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/AbstractConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDisconnectNullsResourceWhenConnected`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/AbstractConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetConnectionParametersReturnsEmptyByDefault`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/AbstractConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetDriverNameReturnsNullByDefault`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/AbstractConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetDriverNameReturnsValueWhenSet`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/AbstractConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetProfilerReturnsNullByDefault`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/AbstractConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetResourceAutoConnectsWhenNotConnected`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/AbstractConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInTransactionReturnsFalseByDefault`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/AbstractConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetConnectionParametersStoresAndReturnsConnection`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/AbstractConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetProfilerStoresAndReturnsProfiler`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Feature/AbstractFeatureTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 2 + +[[issues]] +file = "test/unit/Adapter/Driver/Feature/AbstractFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetDriverStoresDriverAndReturnsInstance`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Feature/DriverFeatureProviderTraitTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 4 + +[[issues]] +file = "test/unit/Adapter/Driver/Feature/DriverFeatureProviderTraitTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddFeatureSetsDriverAndStoresFeature`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Feature/DriverFeatureProviderTraitTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddFeatureThrowsWhenUsedOutsideDriverInterface`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Feature/DriverFeatureProviderTraitTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddFeaturesAddsMultipleFeatures`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Feature/DriverFeatureProviderTraitTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetFeatureReturnsFalseWhenNotFound`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Feature/DriverFeatureProviderTraitTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetFeatureReturnsFeatureByClassName`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 15 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "no-empty-catch-clause" +message = "Do not use empty `catch` blocks." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "no-error-control-operator" +message = "Unsafe use of error control operator `@`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "no-literal-password" +message = "Literal passwords or sensitive data should not be stored in code." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "no-redundant-variable" +message = "Variable `$driver` is assigned but never used." +count = 4 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBeginTransactionAutoConnectsWhenNotConnected`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCommitAutoConnectsWhenNotConnected`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithArraySetsConnectionParameters`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithPdoResourceSetsConnected`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteAutoConnectsWhenNotConnected`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteCallsProfilerFinishBeforeThrowingOnError`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteCallsProfilerStartAndFinish`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFluentSetDriver`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetDsnReturnsDsnAfterConnect`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetDsnThrowsWhenDsnIsNull`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareAutoConnectsAndReturnsStatement`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testResource`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetConnectionParametersStoresParams`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 27 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php" +code = "no-redundant-variable" +message = "Variable `$nested` is assigned but never used." +count = 2 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBeginTransactionReturnsInstanceOfConnection`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBeginTransactionSetsInTransactionAtTrue`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCommitReturnsInstanceOfConnection`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCommitSetsInTransactionAtFalse`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCommitWithoutBeginReturnsInstanceOfConnection`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testNestedTransactionsCommit`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testNestedTransactionsRollback`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRollbackDisconnectedThrowsException`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRollbackReturnsInstanceOfConnection`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRollbackSetsInTransactionAtFalse`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRollbackWithoutBeginThrowsException`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testStandaloneCommit`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 18 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "no-redundant-variable" +message = "Variable `$unused` is assigned but never used." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCheckEnvironmentReturnsTrue`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorAddsFeaturesWhenDriverSupportsFeatures`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorSetsDriverOnConnection`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCreateStatementWithNullConnectsAndInitializes`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCreateStatementWithPdoStatementResource`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCreateStatementWithSqlString`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFormatParameterNameFormatsCorrectly`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFormatParameterNameReturnsQuestionMarkForNumericWithoutType`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFormatParameterNameWithInvalidCharacters`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetConnectionReturnsConnectionInstance`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetLastGeneratedValueDelegatesToConnection`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetPrepareTypeReturnsNamed`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetProfilerReturnsSetProfiler`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetProfilerThrowsWhenNotInitialized`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetResultPrototypeReturnsResult`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/PdoTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetProfilerPropagatesProfilerToConnectionAndStatement`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "assert-description" +message = "Missing description in assert function." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 39 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-first-class-callable" +message = "Use first-class callable syntax `...` instead of a arrow function." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBufferIsCallableWithNoEffect`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCountCachesResultFromClosure`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCountCachesResultFromStatementRowCount`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCountWithClosureInvokesClosureAndReturnsValue`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCountWithIntReturnsProvidedValue`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCountWithNoRowCountFallsBackToStatementRowCount`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrentReturnsSameDataOnConsecutiveCalls`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFetchModeAcceptsNamedMode`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFetchModeObjReturnsStdClass`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetAffectedRowsDelegatesToRowCount`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetFetchModeDefaultIsAssoc`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetFieldCountDelegatesToColumnCount`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetGeneratedValueReturnsInitializedValue`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetGeneratedValueReturnsNullByDefault`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetQueryResultClonesGivenPrototypeRatherThanMutatingIt`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetQueryResultInitializesReturnedResultSetWithThisResult`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetQueryResultReturnsDefaultResultSetPrototypeWhenNoneGiven`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetQueryResultThrowsWhenResultIsNotAQueryResult`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetResourceReturnsPdoStatement`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeStoresResourceAndValues`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsBufferedReturnsFalse`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsQueryResultReturnsFalseWhenNoColumns`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsQueryResultReturnsTrueWhenColumnsExist`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testNextAdvancesPositionAndFetchesData`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRewindResetsIterationToStart`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRewindThrowsExceptionOnForwardOnlyAfterAdvancing`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetFetchModeStoresValidMode`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetFetchModeThrowsOnInvalidFetchMode`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetFetchModeThrowsOnInvalidMode`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetStatementModeThrowsOnInvalidMode`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetStatementModeToForward`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetStatementModeToScrollable`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValidReturnsFalseWhenCurrentDataIsFalse`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValidReturnsTrueWhenCurrentDataExists`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/ResultTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 6 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementIntegrationTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 12 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementIntegrationTest.php" +code = "no-assign-in-argument" +message = "Avoid assignments in function call arguments." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testStatementExecuteWillConvertPhpBoolToPdoBoolWhenBinding`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testStatementExecuteWillUsePdoIntForIntWhenBinding`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testStatementExecuteWillUsePdoStrByDefaultWhenBinding`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testStatementExecuteWillUsePdoStrForStringIntegerWhenBinding`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 39 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "no-assign-in-argument" +message = "Avoid assignments in function call arguments." +count = 2 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBindParametersDetectsBooleanValueType`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBindParametersDetectsNullValueType`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBindParametersFromContainerSkipsWhenAlreadyBound`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBindParametersWithErrataTypeDefaultsToString`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBindParametersWithErrataTypeInteger`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBindParametersWithErrataTypeLob`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBindParametersWithErrataTypeNull`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBindParametersWithPositionalIntegers`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCloneClonesParameterContainerWhenSet`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCloneResetsState`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorAcceptsParameterContainerAndOptions`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteAutoPrepares`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteCallsProfilerFinishOnFailure`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteCallsProfilerOnSuccess`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteCastsNonIntErrorCodeToZero`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteReturnsResult`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteThrowsInvalidQueryExceptionOnPdoException`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteThrowsOnInvalidParameterName`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteWithArrayParametersMergesIntoContainer`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteWithParameterContainerSetsContainer`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFluentPrepare`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFluentSetDriver`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFluentSetParameterContainer`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetParameterContainerReturnsContainer`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetProfilerReturnsNullByDefault`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetResourceReturnsPdoStatement`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSql`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeSetsPdoResource`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsPreparedReturnsTrueAfterPrepare`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareThrowsRuntimeExceptionOnPdoFailure`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareThrowsWhenAlreadyPrepared`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSecondExecuteSkipsBindingWhenAlreadyBound`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetProfilerStoresProfiler`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetResourceStoresPdoStatement`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetSql`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/StatementTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 2 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/TestAsset/SqliteMemoryPdo.php" +code = "no-empty" +message = "Use of the `empty` construct." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/TestAsset/TestConnection.php" +code = "no-else-clause" +message = "Avoid `elseif` clauses." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Driver/Pdo/TestAsset/TestConnection.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Exception/InvalidConnectionParametersExceptionTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Exception/InvalidConnectionParametersExceptionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorStoresMessageAndParameters`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 55 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 2 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithDataPopulatesContainer`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCount`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrent`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetErrataIterator`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetMaxLengthIterator`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetNamedArray`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetPositionalArrayReturnsValues`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testKey`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testNext`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetExists`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetGetErrataByPositionalIndex`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetGetErrataThrowsWhenNameDoesNotExist`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetGetErrata`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetGetMaxLengthByPositionalIndex`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetGetMaxLengthThrowsWhenNameDoesNotExist`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetGet`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetHasErrataByPositionalIndex`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetHasErrata`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetHasMaxLengthByPositionalIndex`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetHasMaxLength`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetSetAndGetMaxLength`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetSetErrataByPositionalIndex`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetSetErrata`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetSetMaxLengthByPositionalIndex`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetSetReferenceCreatesReference`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetSetThrowsOnInvalidKeyType`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetSetWithIntNotInPositionsCastsToString`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetSetWithNameMappingMatchForNonColonName`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetSet`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetUnsetByPositionalIndex`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetUnsetErrataByPositionalIndex`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetUnsetErrataThrowsWhenNameDoesNotExist`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetUnsetErrata`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetUnsetMaxLengthByPositionalIndex`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetUnsetMaxLengthThrowsWhenNameDoesNotExist`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetUnsetMaxLength`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetUnset`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRewind`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetFromArrayNamed`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetFromArray`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValid`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/ParameterContainerTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 15 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 28 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "no-error-control-operator" +message = "Unsafe use of error control operator `@`." +count = 4 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAbstractPlatformQuoteValueEscapesWithDriver`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAbstractPlatformQuoteValueThrowsWithoutDriver`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetIdentifierSeparator`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetName`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetQuoteIdentifierSymbol`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetQuoteValueSymbol`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQuoteIdentifierChain`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQuoteIdentifierInFragmentReturnsUnquotedWhenQuotingDisabled`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQuoteIdentifierInFragment`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQuoteIdentifierReturnsUnquotedWhenQuotingDisabled`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQuoteIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQuoteTrustedValueEscapesSpecialCharacters`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQuoteValueEscapesSpecialCharacters`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQuoteValueListThrowsWithoutDriver`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQuoteValueRaisesNoticeWithoutPlatformSupport`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQuoteValueThrowsWithoutDriver`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Platform/Sql92Test.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 22 + +[[issues]] +file = "test/unit/Adapter/Profiler/ProfilerTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 12 + +[[issues]] +file = "test/unit/Adapter/Profiler/ProfilerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetLastProfileReturnsSqlAndTimings`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Profiler/ProfilerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetProfilesReturnsAllRecordedProfiles`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Profiler/ProfilerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProfilerFinishThrowsWithoutStart`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Profiler/ProfilerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProfilerStartClonesParameterContainerFromStatementContainer`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Profiler/ProfilerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProfilerStartWithStatementContainer`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Profiler/ProfilerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProfilerStartWithString`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/Profiler/ProfilerTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Adapter/StatementContainerTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 6 + +[[issues]] +file = "test/unit/Adapter/StatementContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithSqlSetsSql`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/StatementContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithoutSqlDoesNotSetSql`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/StatementContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetAndGetParameterContainer`." +count = 1 + +[[issues]] +file = "test/unit/Adapter/StatementContainerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetAndGetSql`." +count = 1 + +[[issues]] +file = "test/unit/ConfigProviderTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 1 + +[[issues]] +file = "test/unit/ConfigProviderTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvocationProvidesDependencyConfiguration`." +count = 1 + +[[issues]] +file = "test/unit/Container/AdapterInterfaceFactoryTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 4 + +[[issues]] +file = "test/unit/Container/AdapterInterfaceFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeCreatesAdapterWithAllDependencies`." +count = 1 + +[[issues]] +file = "test/unit/Container/AdapterInterfaceFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeCreatesAdapterWithDefaultResultSet`." +count = 1 + +[[issues]] +file = "test/unit/Container/AdapterInterfaceFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeCreatesAdapterWithoutOptionalProfiler`." +count = 1 + +[[issues]] +file = "test/unit/Container/AdapterInterfaceFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeThrowsWhenAdapterConfigIsEmpty`." +count = 1 + +[[issues]] +file = "test/unit/Container/AdapterInterfaceFactoryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvokeThrowsWhenContainerHasNoConfig`." +count = 1 + +[[issues]] +file = "test/unit/Exception/ContainerExceptionTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 4 + +[[issues]] +file = "test/unit/Exception/ContainerExceptionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testForServiceCreatesFormattedExceptionMessage`." +count = 1 + +[[issues]] +file = "test/unit/Exception/ContainerExceptionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testImplementsContainerExceptionInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/AbstractTableObjectTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 15 + +[[issues]] +file = "test/unit/Metadata/Object/AbstractTableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCompleteTableObjectWithAllProperties`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/AbstractTableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithEmptyString`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/AbstractTableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithName`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/AbstractTableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithNullName`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/AbstractTableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetColumnsReturnsNullWhenNotSet`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/AbstractTableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetConstraintsReturnsNullWhenNotSet`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/AbstractTableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetColumnsAndGetColumns`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/AbstractTableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetColumnsWithEmptyArray`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/AbstractTableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetConstraintsAndGetConstraints`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/AbstractTableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetConstraintsWithEmptyArray`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/AbstractTableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetNameAndGetName`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 56 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCompleteColumnObjectWithAllProperties`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithAllParameters`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithNullSchema`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetErrataNonExistentKeyReturnsNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetErratasReturnsEmptyArrayInitially`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsNullableAlias`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsNumericUnsignedAlias`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetCharacterMaximumLengthAndGetCharacterMaximumLengthWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetCharacterMaximumLengthWithNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetCharacterOctetLengthAndGetCharacterOctetLengthWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetCharacterOctetLengthWithNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetColumnDefaultAndGetColumnDefaultWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetColumnDefaultWithNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetDataTypeAndGetDataTypeWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetErrataAndGetErrata`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetErratasIteratesCorrectly`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetErratasWithArrayAndGetErratas`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetIsNullableAndGetIsNullableWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetNameAndGetName`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetNumericPrecisionAndGetNumericPrecisionWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetNumericScaleAndGetNumericScaleWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetNumericUnsignedAndGetNumericUnsignedWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetOrdinalPositionAndGetOrdinalPositionWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetSchemaNameAndGetSchemaName`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ColumnObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetTableNameAndGetTableNameWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintKeyObjectTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 40 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintKeyObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCompleteConstraintKeyObject`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintKeyObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorSetsColumnName`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintKeyObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testForeignKeyConstants`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintKeyObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetColumnNameAndGetColumnNameWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintKeyObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetForeignKeyDeleteRuleAndGetForeignKeyDeleteRule`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintKeyObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetForeignKeyDeleteRuleWithAllConstants`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintKeyObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetForeignKeyUpdateRuleAndGetForeignKeyUpdateRule`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintKeyObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetForeignKeyUpdateRuleWithAllConstants`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintKeyObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetOrdinalPositionAndGetOrdinalPositionWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintKeyObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetPositionInUniqueConstraintAndGetPositionInUniqueConstraintWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintKeyObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetReferencedColumnNameAndGetReferencedColumnNameWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintKeyObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetReferencedTableNameAndGetReferencedTableNameWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintKeyObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetReferencedTableSchemaAndGetReferencedTableSchemaWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 79 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCompleteCheckConstraint`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCompleteForeignKeyConstraint`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCompletePrimaryKeyConstraint`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCompleteUniqueConstraint`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithAllParameters`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithNullSchema`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testHasColumnsReturnsFalseWhenEmpty`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testHasColumnsReturnsTrueWhenPopulated`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsCheckReturnsFalseForOtherTypes`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsCheckReturnsTrueForCheck`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsForeignKeyReturnsFalseForOtherTypes`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsForeignKeyReturnsTrueForForeignKey`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsPrimaryKeyReturnsFalseForOtherTypes`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsPrimaryKeyReturnsTrueForPrimaryKey`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsUniqueReturnsFalseForOtherTypes`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsUniqueReturnsTrueForUnique`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetCheckClauseAndGetCheckClauseWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetColumnsAndGetColumnsWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetColumnsWithEmptyArray`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetDeleteRuleAndGetDeleteRuleWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetMatchOptionAndGetMatchOptionWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetNameAndGetName`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetReferencedColumnsAndGetReferencedColumnsWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetReferencedTableNameAndGetReferencedTableNameWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetReferencedTableSchemaAndGetReferencedTableSchemaWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetSchemaNameAndGetSchemaName`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetTableNameAndGetTableNameWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetTypeAndGetType`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ConstraintObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetUpdateRuleAndGetUpdateRuleWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TableObjectTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 14 + +[[issues]] +file = "test/unit/Metadata/Object/TableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanBeInstantiated`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCompleteTableObjectWithAllInheritedFunctionality`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithName`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithNullName`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExtendsAbstractTableObject`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInheritedSetColumnsWorks`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInheritedSetConstraintsWorks`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TableObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInheritedSetNameWorks`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 63 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCompleteTriggerObject`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testNullValuesForAllProperties`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetActionConditionAndGetActionConditionWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetActionOrderAndGetActionOrderWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetActionOrientationAndGetActionOrientationWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetActionReferenceNewRowAndGetActionReferenceNewRowWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetActionReferenceNewTableAndGetActionReferenceNewTableWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetActionReferenceOldRowAndGetActionReferenceOldRowWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetActionReferenceOldTableAndGetActionReferenceOldTableWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetActionStatementAndGetActionStatementWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetActionTimingAndGetActionTimingWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetCreatedAndGetCreatedWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetCreatedWithDifferentDateTime`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetEventManipulationAndGetEventManipulationWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetEventObjectCatalogAndGetEventObjectCatalogWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetEventObjectSchemaAndGetEventObjectSchemaWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetEventObjectTableAndGetEventObjectTableWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/TriggerObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetNameAndGetNameWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 34 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCompleteViewObjectWithAllProperties`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithName`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithNullName`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExtendsAbstractTableObject`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInheritedColumnsWork`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInheritedConstraintsWork`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsUpdatableAliasWithNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsUpdatableAlias`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetCheckOptionAndGetCheckOptionWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetCheckOptionWithNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetIsUpdatableAndGetIsUpdatableWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetIsUpdatableWithFalse`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetIsUpdatableWithNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetViewDefinitionAndGetViewDefinitionWithFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetViewDefinitionWithNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testViewObjectWithInheritedSetName`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Object/ViewObjectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testViewObjectWithNullProperties`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 132 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithNullSchemaUsesDefaultConstant`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithSchemaFromAdapter`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetColumnNamesThrowsWhenLoadColumnDataDoesNotPopulate`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetColumnNamesUsesDefaultSchemaWhenNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetColumnNames`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetColumnThrowsExceptionForNonExistentColumn`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetColumnUsesDefaultSchemaWhenNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetColumn`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetColumnsUsesDefaultSchemaWhenNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetColumns`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetConstraintKeysUsesDefaultSchemaWhenNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetConstraintKeysWithMultipleKeys`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetConstraintKeysWithoutReferences`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetConstraintKeys`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetConstraintThrowsExceptionForNonExistent`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetConstraintUsesDefaultSchemaWhenNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetConstraintWithCheckClause`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetConstraint`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetConstraintsUsesDefaultSchemaWhenNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetConstraints`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSchemasCallsLoadSchemaData`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTableForBaseTable`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTableForView`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTableNamesExcludesViewsByDefault`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTableNamesIncludesViewsWhenRequested`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTableNamesWithNullSchemaUsesDefault`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTableNamesWithSpecificSchema`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTableThrowsExceptionForNonExistentTable`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTableThrowsExceptionForUnsupportedTableType`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTableUsesDefaultSchemaWhenNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTablesUsesDefaultSchemaWhenNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTables`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTriggerNamesUsesDefaultSchemaWhenNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTriggerNames`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTriggerThrowsExceptionForNonExistent`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTriggerUsesDefaultSchemaWhenNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTrigger`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTriggersUsesDefaultSchemaWhenNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTriggers`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetViewForExistingView`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetViewNamesUsesDefaultSchemaWhenNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetViewNames`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetViewThrowsExceptionForNonExistentView`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetViewThrowsExceptionForTable`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetViewUsesDefaultSchemaWhenNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetViewsUsesDefaultSchemaWhenNull`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetViews`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLoadColumnDataCallsPrepareDataHierarchy`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLoadColumnDataEarlyReturnWhenDataExists`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLoadConstraintDataCallsPrepareDataHierarchy`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLoadConstraintDataEarlyReturnWhenDataExists`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLoadConstraintDataKeysCallsPrepareDataHierarchy`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLoadConstraintDataKeysEarlyReturnWhenDataExists`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLoadConstraintReferencesCallsPrepareDataHierarchy`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLoadConstraintReferencesEarlyReturnWhenDataExists`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLoadTableNameDataCallsPrepareDataHierarchy`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLoadTableNameDataEarlyReturnWhenDataExists`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLoadTriggerDataCallsPrepareDataHierarchy`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLoadTriggerDataEarlyReturnWhenDataExists`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareDataHierarchyWithMultipleKeys`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareDataHierarchyWithSingleKey`." +count = 1 + +[[issues]] +file = "test/unit/Metadata/Source/AbstractSourceTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 6 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetIntegrationTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 2 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrentCallsDataSourceCurrentAsManyTimesWithoutBuffer`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrentCallsDataSourceCurrentOnceWithBuffer`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "assert-description" +message = "Missing description in assert function." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 47 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBufferIterations`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBuffer`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCountReturnsCachedResult`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCountReturnsNullForUncountableDataSource`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCount`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrentReturnsBufferedDataOnSecondPass`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrent`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetDataSource`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetFieldCountReturnsCachedValue`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetFieldCountReturnsZeroForEmptyIterator`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetFieldCountReturnsZeroWithNoDataSource`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetFieldCountWithCountableRow`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetFieldCount`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeDoesNotCallCount`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeResetsBufferWhenAlreadyBuffered`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeWithBufferedResultInterface`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeWithEmptyArray`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeWithIteratorAggregate`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeWithResultInterfaceRewindsWhenBuffered`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitialize`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsBuffered`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testKey`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMultipleRewindBufferIterations`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testNext`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRewindResetsIteratorPosition`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRewindWithNonIteratorDataSource`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testToArrayConvertsArrayObjectsViaGetArrayCopy`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testToArrayThrowsOnNonCastableRows`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testToArray`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValidReturnsFalseAfterLastElement`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValidWithNonIteratorDataSource`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValid`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/AbstractResultSetTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 15 + +[[issues]] +file = "test/unit/ResultSet/HydratingResultSetIntegrationTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/HydratingResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrentWillReturnBufferedRow`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/HydratingResultSetTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 21 + +[[issues]] +file = "test/unit/ResultSet/HydratingResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorDefaultsToArraySerializableHydrator`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/HydratingResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrentDisablesBufferingImplicitly`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/HydratingResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrentDoesnotHasData`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/HydratingResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrentHasData`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/HydratingResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrentWithBufferReturnsBufferedObject`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/HydratingResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetHydrator`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/HydratingResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetObjectPrototype`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/HydratingResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetRowPrototypeReturnsDefaultArrayObject`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/HydratingResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetHydrator`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/HydratingResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetObjectPrototype`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/HydratingResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetRowPrototypeStoresPrototype`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/HydratingResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testToArrayUsesHydratorExtract`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/HydratingResultSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testToArray`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 27 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "literal-named-argument" +message = "Literal argument `75` should be passed as a named argument for clarity." +count = 3 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "literal-named-argument" +message = "Literal argument `true` should be passed as a named argument for clarity." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBufferCalledAfterIterationThrowsException`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanProvideArrayAsDataSource`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanProvideIteratorAggregateAsDataSource`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanProvideIteratorAsDataSource`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCountReturnsCountOfRows`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrentClonesRowPrototypeOnEachCall`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrentReturnsArrayObjectWhenReturnTypeIsArrayObject`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrentReturnsArrayWhenReturnTypeIsArray`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrentReturnsNullForNonExistingValues`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrentWithBufferingCallsDataSourceCurrentOnce`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDataSourceIsNullByDefault`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFieldCountIsZeroWithNoDataSourcePresent`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFieldCountRepresentsNumberOfFieldsInARowOfData`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetArrayObjectPrototypeDelegatesToGetRowPrototype`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetReturnTypeReturnsArrayWhenSetToArray`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInvalidDataSourceRaisesException`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testReturnTypeIsObjectByDefault`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRowObjectPrototypeIsMutable`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRowObjectPrototypeIsPopulatedByRowObjectByDefault`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRowObjectPrototypeMayBePassedToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSettingInvalidReturnTypeRaisesException`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testToArrayCreatesArrayOfArraysRepresentingRows`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testToArrayRaisesExceptionForRowsThatAreNotArraysOrArrayCastable`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhenReturnTypeIsArrayThenIterationReturnsArrays`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhenReturnTypeIsObjectThenIterationReturnsRowObjects`." +count = 1 + +[[issues]] +file = "test/unit/ResultSet/ResultSetIntegrationTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 2 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 48 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "method-name" +message = "Method name `test__getThrowsExceptionForInvalidColumn` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "method-name" +message = "Method name `test__get` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "method-name" +message = "Method name `test__isset` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "method-name" +message = "Method name `test__set` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "method-name" +message = "Method name `test__unset` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 4 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCount`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDelete`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExchangeArray`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeCreatesFeatureSetIfNotSet`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeEarlyReturnWhenAlreadyInitialized`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeOnlyRunsOnce`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeThrowsExceptionWhenPrimaryKeyColumnIsNull`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeThrowsExceptionWhenSqlIsNull`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeThrowsExceptionWhenTableIsNull`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetExists`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetGet`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetSet`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetUnset`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPopulate`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessPrimaryKeyData`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRowExistsInDatabaseReturnsFalseWhenNew`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRowExistsInDatabaseReturnsTrueAfterPopulateWithTrue`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSaveInsertMultiKey`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSaveInsert`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSaveUpdateChangingPrimaryKey`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSaveUpdate`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testToArray`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__getThrowsExceptionForInvalidColumn`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__get`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__isset`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__set`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__unset`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/AbstractRowGatewayTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 21 + +[[issues]] +file = "test/unit/RowGateway/Feature/AbstractFeatureTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 5 + +[[issues]] +file = "test/unit/RowGateway/Feature/AbstractFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetMagicMethodSpecificationsReturnsEmptyArray`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/AbstractFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetNameReturnsClassName`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/AbstractFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeThrowsRuntimeException`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/AbstractFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetRowGateway`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 20 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddFeatureCallsSetRowGatewayWhenRowGatewayIsSet`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddFeature`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddFeatures`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testApplyCallsMethodOnFeatures`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testApplyHaltsWhenFeatureReturnsHalt`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testApplySkipsFeatureWithoutMethod`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallMagicCallReturnsNull`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallMagicGetReturnsNull`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallMagicSetReturnsNull`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCallMagicCallReturnsFalse`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCallMagicGetReturnsFalse`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCallMagicSetReturnsFalse`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithEmptyArray`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithFeatures`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetFeatureByClassNameReturnsFeature`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetFeatureByClassNameReturnsNullWhenNotFound`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetRowGateway`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/RowGatewayTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 9 + +[[issues]] +file = "test/unit/RowGateway/RowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorThrowsExceptionWhenSqlTableDoesNotMatch`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/RowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithArrayPrimaryKey`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/RowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithNullPrimaryKey`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/RowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithSqlObject`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/RowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithStringPrimaryKey`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/RowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithTableIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/RowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testEmptyPrimaryKey`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/RowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeReturnsEarlyWhenAlreadyInitialized`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/RowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeThrowsWhenSqlIsNull`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/RowGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeThrowsWhenTableIsNull`." +count = 1 + +[[issues]] +file = "test/unit/RowGateway/RowGatewayTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 3 + +[[issues]] +file = "test/unit/Sql/AbstractSqlFunctionalTestCase.php" +code = "halstead" +message = "Method has a high halstead volume" +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlFunctionalTestCase.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 3 + +[[issues]] +file = "test/unit/Sql/AbstractSqlFunctionalTestCase.php" +code = "prefer-first-class-callable" +message = "Use first-class callable syntax `...` instead of a arrow function." +count = 2 + +[[issues]] +file = "test/unit/Sql/AbstractSqlFunctionalTestCase.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlFunctionalTestCase.php" +code = "tagged-todo" +message = "TODO should be tagged with (@username) or (#issue)." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 54 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCreateSqlFromSpecNonCombinedByThrowsOnUnsupportedCount`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCreateSqlFromSpecWithCombinedByScalarParam`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCreateSqlFromSpecWithCombinedByThrowsOnUnsupportedCount`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCreateSqlFromSpecWithNonCombinedByParam`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCreateSqlFromSpecificationThrowsOnParameterCountMismatch`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFlattenExpressionValuesViaInPredicateWithParameterContainer`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFlattenExpressionValuesViaInPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLocalizeVariablesCopiesSubjectProperties`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessExpressionThrowsOnUnknownArgumentType`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessExpressionWithIdentifiersArgument`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessExpressionWithParameterContainerAndParameterizationTypeNamed`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessExpressionWithValuesArgument`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessExpressionWithoutParameterContainer`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessExpressionWorksWithExpressionContainingExpressionObject`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessExpressionWorksWithExpressionContainingSelectObject`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessExpressionWorksWithExpressionContainingStringParts`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessExpressionWorksWithExpressionObjectWithPercentageSigns`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessExpressionWorksWithNamedParameterPrefixContainingWhitespace`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessExpressionWorksWithNamedParameterPrefix`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessJoinReturnsNullWhenEmpty`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessJoinWithArrayAlias`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessJoinWithExpressionNameViaArray`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessJoinWithPredicateExpressionOnClause`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessJoinWithSelectSubqueryViaArray`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessJoinWithTableIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessSubSelectUsesDecoratorWhenPlatformDecorator`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessSubSelectWithParameterContainer`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessSubSelectWithoutParameterContainer`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRenderTableWithAlias`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testResolveColumnValueWithArrayAndFromTable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testResolveColumnValueWithNamedParameterPrefix`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testResolveColumnValueWithNull`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testResolveColumnValueWithSelect`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testResolveTableWithSelect`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testResolveTableWithTableIdentifierAndSchema`." +count = 1 + +[[issues]] +file = "test/unit/Sql/AbstractSqlTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 8 + +[[issues]] +file = "test/unit/Sql/Argument/LiteralTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 3 + +[[issues]] +file = "test/unit/Sql/Argument/LiteralTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSpecificationReturnsPlaceholder`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Argument/LiteralTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTypeReturnsLiteral`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Argument/LiteralTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetValueReturnsLiteralString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ArgumentTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 26 + +[[issues]] +file = "test/unit/Sql/ArgumentTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorThrowsExceptionForInvalidSelectType`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ArgumentTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithArrayContainingArgumentType`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ArgumentTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithBooleanValue`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ArgumentTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithExplicitType`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ArgumentTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithExpressionInterface`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ArgumentTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithFloatValue`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ArgumentTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithNullValue`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ArgumentTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithSimpleArray`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ArgumentTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithSimpleValue`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ArgumentTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithSqlInterface`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ArgumentTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testStaticIdentifierMethod`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ArgumentTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testStaticLiteralMethod`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ArgumentTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testStaticSelectMethodCreatesSelectArgument`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ArgumentTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testStaticValueMethod`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ArgumentTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 7 + +[[issues]] +file = "test/unit/Sql/CombineTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 17 + +[[issues]] +file = "test/unit/Sql/CombineTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAlignColumnsAppendsNullExpressionsForMissingColumns`." +count = 1 + +[[issues]] +file = "test/unit/Sql/CombineTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAlignColumnsReturnsEarlyWhenEmpty`." +count = 1 + +[[issues]] +file = "test/unit/Sql/CombineTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAlignColumns`." +count = 1 + +[[issues]] +file = "test/unit/Sql/CombineTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCombineWithArrayOfSelectAndModifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/CombineTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithSelectDelegatesToCombine`." +count = 1 + +[[issues]] +file = "test/unit/Sql/CombineTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetRawState`." +count = 1 + +[[issues]] +file = "test/unit/Sql/CombineTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringEmpty`." +count = 1 + +[[issues]] +file = "test/unit/Sql/CombineTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringFromArray`." +count = 1 + +[[issues]] +file = "test/unit/Sql/CombineTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringWithModifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/CombineTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/CombineTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareStatementWithModifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/CombineTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRejectsInvalidStatement`." +count = 1 + +[[issues]] +file = "test/unit/Sql/CombineTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 6 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 86 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "literal-named-argument" +message = "Literal argument `''` should be passed as a named argument for clarity." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddColumn`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddConstraintGeneratesCorrectSql`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddConstraint`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testChainedOperations`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testChangeColumnGeneratesCorrectSql`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testChangeColumn`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithEmptyTable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithTableIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithTable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDropColumn`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDropConstraint`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDropIndex`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testEmptyAlterTableGeneratesMinimalSql`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetOptionsReturnsEmpty`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetRawStateIncludesTableOptions`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetRawStateReturnsAllState`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetRawStateWithInvalidKey`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetRawStateWithSpecificKey`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringWithBoolOption`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringWithColumnAndEngineOption`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringWithEngineOption`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringWithIntOption`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringWithMultipleOptions`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringWithStringOption`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMixedOperationsInCorrectOrder`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMultipleChangeColumns`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMultipleColumnsAndConstraints`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMultipleConstraints`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMultipleDropOperations`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetOptionFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetOptionsReplacesAll`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetTable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testTableIdentifierInChangeColumn`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/AlterTableTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 9 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/AbstractLengthColumnTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 6 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/AbstractLengthColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/AbstractLengthColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetLength`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/AbstractLengthColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetLength`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/AbstractLengthColumnTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 4 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/AbstractPrecisionColumnTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 10 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/AbstractPrecisionColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetDecimal`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/AbstractPrecisionColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetDigits`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/AbstractPrecisionColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/AbstractPrecisionColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetDecimal`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/AbstractPrecisionColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetDigits`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/AbstractPrecisionColumnTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 7 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/BigIntegerTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 3 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/BigIntegerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/BigIntegerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testObjectConstruction`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/BigIntegerTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/BinaryTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/BinaryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/BinaryTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/BlobTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/BlobTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/BlobTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/BooleanTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 4 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/BooleanTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/BooleanTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsAlwaysNotNullable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/BooleanTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/CharTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/CharTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/CharTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 44 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddConstraintAppendsConstraintToColumn`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataIncludesConstraintValues`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataIncludesConstraints`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithBoolDefault`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithFloatDefault`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithLiteralDefault`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithValueDefault`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetDefaultWithBool`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetDefaultWithFloat`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetDefaultWithLiteral`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetDefaultWithValue`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetDefault`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetName`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetNullable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetOption`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetOptions`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/ColumnTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 13 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DateTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DateTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DatetimeTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DatetimeTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DatetimeTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DecimalTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 13 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DecimalTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorSetsDigitsAndDecimal`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DecimalTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithNullDecimal`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DecimalTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DecimalTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInheritanceFromAbstractPrecisionColumn`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DecimalTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetDecimalAndGetDecimal`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DecimalTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetDigitsAndGetDigits`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DecimalTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 5 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DoubleTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DoubleTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/DoubleTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/FloatingTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/FloatingTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/FloatingTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/IntegerTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 7 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/IntegerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataExcludesLengthWhenNotSet`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/IntegerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataIncludesLengthWhenOptionSet`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/IntegerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/IntegerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testObjectConstruction`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/IntegerTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 3 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/JsonTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/JsonTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/JsonTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/SmallIntegerTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 3 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/SmallIntegerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/SmallIntegerTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testObjectConstruction`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/SmallIntegerTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/TextTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/TextTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/TextTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/TimeTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/TimeTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/TimeTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/TimestampTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 16 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/TimestampTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithCurrentTimestampDefaultAndOnUpdate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/TimestampTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithCurrentTimestampDefault`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/TimestampTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithOnUpdateOption`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/TimestampTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithoutOnUpdateOption`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/TimestampTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/TimestampTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInheritanceFromAbstractTimestampColumn`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/TimestampTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 4 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/VarbinaryTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/VarbinaryTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/VarbinaryTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/VarcharTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 9 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/VarcharTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithNullLength`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/VarcharTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/VarcharTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInheritanceFromAbstractLengthColumn`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/VarcharTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetLengthAndGetLength`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Column/VarcharTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 4 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/AbstractConstraintTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 5 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/AbstractConstraintTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddColumn`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/AbstractConstraintTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetColumns`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/AbstractConstraintTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetColumns`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/CheckTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/CheckTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/CheckTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/ForeignKeyTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 35 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/ForeignKeyTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/ForeignKeyTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetName`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/ForeignKeyTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetOnDeleteRule`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/ForeignKeyTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetOnUpdateRule`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/ForeignKeyTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetReferenceColumn`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/ForeignKeyTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetReferenceTable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/ForeignKeyTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 15 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/PrimaryKeyTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/PrimaryKeyTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/PrimaryKeyTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/UniqueKeyTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 2 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/UniqueKeyTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Constraint/UniqueKeyTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 87 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddColumn`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddConstraint`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testChainedOperations`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithTableIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithTemporaryFlag`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testEmptyTableConstruction`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetOptionsReturnsEmpty`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetRawStateIncludesTableOptions`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetRawStateReturnsAllState`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetRawStateWithInvalidKey`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringWithBoolOption`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringWithIntOption`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringWithLiteralOption`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringWithMultipleOptions`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringWithNoOptionsUnchanged`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringWithStringOption`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIfNotExistsCombinedWithTemporary`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIfNotExistsDisable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIfNotExists`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsTemporary`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMultipleColumns`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMultipleConstraints`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testObjectConstruction`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetOptionFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetOptionsReplacesAll`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetTableAfterConstruction`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetTable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetTemporary`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/CreateTableTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 29 + +[[issues]] +file = "test/unit/Sql/Ddl/DropTableTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 11 + +[[issues]] +file = "test/unit/Sql/Ddl/DropTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/DropTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIfExistsDisable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/DropTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIfExistsWithTableIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/DropTableTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIfExists`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/DropTableTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 6 + +[[issues]] +file = "test/unit/Sql/Ddl/Index/IndexTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 15 + +[[issues]] +file = "test/unit/Sql/Ddl/Index/IndexTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithBtreeType`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Index/IndexTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithHashType`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Index/IndexTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithLengthUnmatched`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Index/IndexTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithLength`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Index/IndexTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithTypeAndLengths`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Index/IndexTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Index/IndexTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetTypeAndGetType`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Ddl/Index/IndexTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 7 + +[[issues]] +file = "test/unit/Sql/DeleteTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 50 + +[[issues]] +file = "test/unit/Sql/DeleteTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithTableIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/DeleteTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithTable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/DeleteTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFrom`." +count = 1 + +[[issues]] +file = "test/unit/Sql/DeleteTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetRawStateWithKey`." +count = 1 + +[[issues]] +file = "test/unit/Sql/DeleteTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetRawState`." +count = 1 + +[[issues]] +file = "test/unit/Sql/DeleteTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringWithEmptyWhere`." +count = 1 + +[[issues]] +file = "test/unit/Sql/DeleteTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/DeleteTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicGetReturnsNullForUnknownProperty`." +count = 1 + +[[issues]] +file = "test/unit/Sql/DeleteTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicGetReturnsWhereClause`." +count = 1 + +[[issues]] +file = "test/unit/Sql/DeleteTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareStatement`." +count = 1 + +[[issues]] +file = "test/unit/Sql/DeleteTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSpecificationconstantsCouldBeOverridedByExtensionInGetSqlString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/DeleteTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSpecificationconstantsCouldBeOverridedByExtensionInPrepareStatement`." +count = 1 + +[[issues]] +file = "test/unit/Sql/DeleteTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhereAcceptsExpressionInterface`." +count = 1 + +[[issues]] +file = "test/unit/Sql/DeleteTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhere`." +count = 1 + +[[issues]] +file = "test/unit/Sql/DeleteTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 19 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 22 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithFalsyValidParameters`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithInvalidParameter`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithLiteralZero`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithMultipleArguments`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataThrowsExceptionWhenParameterCountMismatch`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataUsesRegexWhenPlaceholderCountMismatches`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWillEscapePercent`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionPreservesPercentageSignInFromUnixtime`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testNumberOfReplacementsConsidersWhenSameVariableIsUsedManyTimes`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testNumberOfReplacementsForExpressionWithParameters`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetExpressionException`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetExpressionThrowsOnEmptyString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetExpression`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetParametersWrapsArrayInValuesArgument`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetParameters`." +count = 1 + +[[issues]] +file = "test/unit/Sql/ExpressionTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 8 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 39 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "method-name" +message = "Method name `test__get` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "method-name" +message = "Method name `test__isset` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "method-name" +message = "Method name `test__set` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "method-name" +message = "Method name `test__unset` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 2 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testColumns`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testEmptyArrayValues`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringUsingColumnsAndValuesMethods`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInto`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareStatementWithSelect`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareStatement`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSpecificationconstantsCouldBeOverridedByExtensionInGetSqlString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSpecificationconstantsCouldBeOverridedByExtensionInPrepareStatement`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValuesMerge`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValuesThrowsExceptionWhenArrayMergeOverSelect`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValuesThrowsExceptionWhenNotArrayOrSelect`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValuesThrowsExceptionWhenSelectMergeOverArray`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValues`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__get`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__isset`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__set`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__unset`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertIgnoreTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 11 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 45 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "method-name" +message = "Method name `test__get` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "method-name" +message = "Method name `test__isset` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "method-name" +message = "Method name `test__set` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "method-name" +message = "Method name `test__unset` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 2 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "no-redundant-variable" +message = "Variable `$value` is assigned but never used." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testColumns`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testEmptyArrayValues`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringThrowsExceptionWhenNoValuesOrSelect`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringUsingColumnsAndValuesMethods`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetThrowsExceptionForNonExistentColumn`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInto`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareStatementCreatesParameterContainerWhenNotPresent`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareStatementWithSelect`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareStatement`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessInsertWithPdoDriverUsesDriverColumnQuoting`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSpecificationconstantsCouldBeOverridedByExtensionInGetSqlString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSpecificationconstantsCouldBeOverridedByExtensionInPrepareStatement`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testUnsetThrowsExceptionForNonExistentColumn`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValuesMerge`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValuesThrowsExceptionWhenArrayMergeOverSelect`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValuesThrowsExceptionWhenNotArrayOrSelect`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValuesThrowsExceptionWhenSelectMergeOverArray`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValues`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__get`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__isset`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__set`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__unset`." +count = 1 + +[[issues]] +file = "test/unit/Sql/InsertTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 11 + +[[issues]] +file = "test/unit/Sql/JoinTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 13 + +[[issues]] +file = "test/unit/Sql/JoinTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCount`." +count = 1 + +[[issues]] +file = "test/unit/Sql/JoinTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCurrentReturnsTheCurrentJoinSpecification`." +count = 1 + +[[issues]] +file = "test/unit/Sql/JoinTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitialPositionIsZero`." +count = 1 + +[[issues]] +file = "test/unit/Sql/JoinTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testJoinFullOuter`." +count = 1 + +[[issues]] +file = "test/unit/Sql/JoinTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testJoinThrowsOnInvalidMultiElementArray`." +count = 1 + +[[issues]] +file = "test/unit/Sql/JoinTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testJoinWillThrowAnExceptionIfNameIsNoValid`." +count = 1 + +[[issues]] +file = "test/unit/Sql/JoinTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testJoin`." +count = 1 + +[[issues]] +file = "test/unit/Sql/JoinTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testKeyReturnsTheCurrentPosition`." +count = 1 + +[[issues]] +file = "test/unit/Sql/JoinTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testNextIncrementsThePosition`." +count = 1 + +[[issues]] +file = "test/unit/Sql/JoinTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testReset`." +count = 1 + +[[issues]] +file = "test/unit/Sql/JoinTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRewindResetsPositionToZero`." +count = 1 + +[[issues]] +file = "test/unit/Sql/JoinTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValidReturnsTrueIfTheIteratorIsAtAValidPositionAndFalseIfNot`." +count = 1 + +[[issues]] +file = "test/unit/Sql/JoinTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 7 + +[[issues]] +file = "test/unit/Sql/LiteralTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 8 + +[[issues]] +file = "test/unit/Sql/LiteralTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWillEscapePercent`." +count = 1 + +[[issues]] +file = "test/unit/Sql/LiteralTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/LiteralTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetLiteral`." +count = 1 + +[[issues]] +file = "test/unit/Sql/LiteralTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetLiteral`." +count = 1 + +[[issues]] +file = "test/unit/Sql/LiteralTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 5 + +[[issues]] +file = "test/unit/Sql/Platform/AbstractPlatformTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 9 + +[[issues]] +file = "test/unit/Sql/Platform/AbstractPlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringDelegatesToDecoratorSubject`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/AbstractPlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringThrowsWhenSubjectNotSqlInterface`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/AbstractPlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTypeDecoratorLoopMatchesByInstanceof`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/AbstractPlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTypeDecoratorReturnsSubjectWhenNoMatch`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/AbstractPlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareStatementDelegatesToDecoratorSubject`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/AbstractPlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareStatementThrowsWhenSubjectNotPreparable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/AbstractPlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetAndGetTypeDecorator`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/AbstractPlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetSubjectReturnsStatic`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/AbstractPlatformTest.php" +code = "string-style" +message = "String concatenation can be replaced with interpolation." +count = 2 + +[[issues]] +file = "test/unit/Sql/Platform/PlatformTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 18 + +[[issues]] +file = "test/unit/Sql/Platform/PlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetDefaultPlatformReturnsInstance`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/PlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringDelegatesToTypeDecorator`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/PlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringThrowsWhenSubjectNotSqlInterface`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/PlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTypeDecoratorFallsThroughWhenNoMatch`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/PlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTypeDecoratorMatchesByInstanceofLoop`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/PlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTypeDecoratorMatchesExactClass`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/PlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTypeDecoratorReturnsSubjectWhenNoDecoratorRegistered`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/PlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareStatementThrowsWhenSubjectNotPreparable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/PlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testResolveDefaultPlatform`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/PlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testResolvePlatformNameCachesResult`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/PlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testResolvePlatformName`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/PlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testResolvePlatformWithAdapterInterface`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/PlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testResolvePlatformWithPlatformInterface`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/PlatformTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetTypeDecoratorRegistersDecorator`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Platform/PlatformTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 5 + +[[issues]] +file = "test/unit/Sql/Predicate/BetweenTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 75 + +[[issues]] +file = "test/unit/Sql/Predicate/BetweenTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorCanPassIdentifierMinimumAndMaximumValues`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/BetweenTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorYieldsNullIdentifierMinimumAndMaximumValues`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/BetweenTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataThrowsExceptionWhenIdentifierNotSet`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/BetweenTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataThrowsExceptionWhenMaxValueNotSet`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/BetweenTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataThrowsExceptionWhenMinValueNotSet`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/BetweenTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIdentifierIsMutable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/BetweenTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMaxValueIsMutable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/BetweenTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMinValueIsMutable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/BetweenTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAndValuesAndArrayOfTypes`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/BetweenTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSpecificationIsMutable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/BetweenTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSpecificationIsNullByDefault`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/BetweenTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 23 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 39 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassArrayOfMultiNullsParameterToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassArrayOfMultiPredicatesParameterToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassArrayOfMultiScalarsParameterToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassArrayOfOneNullParameterToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassArrayOfOnePredicateParameterToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassArrayOfOneScalarParameterToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassLiteralAndSingleScalarParameterToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassMultiNullParametersToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassMultiScalarParametersToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassNoParameterToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassSingleNullParameterToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassSinglePredicateParameterToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassSingleZeroParameterValueToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testEmptyConstructorYieldsEmptyLiteralAndParameter`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLiteralIsMutable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testParameterIsMutable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRetrievingWherePartsReturnsSpecificationArrayOfLiteralAndParametersAndArrayOfTypes`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/ExpressionTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 11 + +[[issues]] +file = "test/unit/Sql/Predicate/InTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 72 + +[[issues]] +file = "test/unit/Sql/Predicate/InTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassIdentifierAndEmptyValueSetToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/InTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassIdentifierAndValueSetToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/InTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testEmptyConstructorYieldsNullIdentifierAndValueSet`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/InTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataThrowsExceptionWhenIdentifierNotSet`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/InTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataThrowsExceptionWhenValueSetNotSet`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/InTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithEmptyValues`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/InTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithSubselectAndArrayIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/InTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithSubselectAndIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/InTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithSubselect`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/InTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIdentifierIsMutable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/InTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAndValuesAndArrayOfTypes`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/InTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetValueSetWithArgumentInterfacePassesThrough`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/InTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetValueSetWithSelectWrapsInArgumentSelect`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/InTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testValueSetIsMutable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/InTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 14 + +[[issues]] +file = "test/unit/Sql/Predicate/IsNullTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 21 + +[[issues]] +file = "test/unit/Sql/Predicate/IsNullTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassIdentifierToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/IsNullTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testEmptyConstructorYieldsNullIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/IsNullTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataThrowsExceptionWhenIdentifierNotSet`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/IsNullTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIdentifierIsMutable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/IsNullTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAndArrayOfTypes`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/IsNullTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetIdentifierWithArgumentInterfacePassesThrough`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/IsNullTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetIdentifierWithStringConvertsToIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/IsNullTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSpecificationIsMutable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/IsNullTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSpecificationIsNullByDefault`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/IsNullTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 6 + +[[issues]] +file = "test/unit/Sql/Predicate/LikeTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 44 + +[[issues]] +file = "test/unit/Sql/Predicate/LikeTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAccessorsMutators`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/LikeTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructEmptyArgs`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/LikeTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructWithArgs`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/LikeTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataThrowsExceptionWhenIdentifierNotSet`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/LikeTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataThrowsExceptionWhenLikeNotSet`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/LikeTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/LikeTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInstanceOfPerSetters`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/LikeTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 16 + +[[issues]] +file = "test/unit/Sql/Predicate/LiteralTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 5 + +[[issues]] +file = "test/unit/Sql/Predicate/LiteralTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/LiteralTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetLiteral`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/LiteralTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetLiteral`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/LiteralTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 4 + +[[issues]] +file = "test/unit/Sql/Predicate/NotBetweenTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 23 + +[[issues]] +file = "test/unit/Sql/Predicate/NotBetweenTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAndValuesAndArrayOfTypes`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/NotBetweenTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSpecificationIsNullByDefault`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/NotBetweenTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 8 + +[[issues]] +file = "test/unit/Sql/Predicate/NotInTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 32 + +[[issues]] +file = "test/unit/Sql/Predicate/NotInTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithSubselectAndArrayIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/NotInTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithSubselectAndIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/NotInTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataWithSubselect`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/NotInTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAndValuesAndArrayOfTypes`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/NotInTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 7 + +[[issues]] +file = "test/unit/Sql/Predicate/NotLikeTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 36 + +[[issues]] +file = "test/unit/Sql/Predicate/NotLikeTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAccessorsMutators`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/NotLikeTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructEmptyArgs`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/NotLikeTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructWithArgs`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/NotLikeTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionData`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/NotLikeTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInstanceOfPerSetters`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/NotLikeTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 13 + +[[issues]] +file = "test/unit/Sql/Predicate/OperatorTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 50 + +[[issues]] +file = "test/unit/Sql/Predicate/OperatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassAllValuesToConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/OperatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testEmptyConstructorYieldsDefaultsForOperatorAndLeftAndRightTypes`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/OperatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testEmptyConstructorYieldsNullLeftAndRightValues`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/OperatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataThrowsExceptionWhenLeftNotSet`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/OperatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataThrowsExceptionWhenRightNotSet`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/OperatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLeftIsMutable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/OperatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOperatorIsMutable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/OperatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRetrievingWherePartsReturnsSpecificationArrayOfLeftAndRightAndArrayOfTypes`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/OperatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRightIsMutable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/OperatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetLeftWithExpressionInterfaceWrapsInSelect`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/OperatorTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetRightWithExpressionInterfaceWrapsInSelect`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/OperatorTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 13 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateSetTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 46 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddPredicateThrowsOnInvalidCombination`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddPredicatesThrowsWhenStringKeyUsedWithPredicateInterface`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddPredicatesWithExpression`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddPredicatesWithMultipleExpressions`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddPredicates`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassBothPredicateAndCombinationToAddPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanPassPredicatesAndDefaultCombinationViaConstructor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanUseOrPredicateAndAndPredicateMethods`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCombinationIsAndByDefault`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testEmptyConstructorYieldsCountOfZero`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetExpressionDataReturnsEmptyWhenNoPredicates`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateSetTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 10 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 90 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "no-else-clause" +message = "Avoid `else` clauses." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBetweenCreatesBetweenPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBetweenCreatesNotBetweenPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanChainPredicateFactoriesBetweenOperators`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanNestPredicates`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testEqualToCreatesOperatorPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExpressionNullParameters`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExpression`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGreaterThanCreatesOperatorPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGreaterThanOrEqualToCreatesOperatorPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInCreatesInPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsNotNullCreatesIsNotNullPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsNullCreatesIsNullPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLessThanCreatesOperatorPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLessThanOrEqualToCreatesOperatorPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLikeCreatesLikePredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLiteralCreatesLiteralPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLiteral`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicGetNestReturnsNestedPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicGetUnnestReturnsParentPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testNotEqualToCreatesOperatorPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testNotInCreatesNotInPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testNotLikeCreatesLikePredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPredicateMethodAddsCustomPredicateInterface`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testUnnestThrowsWhenNotNested`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWillBindSqlParametersToExpressionsWithGivenParameter`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWillBindSqlParametersToExpressionsWithGivenStringParameter`." +count = 1 + +[[issues]] +file = "test/unit/Sql/Predicate/PredicateTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 21 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 151 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "excessive-parameter-list" +message = "Parameter list is too long." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "halstead" +message = "Method has a high halstead volume and effort" +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "no-assign-in-argument" +message = "Avoid assignments in function call arguments." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "no-boolean-flag-parameter" +message = "Avoid boolean flag parameters." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "no-redundant-variable" +message = "Variable `$value` is assigned but never used." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBadJoin`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCloneDeepCopiesAllSubObjects`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCloning`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testColumnsWithPrefixDisabledOmitsTablePrefix`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testColumnsWithPrefixDisabled`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testColumns`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCombineThrowsWhenAlreadyCombined`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCombineWrapsStatementInParentheses`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCombine`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstruct`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFromThrowsExceptionForInvalidArrayFormat`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFromThrowsExceptionForInvalidTableType`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFromThrowsExceptionWhenTableReadOnly`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFromWithAliasArrayResolvesTableAndAlias`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testFrom`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetRawStateInitializesLazyProperties`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetThrowsExceptionForInvalidProperty`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGroup`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testHavingAcceptsExpressionInterface`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testHavingArgument1IsHavingObject`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testHaving`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsTableReadOnly`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testJoin`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLimitExceptionOnInvalidParameter`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLimit`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicAccessor`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMagicGetJoinsReturnsJoinInstance`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffsetExceptionOnInvalidParameter`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOffset`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOrderCorrectlySplitsParameter`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOrderWithAssociativeArray`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOrderWithExpressionObject`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOrderWithStringContainingDirection`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testOrder`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareStatement`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessMethods`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQuantifierParameterExpressionInterface`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testQuantifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testResetCombine`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testResetQuantifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testResetTableThrowsWhenTableReadOnly`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testResetThrowsOnInvalidPart`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testReset`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSelectUsingTableIdentifierWithEmptyScheme`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetSpecificationStoresValidSpecification`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetSpecificationThrowsExceptionForInvalidName`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhereAcceptsExpressionInterface`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhereArgument1IsAssociativeArrayContainingReplacementCharacter`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhereArgument1IsAssociativeArrayIsPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhereArgument1IsAssociativeArrayNotContainingReplacementCharacter`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhereArgument1IsClosure`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhereArgument1IsIndexedArrayArgument2IsOr`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhereArgument1IsIndexedArray`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhereArgument1IsPredicate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhereArgument1IsString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhereArgument1IsWhereObject`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhereReturnsSameSelectObject`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 31 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "variable-name" +message = "Variable name `$select32subselect` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "variable-name" +message = "Variable name `$select39subselect` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "variable-name" +message = "Variable name `$select44b` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "variable-name" +message = "Variable name `$select48b` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/Sql/SelectTest.php" +code = "variable-name" +message = "Variable name `$select48combined` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 13 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "method-name" +message = "Method name `test__construct` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBuildSqlStringThrowsWhenPlatformNotSqlInterface`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testBuildSqlString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDeleteThrowsWhenTableConflicts`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDelete`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlPlatformReturnsPlatformDecorator`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInsertThrowsWhenTableConflicts`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInsert`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareStatementForSqlObject`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareStatementThrowsWhenPlatformNotPreparable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSelectThrowsWhenTableConflicts`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSelect`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testUpdateThrowsWhenTableConflicts`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testUpdate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__construct`." +count = 1 + +[[issues]] +file = "test/unit/Sql/SqlTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 7 + +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetDefaultSchema`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSchemaFromObjectStringCast`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSchema`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTableFromObjectStringCast`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRejectsInvalidSchema`." +count = 1 + +[[issues]] +file = "test/unit/Sql/TableIdentifierTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testRejectsInvalidTable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 68 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "no-fully-qualified-global-class-like" +message = "Fully-qualified class-like reference detected." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCloneDeepCopiesSetWhereAndJoins`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCloneUpdate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructWithTableIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstruct`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetRawStateReturnsAllState`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetRawState`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringForFalseUpdateValueParameter`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlStringWithEmptyWhere`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSqlString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetUpdateFails`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetUpdate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testJoinChainable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testJoinMultiUpdate`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testJoinWithTableIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testJoin`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPassingMultipleKeyValueInWhereClause`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPrepareStatement`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testProcessSetWithPdoDriverUsesDriverColumnQuoting`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetWithMergeFlag`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetWithNonStringKeyThrowsException`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetWithNumericPriority`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSet`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSortableSet`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSpecificationconstantsCouldBeOverridedByExtensionInGetSqlString`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSpecificationconstantsCouldBeOverridedByExtensionInPrepareStatement`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testTable`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhereAcceptsExpressionInterface`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testWhere`." +count = 1 + +[[issues]] +file = "test/unit/Sql/UpdateTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 20 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 47 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "method-name" +message = "Method name `test__callThrowsExceptionForInvalidMethod` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "method-name" +message = "Method name `test__callWithFeatureSetMagicCall` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "method-name" +message = "Method name `test__cloneWithAliasedTableIdentifier` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "method-name" +message = "Method name `test__cloneWithTableIdentifier` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "method-name" +message = "Method name `test__clone` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "method-name" +message = "Method name `test__getAdapter` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "method-name" +message = "Method name `test__getLastInsertValue` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "method-name" +message = "Method name `test__getTable` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "method-name" +message = "Method name `test__getThrowsExceptionForInvalidProperty` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "method-name" +message = "Method name `test__getWithFeatureSetMagicGet` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "method-name" +message = "Method name `test__get` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "method-name" +message = "Method name `test__setThrowsExceptionForInvalidProperty` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "method-name" +message = "Method name `test__setWithFeatureSetMagicSet` should be in camel case." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDeleteWithClosure`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDeleteWith`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDelete`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteDeleteThrowsExceptionWhenTableDoesNotMatch`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteInsertThrowsExceptionWhenTableDoesNotMatch`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteSelectThrowsExceptionWhenArrayTableDoesNotMatch`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testExecuteUpdateThrowsExceptionWhenTableDoesNotMatch`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetAdapter`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetColumns`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetFeatureSet`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetLastInsertValue`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSelectResultPrototype`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSql`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetTable`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeBuildsAResultSet`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeEarlyReturnWhenAlreadyInitialized`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeThrowsExceptionWithoutAdapter`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeThrowsExceptionWithoutTable`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInsertWith`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInsert`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testIsInitialized`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSelectAppliesColumnsWhenStarSelected`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSelectWithArrayTable`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSelectWithClosure`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSelectWithNoWhere`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSelectWithWhereString`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testUpdateWithJoinDefaultType`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testUpdateWithJoin`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testUpdateWithNoCriteria`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testUpdateWith`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testUpdate`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__callThrowsExceptionForInvalidMethod`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__callWithFeatureSetMagicCall`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__cloneWithAliasedTableIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__cloneWithTableIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__clone`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__getAdapter`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__getLastInsertValue`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__getTable`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__getThrowsExceptionForInvalidProperty`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__getWithFeatureSetMagicGet`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__get`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__setThrowsExceptionForInvalidProperty`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `test__setWithFeatureSetMagicSet`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/AbstractTableGatewayTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 16 + +[[issues]] +file = "test/unit/TableGateway/Feature/AbstractFeatureTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 4 + +[[issues]] +file = "test/unit/TableGateway/Feature/AbstractFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetMagicMethodSpecificationsReturnsEmptyArray`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/AbstractFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetNameReturnsClassName`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/AbstractFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInitializeDoesNothing`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/AbstractFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetTableGateway`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 13 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetParamWithDefault`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPropagationIsStoppedAlwaysReturnsFalse`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetNameAndGetName`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetParamAndGetParam`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetParamsAndGetParams`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetParamsWithObject`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetTargetAndGetTarget`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testStopPropagation`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 4 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeatureTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 49 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeatureTest.php" +code = "no-assign-in-argument" +message = "Avoid assignments in function call arguments." +count = 13 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithDefaults`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetEventManager`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetEvent`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostDelete`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitialize`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInsert`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostSelect`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostUpdate`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreDelete`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreInitializeAddsIdentifiersForCustomTableGatewayClass`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreInitialize`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreInsert`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreSelect`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/EventFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreUpdate`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 22 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddFeatureThatFeatureDoesNotHaveTableGatewayButFeatureSetHas`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddFeatureThatFeatureHasTableGatewayButFeatureSetDoesNotHave`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testAddFeaturesReturnsFluentInterface`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testApplyCallsAllFeaturesWhenNoHalt`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testApplyCallsMethodOnFeatures`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testApplyHaltsWhenFeatureReturnsHalt`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testApplyPassesArgumentsToFeatures`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testApplySkipsFeatureWithoutMethod`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallMagicCallReturnsNullWhenNoFeatureHasMethod`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallMagicCallSucceedsForValidMethodOfAddedFeature`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallMagicGetReturnsNull`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCallMagicSetReturnsNull`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCallMagicCallReturnsFalseForAddedMethodOfAddedFeature`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCallMagicCallReturnsFalseWhenNoFeaturesHaveBeenAdded`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCallMagicCallReturnsTrueForAddedMethodOfAddedFeature`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCallMagicGetReturnsFalse`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testCanCallMagicSetReturnsFalse`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithFeatures`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetFeatureByClassNameReturnsNullWhenNotFound`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetTableGateway`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/FeatureSetTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 6 + +[[issues]] +file = "test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetStaticAdapterReturnsDefaultAdapterWhenClassSpecificNotSet`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetStaticAdapterThrowsExceptionWhenNoAdapterSet`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreInitializeSetsAdapterOnTableGateway`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSetStaticAdapter`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSubclassCanSetAndGetOwnAdapter`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSubclassFallsBackToDefaultAdapterWhenNoSpecificAdapterSet`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testSubclassThrowsExceptionWhenNoAdaptersSet`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 5 + +[[issues]] +file = "test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithSlaveSql`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testGetSlaveAdapter`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeWithProvidedSlaveSql`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitialize`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostSelect`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreSelect`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 12 + +[[issues]] +file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" +code = "no-isset" +message = "Use of the `isset` construct." +count = 2 + +[[issues]] +file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorSetsInitialSharedData`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeRecordsListOfColumnsInPrimaryKeyToSharedMetadata`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeRecordsPrimaryKeyColumnToSharedMetadata`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeSkipsPrimaryKeyCheckIfNotTable`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeThrowsExceptionWhenNoPrimaryKeyFound`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeWithArrayTable`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/MetadataFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeWithTableIdentifier`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 6 + +[[issues]] +file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorStoresArguments`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorStoresRowGatewayInstance`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithNoArguments`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeThrowsExceptionForNonResultSet`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeThrowsExceptionWhenMetadataHasNoMetadataKey`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeThrowsExceptionWhenNoMetadataAndNoPrimaryKey`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeWithMetadataFeature`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeWithRowGatewayInstance`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/RowGatewayFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInitializeWithStringPrimaryKey`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 13 + +[[issues]] +file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLastSequenceIdThrowsExceptionForUnsupportedPlatform`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testLastSequenceId`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testNextSequenceIdThrowsExceptionForUnsupportedPlatform`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testNextSequenceId`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInsertDoesNotSetLastInsertValueWhenSequenceValueIsNull`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPostInsertSetsLastInsertValue`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreInsertGeneratesSequenceWhenPrimaryKeyNotInValues`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreInsertReturnsEarlyWhenNextSequenceIdReturnsNull`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreInsertWhenPrimaryKeyAlreadyInValues`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testPreInsertWithPrimaryKeyColumnButNullValue`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/Feature/SequenceFeatureTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 5 + +[[issues]] +file = "test/unit/TableGateway/Feature/TestAsset/TestTableGatewayFeature.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testMethod`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/TableGatewayTest.php" +code = "assertion-style" +message = "Inconsistent assertions style." +count = 32 + +[[issues]] +file = "test/unit/TableGateway/TableGatewayTest.php" +code = "no-assign-in-argument" +message = "Avoid assignments in function call arguments." +count = 3 + +[[issues]] +file = "test/unit/TableGateway/TableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorThrowsExceptionWhenSqlTableDoesNotMatch`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/TableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithArrayOfFeatures`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/TableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithCustomResultSetPrototype`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/TableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithFeatureSet`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/TableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructorWithSingleFeature`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/TableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testConstructor`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/TableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testDeleteShouldResetTableToUnaliasedTable`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/TableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testInsertShouldResetTableToUnaliasedTable`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/TableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testTableAsAliasedTableIdentifierObject`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/TableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testTableAsString`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/TableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testTableAsTableIdentifierObject`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/TableGatewayTest.php" +code = "prefer-test-attribute" +message = "Use `#[Test]` attribute instead of `test` prefix on method `testUpdateShouldResetTableToUnaliasedTable`." +count = 1 + +[[issues]] +file = "test/unit/TableGateway/TableGatewayTest.php" +code = "strict-assertions" +message = "Use strict assertions in PHPUnit tests." +count = 2 diff --git a/mago.toml b/mago.toml new file mode 100644 index 000000000..86ec9038b --- /dev/null +++ b/mago.toml @@ -0,0 +1,18 @@ +extends = "vendor/php-db/phpdb-qa-tools/mago.toml" +php-version = "8.2.0" + +[source] +paths = ["src", "test"] +includes = ["vendor"] + +[formatter] +parentheses-around-new-in-member-access = true + +[linter] +baseline = "lint-baseline.toml" + +[analyzer] +baseline = "analyzer-baseline.toml" +class-initializers = [ + "PhpDb\\ResultSet\\AbstractResultSet::initialize", +] diff --git a/phpcs.xml.dist b/phpcs.xml.dist deleted file mode 100644 index 9f8eda43d..000000000 --- a/phpcs.xml.dist +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - src - test - - - - - - - - - */test/unit/Adapter/Driver/IbmDb2/StatementTest.php - - - src/Adapter/AdapterServiceFactory.php - src/Adapter/AdapterAbstractServiceFactory.php - - diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon deleted file mode 100644 index d78b460f6..000000000 --- a/phpstan-baseline.neon +++ /dev/null @@ -1,127 +0,0 @@ -parameters: - ignoreErrors: - - - message: '#^Variable \$paramSpecs might not be defined\.$#' - identifier: variable.undefined - count: 1 - path: src/Sql/AbstractSql.php - - - - message: '#^Parameter \#1 \$attribute \(string\) of method PhpDbTest\\Adapter\\Driver\\TestAsset\\PdoMock\:\:getAttribute\(\) should be compatible with parameter \$attribute \(int\) of method PDO\:\:getAttribute\(\)$#' - identifier: method.childParameterType - count: 1 - path: test/unit/Adapter/Driver/TestAsset/PdoMock.php - - - - message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertIsArray\(\) with array will always evaluate to true\.$#' - identifier: staticMethod.alreadyNarrowedType - count: 1 - path: test/unit/ResultSet/HydratingResultSetTest.php - - - - message: '#^Access to an undefined property PhpDb\\RowGateway\\RowGateway\:\:\$foo\.$#' - identifier: property.notFound - count: 5 - path: test/unit/RowGateway/AbstractRowGatewayTest.php - - - - message: '#^Access to an undefined property PhpDb\\RowGateway\\RowGateway\:\:\$id\.$#' - identifier: property.notFound - count: 2 - path: test/unit/RowGateway/AbstractRowGatewayTest.php - - - - message: '#^Access to an undefined property PhpDb\\RowGateway\\RowGateway\:\:\$testColumn\.$#' - identifier: property.notFound - count: 4 - path: test/unit/RowGateway/AbstractRowGatewayTest.php - - - - message: '#^Property PhpDbTest\\RowGateway\\AbstractRowGatewayTest\:\:\$rowGateway \(PhpDb\\RowGateway\\RowGateway\) does not accept PhpDb\\RowGateway\\AbstractRowGateway&PHPUnit\\Framework\\MockObject\\MockObject\.$#' - identifier: assign.propertyType - count: 2 - path: test/unit/RowGateway/AbstractRowGatewayTest.php - - - - message: '#^Static call to instance method PhpDbTest\\Sql\\AbstractSqlFunctionalTestCase\:\:createColumn\(\)\.$#' - identifier: method.staticCall - count: 2 - path: test/unit/Sql/AbstractSqlFunctionalTestCase.php - - - - message: '#^Static call to instance method PhpDbTest\\Sql\\AbstractSqlFunctionalTestCase\:\:createTable\(\)\.$#' - identifier: method.staticCall - count: 2 - path: test/unit/Sql/AbstractSqlFunctionalTestCase.php - - - - message: '#^Static call to instance method PhpDbTest\\Sql\\AbstractSqlFunctionalTestCase\:\:delete\(\)\.$#' - identifier: method.staticCall - count: 1 - path: test/unit/Sql/AbstractSqlFunctionalTestCase.php - - - - message: '#^Static call to instance method PhpDbTest\\Sql\\AbstractSqlFunctionalTestCase\:\:insert\(\)\.$#' - identifier: method.staticCall - count: 1 - path: test/unit/Sql/AbstractSqlFunctionalTestCase.php - - - - message: '#^Static call to instance method PhpDbTest\\Sql\\AbstractSqlFunctionalTestCase\:\:select\(\)\.$#' - identifier: method.staticCall - count: 15 - path: test/unit/Sql/AbstractSqlFunctionalTestCase.php - - - - message: '#^Static call to instance method PhpDbTest\\Sql\\AbstractSqlFunctionalTestCase\:\:update\(\)\.$#' - identifier: method.staticCall - count: 3 - path: test/unit/Sql/AbstractSqlFunctionalTestCase.php - - - - message: '#^PHPDoc tag @var with type PhpDb\\Sql\\Ddl\\Column\\ColumnInterface is not subtype of native type PHPUnit\\Framework\\MockObject\\MockObject\.$#' - identifier: varTag.nativeType - count: 2 - path: test/unit/Sql/Ddl/AlterTableTest.php - - - - message: '#^PHPDoc tag @var with type PhpDb\\Sql\\Ddl\\Constraint\\ConstraintInterface is not subtype of native type PHPUnit\\Framework\\MockObject\\MockObject\.$#' - identifier: varTag.nativeType - count: 1 - path: test/unit/Sql/Ddl/AlterTableTest.php - - - - message: '#^Call to an undefined method PHPUnit\\Framework\\MockObject\\MockObject\:\:addColumn\(\)\.$#' - identifier: method.notFound - count: 1 - path: test/unit/Sql/Ddl/Constraint/AbstractConstraintTest.php - - - - message: '#^Call to an undefined method PHPUnit\\Framework\\MockObject\\MockObject\:\:getColumns\(\)\.$#' - identifier: method.notFound - count: 3 - path: test/unit/Sql/Ddl/Constraint/AbstractConstraintTest.php - - - - message: '#^Call to an undefined method PHPUnit\\Framework\\MockObject\\MockObject\:\:setColumns\(\)\.$#' - identifier: method.notFound - count: 2 - path: test/unit/Sql/Ddl/Constraint/AbstractConstraintTest.php - - - - message: '#^Access to an undefined property PhpDb\\Sql\\InsertIgnore\:\:\$foo\.$#' - identifier: property.notFound - count: 6 - path: test/unit/Sql/InsertIgnoreTest.php - - - - message: '#^Access to an undefined property PhpDb\\Sql\\Insert\:\:\$foo\.$#' - identifier: property.notFound - count: 6 - path: test/unit/Sql/InsertTest.php - - - - message: '#^Access to an undefined property PhpDb\\Sql\\Insert\:\:\$nonexistent\.$#' - identifier: property.notFound - count: 1 - path: test/unit/Sql/InsertTest.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist deleted file mode 100644 index 035df5b2c..000000000 --- a/phpstan.neon.dist +++ /dev/null @@ -1,19 +0,0 @@ -includes: - - phpstan-baseline.neon -parameters: - level: 4 - paths: - - src - - test - excludePaths: - - test/benchmark (?) - universalObjectCratesClasses: - - Laminas\Stdlib\ArrayObject - stubFiles: - - stubs/Laminas/ServiceManager/Factory/AbstractFactoryInterface.stub - - stubs/Laminas/ServiceManager/Factory/DelegatorFactoryInterface.stub - - stubs/Laminas/ServiceManager/Factory/FactoryInterface.stub - - stubs/Laminas/ServiceManager/Factory/InvokableFactory.stub - - stubs/Laminas/ServiceManager/Initializer/InitializerInterface.stub - - stubs/Psr/Container/ContainerInterface.stub - treatPhpDocTypesAsCertain: false \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist index cc3153f7b..0e7051d6b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,20 +1,22 @@ - - - - - src - - - + ./test/unit @@ -23,4 +25,13 @@ ./test/integration + + + + + + + ./src + + diff --git a/src/Adapter/Adapter.php b/src/Adapter/Adapter.php index 1c3fde997..6d0a19618 100644 --- a/src/Adapter/Adapter.php +++ b/src/Adapter/Adapter.php @@ -21,21 +21,58 @@ public function __construct( protected Driver\DriverInterface $driver, protected Platform\PlatformInterface $platform, protected ResultSet\ResultSetInterface $queryResultSetPrototype = new ResultSet\ResultSet(), - protected ?Profiler\ProfilerInterface $profiler = null + protected ?Profiler\ProfilerInterface $profiler = null, ) { if ($profiler) { $this->setProfiler($profiler); } } + /** + * Create statement + */ #[Override] - public function setProfiler(Profiler\ProfilerInterface $profiler): Profiler\ProfilerAwareInterface + public function createStatement( + ?string $initialSql = null, + ParameterContainer|array $initialParameters = [], + ): Driver\StatementInterface { + $statement = $this->driver->createStatement($initialSql); + if ( + is_array($initialParameters) + ) { + $initialParameters = new ParameterContainer($initialParameters); + } + $statement->setParameterContainer($initialParameters); + return $statement; + } + + /** + * Execute raw SQL or a prepared statement. + * + * Narrows the driver's execution result to a Driver\ResultInterface, + * never a wrapped ResultSet. Callers can check isQueryResult() and use + * getQueryResult() themselves if they want the result wrapped. + * + * @throws Exception\RuntimeException When execution did not produce a result. + */ + #[Override] + public function executeQuery(Driver\StatementInterface|string $sql): Driver\ResultInterface { - $this->profiler = $profiler; - if ($this->driver instanceof Profiler\ProfilerAwareInterface) { - $this->driver->setProfiler($profiler); + $result = $sql instanceof Driver\StatementInterface + ? $sql->execute() + : $this->driver->getConnection()->execute($sql); + + if (! $result instanceof Driver\ResultInterface) { + throw new Exception\RuntimeException('Query execution did not produce a result'); } - return $this; + + return $result; + } + + #[Override] + public function getCurrentSchema(): string|false + { + return $this->driver->getConnection()->getCurrentSchema(); } #[Override] @@ -44,6 +81,31 @@ public function getDriver(): Driver\DriverInterface return $this->driver; } + /** + * {@inheritDoc} + */ + #[Override] + public function getHelpers() + { + $functions = []; + $platform = $this->platform; + foreach (func_get_args() as $arg) { + switch ($arg) { + case self::FUNCTION_QUOTE_IDENTIFIER: + $functions[] = static function ($value) use ($platform) { + return $platform->quoteIdentifier($value); + }; + break; + case self::FUNCTION_QUOTE_VALUE: + $functions[] = static function ($value) use ($platform) { + return $platform->quoteValue($value); + }; + break; + } + } + return $functions; + } + #[Override] public function getPlatform(): Platform\PlatformInterface { @@ -62,10 +124,27 @@ public function getQueryResultSetPrototype(): ResultSet\ResultSetInterface return $this->queryResultSetPrototype; } + /** + * Prepare a statement for the given SQL, optionally binding parameters. + * + * Always prepares the statement; never executes it. Use executeQuery() + * to run the returned statement. + */ #[Override] - public function getCurrentSchema(): string|false - { - return $this->driver->getConnection()->getCurrentSchema(); + public function prepareQuery( + string $sql, + ParameterContainer|array $parameters = [], + ): Driver\StatementInterface { + $statement = $this->driver->createStatement($sql); + + if (is_array($parameters)) { + $parameters = new ParameterContainer($parameters); + } + + $statement->setParameterContainer($parameters); + $statement->prepare(); + + return $statement; } /** @@ -81,20 +160,19 @@ public function getCurrentSchema(): string|false public function query( string $sql, ParameterContainer|array|string $parametersOrQueryMode = self::QUERY_MODE_PREPARE, - ?ResultSet\ResultSetInterface $resultPrototype = null + ?ResultSet\ResultSetInterface $resultPrototype = null, ): Driver\StatementInterface|ResultSet\ResultSetInterface|Driver\ResultInterface { - if ($parametersOrQueryMode === self::QUERY_MODE_PREPARE) { + if (self::QUERY_MODE_PREPARE === $parametersOrQueryMode) { return $this->prepareQuery($sql); } $sql = match (true) { - $parametersOrQueryMode === self::QUERY_MODE_EXECUTE - => $sql, + self::QUERY_MODE_EXECUTE === $parametersOrQueryMode => $sql, $parametersOrQueryMode instanceof ParameterContainer, - is_array($parametersOrQueryMode) + is_array($parametersOrQueryMode), => $this->prepareQuery($sql, $parametersOrQueryMode), default => throw new Exception\InvalidArgumentException( - 'Flag incorrectly set' + 'Flag incorrectly set', ), }; @@ -105,92 +183,14 @@ public function query( : $result; } - /** - * Prepare a statement for the given SQL, optionally binding parameters. - * - * Always prepares the statement; never executes it. Use executeQuery() - * to run the returned statement. - */ - #[Override] - public function prepareQuery( - string $sql, - ParameterContainer|array $parameters = [] - ): Driver\StatementInterface { - $statement = $this->driver->createStatement($sql); - - if (is_array($parameters)) { - $parameters = new ParameterContainer($parameters); - } - - $statement->setParameterContainer($parameters); - $statement->prepare(); - - return $statement; - } - - /** - * Execute raw SQL or a prepared statement. - * - * Narrows the driver's execution result to a Driver\ResultInterface, - * never a wrapped ResultSet. Callers can check isQueryResult() and use - * getQueryResult() themselves if they want the result wrapped. - * - * @throws Exception\RuntimeException When execution did not produce a result. - */ #[Override] - public function executeQuery(Driver\StatementInterface|string $sql): Driver\ResultInterface - { - $result = $sql instanceof Driver\StatementInterface - ? $sql->execute() - : $this->driver->getConnection()->execute($sql); - - if (! $result instanceof Driver\ResultInterface) { - throw new Exception\RuntimeException('Query execution did not produce a result'); - } - - return $result; - } - - /** - * Create statement - */ - #[Override] - public function createStatement( - ?string $initialSql = null, - ParameterContainer|array $initialParameters = [] - ): Driver\StatementInterface { - $statement = $this->driver->createStatement($initialSql); - if ( - is_array($initialParameters) - ) { - $initialParameters = new ParameterContainer($initialParameters); - } - $statement->setParameterContainer($initialParameters); - return $statement; - } - - /** - * {@inheritDoc} - */ - public function getHelpers() + public function setProfiler(Profiler\ProfilerInterface $profiler): Profiler\ProfilerAwareInterface { - $functions = []; - $platform = $this->platform; - foreach (func_get_args() as $arg) { - switch ($arg) { - case self::FUNCTION_QUOTE_IDENTIFIER: - $functions[] = function ($value) use ($platform) { - return $platform->quoteIdentifier($value); - }; - break; - case self::FUNCTION_QUOTE_VALUE: - $functions[] = function ($value) use ($platform) { - return $platform->quoteValue($value); - }; - break; - } + $this->profiler = $profiler; + if ($this->driver instanceof Profiler\ProfilerAwareInterface) { + $this->driver->setProfiler($profiler); } - return $functions; + return $this; } /** @throws Exception\InvalidArgumentException */ diff --git a/src/Adapter/AdapterInterface.php b/src/Adapter/AdapterInterface.php index 9e214ef36..acf9f7a54 100644 --- a/src/Adapter/AdapterInterface.php +++ b/src/Adapter/AdapterInterface.php @@ -30,31 +30,9 @@ interface AdapterInterface public const VALUE_QUOTE_SEPARATOR = 'quoteSeparator'; - public function getDriver(): Driver\DriverInterface; - - public function getPlatform(): Platform\PlatformInterface; - - public function getProfiler(): ?Profiler\ProfilerInterface; - - public function getQueryResultSetPrototype(): ResultSet\ResultSetInterface; - public function createStatement( ?string $initialSql = null, - ParameterContainer|array $initialParameters = [] - ): Driver\StatementInterface; - - public function query( - string $sql, - ParameterContainer|array|string $parametersOrQueryMode = self::QUERY_MODE_PREPARE, - ?ResultSet\ResultSetInterface $resultPrototype = null - ): Driver\StatementInterface|ResultSet\ResultSetInterface|Driver\ResultInterface; - - /** - * Prepares a statement for the given SQL without executing it. - */ - public function prepareQuery( - string $sql, - ParameterContainer|array $parameters = [] + ParameterContainer|array $initialParameters = [], ): Driver\StatementInterface; /** @@ -64,9 +42,31 @@ public function prepareQuery( */ public function executeQuery(Driver\StatementInterface|string $sql): Driver\ResultInterface; + public function getDriver(): Driver\DriverInterface; + /** * @todo 0.3.x track down this usage!!! * @return array */ public function getHelpers(); + + public function getPlatform(): Platform\PlatformInterface; + + public function getProfiler(): ?Profiler\ProfilerInterface; + + public function getQueryResultSetPrototype(): ResultSet\ResultSetInterface; + + /** + * Prepares a statement for the given SQL without executing it. + */ + public function prepareQuery( + string $sql, + ParameterContainer|array $parameters = [], + ): Driver\StatementInterface; + + public function query( + string $sql, + ParameterContainer|array|string $parametersOrQueryMode = self::QUERY_MODE_PREPARE, + ?ResultSet\ResultSetInterface $resultPrototype = null, + ): Driver\StatementInterface|ResultSet\ResultSetInterface|Driver\ResultInterface; } diff --git a/src/Adapter/Driver/AbstractConnection.php b/src/Adapter/Driver/AbstractConnection.php index a42bc9f99..c76498a89 100644 --- a/src/Adapter/Driver/AbstractConnection.php +++ b/src/Adapter/Driver/AbstractConnection.php @@ -76,6 +76,7 @@ public function inTransaction(): bool return $this->inTransaction; } + #[Override] public function setConnectionParameters(array $connectionParameters): ConnectionInterface { $this->connectionParameters = $connectionParameters; diff --git a/src/Adapter/Driver/ConnectionInterface.php b/src/Adapter/Driver/ConnectionInterface.php index 16c3b468f..77ca43801 100644 --- a/src/Adapter/Driver/ConnectionInterface.php +++ b/src/Adapter/Driver/ConnectionInterface.php @@ -8,13 +8,13 @@ interface ConnectionInterface extends SchemaAwareInterface { - public function beginTransaction(): ConnectionInterface; + public function beginTransaction(): self; - public function connect(): ConnectionInterface; + public function commit(): self; - public function commit(): ConnectionInterface; + public function connect(): self; - public function disconnect(): ConnectionInterface; + public function disconnect(): self; public function execute(string $sql): ?ResultInterface; @@ -39,7 +39,7 @@ public function inTransaction(): bool; public function isConnected(): bool; - public function rollback(): ConnectionInterface; + public function rollback(): self; - public function setConnectionParameters(array $connectionParameters): ConnectionInterface; + public function setConnectionParameters(array $connectionParameters): self; } diff --git a/src/Adapter/Driver/DriverAwareInterface.php b/src/Adapter/Driver/DriverAwareInterface.php index 7ed39ea49..2fc7a6f20 100644 --- a/src/Adapter/Driver/DriverAwareInterface.php +++ b/src/Adapter/Driver/DriverAwareInterface.php @@ -7,5 +7,5 @@ interface DriverAwareInterface { /** Implementation should provide a fluent interface */ - public function setDriver(DriverInterface $driver): DriverAwareInterface; + public function setDriver(DriverInterface $driver): self; } diff --git a/src/Adapter/Driver/DriverInterface.php b/src/Adapter/Driver/DriverInterface.php index 6cbb2e407..eb3fd59e3 100644 --- a/src/Adapter/Driver/DriverInterface.php +++ b/src/Adapter/Driver/DriverInterface.php @@ -18,16 +18,6 @@ interface DriverInterface */ public function checkEnvironment(): bool; - /** Get connection */ - public function getConnection(): ConnectionInterface; - - /** - * Create statement - * - * @param resource|string $sqlOrResource - */ - public function createStatement($sqlOrResource = null): StatementInterface; - /** * Create result * @@ -35,12 +25,22 @@ public function createStatement($sqlOrResource = null): StatementInterface; */ public function createResult($resource): ResultInterface; - /** Get prepare type */ - public function getPrepareType(): string; + /** + * Create statement + * + * @param resource|string $sqlOrResource + */ + public function createStatement($sqlOrResource = null): StatementInterface; /** Format parameter name */ public function formatParameterName(string $name, ?string $type = null): string; + /** Get connection */ + public function getConnection(): ConnectionInterface; + /** Get last generated value */ public function getLastGeneratedValue(): string|int|false|null; + + /** Get prepare type */ + public function getPrepareType(): string; } diff --git a/src/Adapter/Driver/Feature/DriverFeatureInterface.php b/src/Adapter/Driver/Feature/DriverFeatureInterface.php index 3d38c6471..216aa07d9 100644 --- a/src/Adapter/Driver/Feature/DriverFeatureInterface.php +++ b/src/Adapter/Driver/Feature/DriverFeatureInterface.php @@ -6,6 +6,4 @@ use PhpDb\Adapter\Driver\DriverAwareInterface; -interface DriverFeatureInterface extends DriverAwareInterface -{ -} +interface DriverFeatureInterface extends DriverAwareInterface {} diff --git a/src/Adapter/Driver/Feature/DriverFeatureProviderInterface.php b/src/Adapter/Driver/Feature/DriverFeatureProviderInterface.php index 49fc8cde2..f03c2bdd9 100644 --- a/src/Adapter/Driver/Feature/DriverFeatureProviderInterface.php +++ b/src/Adapter/Driver/Feature/DriverFeatureProviderInterface.php @@ -11,10 +11,10 @@ */ interface DriverFeatureProviderInterface { - /** @param DriverFeatureInterface[] $features */ - public function addFeatures(array $features): DriverFeatureProviderInterface; + public function addFeature(DriverFeatureInterface $feature): self; - public function addFeature(DriverFeatureInterface $feature): DriverFeatureProviderInterface; + /** @param DriverFeatureInterface[] $features */ + public function addFeatures(array $features): self; /** Get feature by class FQCN. */ public function getFeature(string $name): DriverFeatureInterface|false; diff --git a/src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php b/src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php index 62a56cbfc..d7e77a275 100644 --- a/src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php +++ b/src/Adapter/Driver/Feature/DriverFeatureProviderTrait.php @@ -30,7 +30,7 @@ public function addFeature(DriverFeatureInterface $feature): DriverFeatureProvid throw new RuntimeException(sprintf( '%s can only be composed into %s', __TRAIT__, - DriverInterface::class + DriverInterface::class, )); } diff --git a/src/Adapter/Driver/Pdo/AbstractPdo.php b/src/Adapter/Driver/Pdo/AbstractPdo.php index b4d13055d..925fff9a2 100644 --- a/src/Adapter/Driver/Pdo/AbstractPdo.php +++ b/src/Adapter/Driver/Pdo/AbstractPdo.php @@ -36,24 +36,6 @@ abstract class AbstractPdo implements PdoDriverInterface, ProfilerAwareInterface /** @internal */ protected ?ProfilerInterface $profiler; - #[Override] - public function setProfiler(ProfilerInterface $profiler): ProfilerAwareInterface - { - $this->profiler = $profiler; - if ($this->connection instanceof ProfilerAwareInterface) { - $this->connection->setProfiler($profiler); - } - if ($this->statementPrototype instanceof ProfilerAwareInterface) { - $this->statementPrototype->setProfiler($profiler); - } - return $this; - } - - public function getProfiler(): ?ProfilerInterface - { - return $this->profiler; - } - /** * Check environment */ @@ -63,19 +45,14 @@ public function checkEnvironment(): bool if (! extension_loaded('PDO')) { // @codeCoverageIgnoreStart throw new Exception\RuntimeException( - 'The PDO extension is required for this adapter but the extension is not loaded' + 'The PDO extension is required for this adapter but the extension is not loaded', ); + // @codeCoverageIgnoreEnd } return true; } - #[Override] - public function getConnection(): PdoConnectionInterface - { - return $this->connection; - } - /** * todo: this needs improved * @@ -102,32 +79,15 @@ public function createStatement($sqlOrResource = null): StatementInterface return $statement; } - /** - * {@inheritDoc} - */ - public function getResultPrototype(): ?ResultInterface - { - return $this->resultPrototype; - } - - /** - * {@inheritDoc} - */ - #[Override] - public function getPrepareType(): string - { - return self::PARAMETERIZATION_NAMED; - } - /** * {@inheritDoc} */ #[Override] public function formatParameterName(string|int $name, ?string $type = null): string { - if ($type === null && ! is_numeric($name) || $type === self::PARAMETERIZATION_NAMED) { + if (null === $type && ! is_numeric($name) || self::PARAMETERIZATION_NAMED === $type) { // proposed fix for passing $name as int with type self::PARAMETERIZATION_NAMED - if (is_int($name) && $type === self::PARAMETERIZATION_NAMED) { + if (is_int($name) && self::PARAMETERIZATION_NAMED === $type) { $name = (string) $name; } // end proposed fix @@ -136,17 +96,23 @@ public function formatParameterName(string|int $name, ?string $type = null): str if (preg_match('/[^a-zA-Z0-9_]/', $name)) { throw new Exception\RuntimeException(sprintf( 'The PDO param %s contains invalid characters.' - . ' Only alphabetic characters, digits, and underscores (_)' - . ' are allowed.', - $name + . ' Only alphabetic characters, digits, and underscores (_)' + . ' are allowed.', + $name, )); } - return ':' . $name; + return ":{$name}"; } return '?'; } + #[Override] + public function getConnection(): PdoConnectionInterface + { + return $this->connection; + } + /** * {@inheritDoc} */ @@ -155,4 +121,39 @@ public function getLastGeneratedValue(?string $name = null): string|int|false|nu { return $this->connection->getLastGeneratedValue($name); } + + /** + * {@inheritDoc} + */ + #[Override] + public function getPrepareType(): string + { + return self::PARAMETERIZATION_NAMED; + } + + public function getProfiler(): ?ProfilerInterface + { + return $this->profiler; + } + + /** + * {@inheritDoc} + */ + public function getResultPrototype(): ?ResultInterface + { + return $this->resultPrototype; + } + + #[Override] + public function setProfiler(ProfilerInterface $profiler): ProfilerAwareInterface + { + $this->profiler = $profiler; + if ($this->connection instanceof ProfilerAwareInterface) { + $this->connection->setProfiler($profiler); + } + if ($this->statementPrototype instanceof ProfilerAwareInterface) { + $this->statementPrototype->setProfiler($profiler); + } + return $this; + } } diff --git a/src/Adapter/Driver/Pdo/AbstractPdoConnection.php b/src/Adapter/Driver/Pdo/AbstractPdoConnection.php index d292a83ad..42fde3230 100644 --- a/src/Adapter/Driver/Pdo/AbstractPdoConnection.php +++ b/src/Adapter/Driver/Pdo/AbstractPdoConnection.php @@ -29,54 +29,6 @@ abstract class AbstractPdoConnection extends AbstractConnection implements /** @var ?PDO $resource */ protected $resource; - #[Override] - public function setDriver(PdoDriverInterface $driver): PdoDriverAwareInterface - { - $this->driver = $driver; - - return $this; - } - - #[Override] - public function setConnectionParameters(array $connectionParameters): ConnectionInterface - { - $this->connectionParameters = $connectionParameters; - - return $this; - } - - /** - * Get the dsn string for this connection - * - * @throws RuntimeException - */ - #[Override] - final public function getDsn(): string - { - if (! $this->dsn) { - throw new Exception\RuntimeException( - 'The DSN has not been set or constructed from parameters in connect() for this Connection' - ); - } - - return $this->dsn; - } - - public function setResource(PDO $resource): PdoConnectionInterface - { - $this->resource = $resource; - $this->driverName = strtolower($this->resource->getAttribute(PDO::ATTR_DRIVER_NAME)); - - return $this; - } - - /** {@inheritDoc} */ - #[Override] - public function isConnected(): bool - { - return $this->resource instanceof PDO; - } - /** {@inheritDoc} */ #[Override] public function beginTransaction(): ConnectionInterface @@ -119,30 +71,6 @@ public function commit(): ConnectionInterface return $this; } - /** - * {@inheritDoc} - * - * @throws Exception\RuntimeException - */ - #[Override] - public function rollback(): ConnectionInterface - { - if (! $this->isConnected()) { - throw new Exception\RuntimeException('Must be connected before you can rollback'); - } - - if (! $this->inTransaction()) { - throw new Exception\RuntimeException('Must call beginTransaction() before you can rollback'); - } - - $this->resource->rollBack(); - - $this->inTransaction = false; - $this->nestedTransactionsCount = 0; - - return $this; - } - /** * {@inheritDoc} * @@ -161,7 +89,7 @@ public function execute($sql): ?ResultInterface $this->profiler?->profilerFinish(); - if ($resultResource === false) { + if (false === $resultResource) { $errorInfo = $this->resource->errorInfo(); throw new Exception\InvalidQueryException($errorInfo[2]); } @@ -170,6 +98,30 @@ public function execute($sql): ?ResultInterface return $this->driver->createResult($resultResource, $sql); } + /** + * Get the dsn string for this connection + * + * @throws RuntimeException + */ + #[Override] + final public function getDsn(): string + { + if (! $this->dsn) { + throw new Exception\RuntimeException( + 'The DSN has not been set or constructed from parameters in connect() for this Connection', + ); + } + + return $this->dsn; + } + + /** {@inheritDoc} */ + #[Override] + public function isConnected(): bool + { + return $this->resource instanceof PDO; + } + /** Prepare a statement */ public function prepare(?string $sql = null): StatementInterface { @@ -179,4 +131,52 @@ public function prepare(?string $sql = null): StatementInterface return $this->driver->createStatement($sql); } + + /** + * {@inheritDoc} + * + * @throws Exception\RuntimeException + */ + #[Override] + public function rollback(): ConnectionInterface + { + if (! $this->isConnected()) { + throw new Exception\RuntimeException('Must be connected before you can rollback'); + } + + if (! $this->inTransaction()) { + throw new Exception\RuntimeException('Must call beginTransaction() before you can rollback'); + } + + $this->resource->rollBack(); + + $this->inTransaction = false; + $this->nestedTransactionsCount = 0; + + return $this; + } + + #[Override] + public function setConnectionParameters(array $connectionParameters): ConnectionInterface + { + $this->connectionParameters = $connectionParameters; + + return $this; + } + + #[Override] + public function setDriver(PdoDriverInterface $driver): PdoDriverAwareInterface + { + $this->driver = $driver; + + return $this; + } + + public function setResource(PDO $resource): PdoConnectionInterface + { + $this->resource = $resource; + $this->driverName = strtolower($this->resource->getAttribute(PDO::ATTR_DRIVER_NAME)); + + return $this; + } } diff --git a/src/Adapter/Driver/Pdo/Result.php b/src/Adapter/Driver/Pdo/Result.php index 40875cd25..b87fb0de1 100644 --- a/src/Adapter/Driver/Pdo/Result.php +++ b/src/Adapter/Driver/Pdo/Result.php @@ -29,11 +29,11 @@ class Result implements Iterator, ResultInterface /** @var int */ protected $fetchMode = PDO::FETCH_ASSOC; - /** - * @internal - * - * @var array - */ + /** + * @internal + * + * @var array + */ public const VALID_FETCH_MODES = [ PDO::FETCH_LAZY, // 1 PDO::FETCH_ASSOC, // 2 @@ -86,72 +86,98 @@ class Result implements Iterator, ResultInterface protected Closure|int|null $rowCount = null; /** - * Initialize - * - * @param string|int|false|null $generatedValue + * {@inheritdoc} */ - public function initialize( - PDOStatement $resource, - $generatedValue, - Closure|int|null $rowCount = null - ): ResultInterface&Result { - $this->resource = $resource; - $this->generatedValue = $generatedValue; - $this->rowCount = $rowCount; + #[Override] + public function buffer(): void {} - return $this; + /** + * Count + * + * @return int + */ + #[ReturnTypeWillChange] + #[Override] + public function count() + { + if (is_int($this->rowCount)) { + return $this->rowCount; + } + if ($this->rowCount instanceof Closure) { + $this->rowCount = (int) ($this->rowCount)(); + } else { + $this->rowCount = (int) $this->resource->rowCount(); + } + return $this->rowCount; } /** - * {@inheritdoc} + * Get the data + * + * @return mixed */ + #[ReturnTypeWillChange] #[Override] - public function buffer(): void + public function current() { + if ($this->currentComplete) { + return $this->currentData; + } + + $this->currentData = $this->resource->fetch($this->fetchMode); + $this->currentComplete = true; + return $this->currentData; } /** * {@inheritdoc} */ #[Override] - public function isBuffered(): bool + public function getAffectedRows(): int { - return false; + return $this->resource->rowCount(); + } + + public function getFetchMode(): int + { + return $this->fetchMode; } /** - * @throws Exception\InvalidArgumentException On invalid fetch mode. + * {@inheritdoc} */ - public function setFetchMode(int $fetchMode): void + #[Override] + public function getFieldCount(): int { - if (! in_array($fetchMode, self::VALID_FETCH_MODES, true)) { - throw new Exception\InvalidArgumentException( - 'The fetch mode must be one of the PDO::FETCH_* constants.' - ); - } - - $this->fetchMode = (int) $fetchMode; + return $this->resource->columnCount(); } - public function getFetchMode(): int + #[Override] + public function getGeneratedValue(): string|int|false|null { - return $this->fetchMode; + return $this->generatedValue; } - public function setStatementMode(string $statementMode = self::STATEMENT_MODE_FORWARD): void + /** + * {@inheritdoc} + * + * @throws Exception\RuntimeException + */ + #[Override] + public function getQueryResult(?ResultSetInterface $resultPrototype = null): ResultSetInterface { - if (! in_array($statementMode, [self::STATEMENT_MODE_SCROLLABLE, self::STATEMENT_MODE_FORWARD], true)) { - throw new Exception\InvalidArgumentException( - 'The statement mode must be one of the defined constants.' + if (! $this->isQueryResult()) { + throw new Exception\RuntimeException( + 'Cannot produce a query result set from a result that is not a query result;' + . ' check isQueryResult() first', ); } - $this->statementMode = $statementMode; - } + $resultPrototype ??= new ResultSet(); + $resultSet = clone $resultPrototype; + $resultSet->initialize($this); - public function getStatementMode(): string - { - return $this->statementMode; + return $resultSet; } /** @@ -163,37 +189,44 @@ public function getResource(): mixed return $this->resource; } + public function getStatementMode(): string + { + return $this->statementMode; + } + /** - * Get the data + * Initialize * - * @return mixed + * @param string|int|false|null $generatedValue + */ + public function initialize( + PDOStatement $resource, + $generatedValue, + Closure|int|null $rowCount = null, + ): self { + $this->resource = $resource; + $this->generatedValue = $generatedValue; + $this->rowCount = $rowCount; + + return $this; + } + + /** + * {@inheritdoc} */ - #[ReturnTypeWillChange] #[Override] - public function current() + public function isBuffered(): bool { - if ($this->currentComplete) { - return $this->currentData; - } - - $this->currentData = $this->resource->fetch($this->fetchMode); - $this->currentComplete = true; - return $this->currentData; + return false; } /** - * Next - * - * @return mixed + * {@inheritdoc} */ - #[ReturnTypeWillChange] #[Override] - public function next() + public function isQueryResult(): bool { - $this->currentData = $this->resource->fetch($this->fetchMode); - $this->currentComplete = true; - $this->position++; - return $this->currentData; + return $this->resource->columnCount() > 0; } /** @@ -208,6 +241,21 @@ public function key() return $this->position; } + /** + * Next + * + * @return mixed + */ + #[ReturnTypeWillChange] + #[Override] + public function next() + { + $this->currentData = $this->resource->fetch($this->fetchMode); + $this->currentComplete = true; + $this->position++; + return $this->currentData; + } + /** * @throws Exception\RuntimeException * @return void @@ -216,9 +264,9 @@ public function key() #[Override] public function rewind() { - if ($this->statementMode === self::STATEMENT_MODE_FORWARD && $this->position > 0) { + if (self::STATEMENT_MODE_FORWARD === $this->statementMode && $this->position > 0) { throw new Exception\RuntimeException( - 'This result is a forward only result set, calling rewind() after moving forward is not supported' + 'This result is a forward only result set, calling rewind() after moving forward is not supported', ); } if (! $this->currentComplete) { @@ -229,89 +277,39 @@ public function rewind() } /** - * Valid - * - * @return bool - */ - #[ReturnTypeWillChange] - #[Override] - public function valid() - { - return $this->currentData !== false; - } - - /** - * Count - * - * @return int + * @throws Exception\InvalidArgumentException On invalid fetch mode. */ - #[ReturnTypeWillChange] - #[Override] - public function count() + public function setFetchMode(int $fetchMode): void { - if (is_int($this->rowCount)) { - return $this->rowCount; - } - if ($this->rowCount instanceof Closure) { - $this->rowCount = (int) ($this->rowCount)(); - } else { - $this->rowCount = (int) $this->resource->rowCount(); + if (! in_array($fetchMode, self::VALID_FETCH_MODES, true)) { + throw new Exception\InvalidArgumentException( + 'The fetch mode must be one of the PDO::FETCH_* constants.', + ); } - return $this->rowCount; - } - /** - * {@inheritdoc} - */ - #[Override] - public function getFieldCount(): int - { - return $this->resource->columnCount(); - } - - /** - * {@inheritdoc} - */ - #[Override] - public function isQueryResult(): bool - { - return $this->resource->columnCount() > 0; + $this->fetchMode = (int) $fetchMode; } - /** - * {@inheritdoc} - * - * @throws Exception\RuntimeException - */ - #[Override] - public function getQueryResult(?ResultSetInterface $resultPrototype = null): ResultSetInterface + public function setStatementMode(string $statementMode = self::STATEMENT_MODE_FORWARD): void { - if (! $this->isQueryResult()) { - throw new Exception\RuntimeException( - 'Cannot produce a query result set from a result that is not a query result;' - . ' check isQueryResult() first' + if (! in_array($statementMode, [self::STATEMENT_MODE_SCROLLABLE, self::STATEMENT_MODE_FORWARD], true)) { + throw new Exception\InvalidArgumentException( + 'The statement mode must be one of the defined constants.', ); } - $resultPrototype ??= new ResultSet(); - $resultSet = clone $resultPrototype; - $resultSet->initialize($this); - - return $resultSet; + $this->statementMode = $statementMode; } /** - * {@inheritdoc} + * Valid + * + * @return bool */ + #[ReturnTypeWillChange] #[Override] - public function getAffectedRows(): int - { - return $this->resource->rowCount(); - } - - #[Override] - public function getGeneratedValue(): string|int|false|null + public function valid() { - return $this->generatedValue; + return false !== $this->currentData; } } diff --git a/src/Adapter/Driver/Pdo/Statement.php b/src/Adapter/Driver/Pdo/Statement.php index cb0de27de..dea6152db 100644 --- a/src/Adapter/Driver/Pdo/Statement.php +++ b/src/Adapter/Driver/Pdo/Statement.php @@ -46,21 +46,59 @@ class Statement implements StatementInterface, PdoDriverAwareInterface, Profiler public function __construct( protected ParameterContainer $parameterContainer = new ParameterContainer(), protected array $options = [], - ) { - } + ) {} + /** @throws Exception\InvalidQueryException */ #[Override] - public function setDriver(PdoDriverInterface $driver): PdoDriverAwareInterface + public function execute(array|ParameterContainer|null $parameters = null): ?ResultInterface { - $this->driver = $driver; - return $this; + if (! $this->isPrepared) { + $this->prepare(); + } + + /** START Standard ParameterContainer Merging Block */ + if ($parameters instanceof ParameterContainer) { + $this->parameterContainer = $parameters; + $parameters = null; + } + + if (is_array($parameters)) { + $this->parameterContainer->setFromArray($parameters); + } + + if ($this->parameterContainer->count() > 0) { + $this->bindParametersFromContainer(); + } + /** END Standard ParameterContainer Merging Block */ + + $this->profiler?->profilerStart($this); + + try { + $this->resource->execute(); + } catch (PDOException $e) { + $this->profiler?->profilerFinish(); + + $code = $e->getCode(); + if (! is_int($code)) { + $code = 0; + } + + throw new Exception\InvalidQueryException( + 'Statement could not be executed (' . implode(' - ', $this->resource->errorInfo()) . ')', + $code, + $e, + ); + } + + $this->profiler?->profilerFinish(); + + return $this->driver->createResult($this->resource); } #[Override] - public function setProfiler(ProfilerInterface $profiler): ProfilerAwareInterface + public function getParameterContainer(): ParameterContainer { - $this->profiler = $profiler; - return $this; + return $this->parameterContainer; } public function getProfiler(): ?ProfilerInterface @@ -68,20 +106,6 @@ public function getProfiler(): ?ProfilerInterface return $this->profiler; } - /** Initialize */ - public function initialize(PDO $connectionResource): StatementInterface - { - $this->pdo = $connectionResource; - return $this; - } - - /** Set resource */ - public function setResource(PDOStatement $pdoStatement): StatementInterface - { - $this->resource = $pdoStatement; - return $this; - } - /** Get resource */ #[Override] public function getResource(): PDOStatement|false|null @@ -89,14 +113,6 @@ public function getResource(): PDOStatement|false|null return $this->resource; } - /** Set sql */ - #[Override] - public function setSql(?string $sql): StatementContainerInterface - { - $this->sql = $sql; - return $this; - } - /** Get sql */ #[Override] public function getSql(): ?string @@ -104,17 +120,17 @@ public function getSql(): ?string return $this->sql; } - #[Override] - public function setParameterContainer(ParameterContainer $parameterContainer): StatementContainerInterface + /** Initialize */ + public function initialize(PDO $connectionResource): StatementInterface { - $this->parameterContainer = $parameterContainer; + $this->pdo = $connectionResource; return $this; } #[Override] - public function getParameterContainer(): ParameterContainer + public function isPrepared(): bool { - return $this->parameterContainer; + return $this->isPrepared; } /** @throws Exception\RuntimeException */ @@ -125,13 +141,13 @@ public function prepare(?string $sql = null): StatementInterface throw new Exception\RuntimeException('This statement has been prepared already'); } - if ($sql === null) { + if (null === $sql) { $sql = $this->sql; } $this->resource = $this->pdo->prepare($sql); - if ($this->resource === false) { + if (false === $this->resource) { $error = $this->pdo->errorInfo(); throw new Exception\RuntimeException($error[2]); } @@ -142,56 +158,39 @@ public function prepare(?string $sql = null): StatementInterface } #[Override] - public function isPrepared(): bool + public function setDriver(PdoDriverInterface $driver): PdoDriverAwareInterface { - return $this->isPrepared; + $this->driver = $driver; + return $this; } - /** @throws Exception\InvalidQueryException */ #[Override] - public function execute(null|array|ParameterContainer $parameters = null): ?ResultInterface + public function setParameterContainer(ParameterContainer $parameterContainer): StatementContainerInterface { - if (! $this->isPrepared) { - $this->prepare(); - } - - /** START Standard ParameterContainer Merging Block */ - if ($parameters instanceof ParameterContainer) { - $this->parameterContainer = $parameters; - $parameters = null; - } - - if (is_array($parameters)) { - $this->parameterContainer->setFromArray($parameters); - } - - if ($this->parameterContainer->count() > 0) { - $this->bindParametersFromContainer(); - } - /** END Standard ParameterContainer Merging Block */ - - $this->profiler?->profilerStart($this); - - try { - $this->resource->execute(); - } catch (PDOException $e) { - $this->profiler?->profilerFinish(); - - $code = $e->getCode(); - if (! is_int($code)) { - $code = 0; - } + $this->parameterContainer = $parameterContainer; + return $this; + } - throw new Exception\InvalidQueryException( - 'Statement could not be executed (' . implode(' - ', $this->resource->errorInfo()) . ')', - $code, - $e - ); - } + #[Override] + public function setProfiler(ProfilerInterface $profiler): ProfilerAwareInterface + { + $this->profiler = $profiler; + return $this; + } - $this->profiler?->profilerFinish(); + /** Set resource */ + public function setResource(PDOStatement $pdoStatement): StatementInterface + { + $this->resource = $pdoStatement; + return $this; + } - return $this->driver->createResult($this->resource); + /** Set sql */ + #[Override] + public function setSql(?string $sql): StatementContainerInterface + { + $this->sql = $sql; + return $this; } /** Bind parameters from container */ @@ -208,15 +207,15 @@ protected function bindParametersFromContainer(): void if (isset($errata[$name])) { $type = match ($errata[$name]) { ParameterContainer::TYPE_INTEGER => PDO::PARAM_INT, - ParameterContainer::TYPE_NULL => PDO::PARAM_NULL, - ParameterContainer::TYPE_LOB => PDO::PARAM_LOB, - default => PDO::PARAM_STR, + ParameterContainer::TYPE_NULL => PDO::PARAM_NULL, + ParameterContainer::TYPE_LOB => PDO::PARAM_LOB, + default => PDO::PARAM_STR, }; } else { $type = match (true) { is_int($value) => PDO::PARAM_INT, - $value === false, $value === true => PDO::PARAM_BOOL, - $value === null => PDO::PARAM_NULL, + false === $value, true === $value => PDO::PARAM_BOOL, + null === $value => PDO::PARAM_NULL, default => PDO::PARAM_STR, }; } @@ -227,8 +226,8 @@ protected function bindParametersFromContainer(): void if (! preg_match('/^:?[a-zA-Z0-9_]+$/', $name)) { throw new Exception\RuntimeException(sprintf( 'The PDO param "%s" contains invalid characters.' - . ' Only alphabetic characters, digits, and underscores (_) are allowed.', - $name + . ' Only alphabetic characters, digits, and underscores (_) are allowed.', + $name, )); } $parameter = ':' . ltrim($name, ':'); diff --git a/src/Adapter/Driver/PdoDriverAwareInterface.php b/src/Adapter/Driver/PdoDriverAwareInterface.php index 6bcfd62a7..636e26f8e 100644 --- a/src/Adapter/Driver/PdoDriverAwareInterface.php +++ b/src/Adapter/Driver/PdoDriverAwareInterface.php @@ -7,5 +7,5 @@ interface PdoDriverAwareInterface { /** Implementation should provide a fluent interface */ - public function setDriver(PdoDriverInterface $driver): PdoDriverAwareInterface; + public function setDriver(PdoDriverInterface $driver): self; } diff --git a/src/Adapter/Driver/PdoDriverInterface.php b/src/Adapter/Driver/PdoDriverInterface.php index 683f8ad2d..ef3326a48 100644 --- a/src/Adapter/Driver/PdoDriverInterface.php +++ b/src/Adapter/Driver/PdoDriverInterface.php @@ -4,6 +4,4 @@ namespace PhpDb\Adapter\Driver; -interface PdoDriverInterface extends DriverInterface -{ -} +interface PdoDriverInterface extends DriverInterface {} diff --git a/src/Adapter/Driver/ResultInterface.php b/src/Adapter/Driver/ResultInterface.php index bb826e4ac..75be8c068 100644 --- a/src/Adapter/Driver/ResultInterface.php +++ b/src/Adapter/Driver/ResultInterface.php @@ -9,9 +9,7 @@ use PhpDb\Adapter\Exception; use PhpDb\ResultSet\ResultSetInterface; -interface ResultInterface extends - Countable, - Iterator +interface ResultInterface extends Countable, Iterator { /** * Force buffering @@ -19,14 +17,19 @@ interface ResultInterface extends public function buffer(): void; /** - * Check if is buffered + * Get affected rows */ - public function isBuffered(): ?bool; + public function getAffectedRows(): int; /** - * Is query result? + * Get field count */ - public function isQueryResult(): bool; + public function getFieldCount(): int; + + /** + * Get generated value + */ + public function getGeneratedValue(): string|int|false|null; /** * Get the seeded query result set, cloned from $resultPrototype (or a @@ -37,22 +40,17 @@ public function isQueryResult(): bool; public function getQueryResult(?ResultSetInterface $resultPrototype = null): ResultSetInterface; /** - * Get affected rows - */ - public function getAffectedRows(): int; - - /** - * Get generated value + * Get the resource */ - public function getGeneratedValue(): string|int|false|null; + public function getResource(): mixed; /** - * Get the resource + * Check if is buffered */ - public function getResource(): mixed; + public function isBuffered(): ?bool; /** - * Get field count + * Is query result? */ - public function getFieldCount(): int; + public function isQueryResult(): bool; } diff --git a/src/Adapter/Driver/StatementInterface.php b/src/Adapter/Driver/StatementInterface.php index a56225f0b..7c44898b6 100644 --- a/src/Adapter/Driver/StatementInterface.php +++ b/src/Adapter/Driver/StatementInterface.php @@ -9,6 +9,9 @@ interface StatementInterface extends StatementContainerInterface { + /** Execute */ + public function execute(ParameterContainer|array|null $parameters = null): ?ResultInterface; + /** * Get resource * @@ -16,12 +19,9 @@ interface StatementInterface extends StatementContainerInterface */ public function getResource(); - /** Prepare sql */ - public function prepare(?string $sql = null): StatementInterface; - /** Check if is prepared */ public function isPrepared(): bool; - /** Execute */ - public function execute(ParameterContainer|array|null $parameters = null): ?ResultInterface; + /** Prepare sql */ + public function prepare(?string $sql = null): self; } diff --git a/src/Adapter/Exception/ErrorException.php b/src/Adapter/Exception/ErrorException.php index 08b914341..d2455bcfa 100644 --- a/src/Adapter/Exception/ErrorException.php +++ b/src/Adapter/Exception/ErrorException.php @@ -6,6 +6,4 @@ use PhpDb\Exception; -class ErrorException extends Exception\ErrorException implements ExceptionInterface -{ -} +class ErrorException extends Exception\ErrorException implements ExceptionInterface {} diff --git a/src/Adapter/Exception/ExceptionInterface.php b/src/Adapter/Exception/ExceptionInterface.php index 2e7597261..43ba25523 100644 --- a/src/Adapter/Exception/ExceptionInterface.php +++ b/src/Adapter/Exception/ExceptionInterface.php @@ -6,6 +6,4 @@ use PhpDb\Exception; -interface ExceptionInterface extends Exception\ExceptionInterface -{ -} +interface ExceptionInterface extends Exception\ExceptionInterface {} diff --git a/src/Adapter/Exception/InvalidArgumentException.php b/src/Adapter/Exception/InvalidArgumentException.php index 807769aa6..3d972a7de 100644 --- a/src/Adapter/Exception/InvalidArgumentException.php +++ b/src/Adapter/Exception/InvalidArgumentException.php @@ -4,6 +4,4 @@ use PhpDb\Exception; -class InvalidArgumentException extends Exception\InvalidArgumentException implements ExceptionInterface -{ -} +class InvalidArgumentException extends Exception\InvalidArgumentException implements ExceptionInterface {} diff --git a/src/Adapter/Exception/InvalidQueryException.php b/src/Adapter/Exception/InvalidQueryException.php index 7997b050e..813794ee1 100644 --- a/src/Adapter/Exception/InvalidQueryException.php +++ b/src/Adapter/Exception/InvalidQueryException.php @@ -4,6 +4,4 @@ namespace PhpDb\Adapter\Exception; -class InvalidQueryException extends UnexpectedValueException implements ExceptionInterface -{ -} +class InvalidQueryException extends UnexpectedValueException implements ExceptionInterface {} diff --git a/src/Adapter/Exception/RuntimeException.php b/src/Adapter/Exception/RuntimeException.php index 202a7a40b..085804fbe 100644 --- a/src/Adapter/Exception/RuntimeException.php +++ b/src/Adapter/Exception/RuntimeException.php @@ -6,6 +6,4 @@ use PhpDb\Exception; -class RuntimeException extends Exception\RuntimeException implements ExceptionInterface -{ -} +class RuntimeException extends Exception\RuntimeException implements ExceptionInterface {} diff --git a/src/Adapter/Exception/UnexpectedValueException.php b/src/Adapter/Exception/UnexpectedValueException.php index 970ee4807..d9b94053d 100644 --- a/src/Adapter/Exception/UnexpectedValueException.php +++ b/src/Adapter/Exception/UnexpectedValueException.php @@ -6,6 +6,4 @@ use PhpDb\Exception; -class UnexpectedValueException extends Exception\UnexpectedValueException implements ExceptionInterface -{ -} +class UnexpectedValueException extends Exception\UnexpectedValueException implements ExceptionInterface {} diff --git a/src/Adapter/Exception/VunerablePlatformQuoteException.php b/src/Adapter/Exception/VunerablePlatformQuoteException.php index 33f59968d..f51fd0bd9 100644 --- a/src/Adapter/Exception/VunerablePlatformQuoteException.php +++ b/src/Adapter/Exception/VunerablePlatformQuoteException.php @@ -15,7 +15,7 @@ public static function forPlatformAndMethod(string $platformName, string $method 'Attempting to quote in %s::%s without extension/driver support' . ' can introduce security vulnerabilities in a production environment.', $platformName, - $methodName + $methodName, ), ); } diff --git a/src/Adapter/ParameterContainer.php b/src/Adapter/ParameterContainer.php index b9eb53d3a..4ae7d690d 100644 --- a/src/Adapter/ParameterContainer.php +++ b/src/Adapter/ParameterContainer.php @@ -65,11 +65,91 @@ class ParameterContainer implements Iterator, ArrayAccess, Countable */ public function __construct(array $data = []) { - if ($data !== []) { + if ([] !== $data) { $this->setFromArray($data); } } + /** + * count + */ + #[Override] + #[ReturnTypeWillChange] + public function count(): int + { + return count($this->data); + } + + /** + * Current + */ + #[Override] + #[ReturnTypeWillChange] + public function current(): mixed + { + return current($this->data); + } + + /** + * Get errata iterator + * + * @return ArrayIterator + */ + public function getErrataIterator(): ArrayIterator + { + return new ArrayIterator($this->errata); + } + + /** + * Get max length iterator + * + * @return ArrayIterator + */ + public function getMaxLengthIterator(): ArrayIterator + { + return new ArrayIterator($this->maxLength); + } + + /** + * getNamedArray + * + * @return array + */ + public function getNamedArray(): array + { + return $this->data; + } + + /** + * getNamedArray + * + * @return array + */ + public function getPositionalArray(): array + { + return array_values($this->data); + } + + /** + * Key + */ + #[Override] + #[ReturnTypeWillChange] + public function key(): int|string|null + { + return key($this->data); + } + + /** + * Next + */ + #[Override] + #[ReturnTypeWillChange] + public function next(): void + { + next($this->data); + } + /** * Offset exists * @@ -97,8 +177,7 @@ public function offsetGet(mixed $name): mixed $normalizedName = ltrim($name, ':'); if ( - isset($this->nameMapping[$normalizedName]) - && isset($this->data[$this->nameMapping[$normalizedName]]) + isset($this->nameMapping[$normalizedName], $this->data[$this->nameMapping[$normalizedName]]) ) { return $this->data[$this->nameMapping[$normalizedName]]; } @@ -106,9 +185,58 @@ public function offsetGet(mixed $name): mixed return null; } - public function offsetSetReference(string|int $name, string|int $from): void + /** + * Offset get errata + * + * @throws Exception\InvalidArgumentException + */ + public function offsetGetErrata(string|int $name): mixed { - $this->data[$name] = &$this->data[$from]; + if (is_int($name)) { + $name = $this->positions[$name]; + } + if (! array_key_exists($name, $this->data)) { + throw new Exception\InvalidArgumentException('Data does not exist for this name/position'); + } + return $this->errata[$name]; + } + + /** + * Offset get max length + * + * @throws Exception\InvalidArgumentException + */ + public function offsetGetMaxLength(string|int $name): mixed + { + if (is_int($name)) { + $name = $this->positions[$name]; + } + if (! array_key_exists($name, $this->data)) { + throw new Exception\InvalidArgumentException('Data does not exist for this name/position'); + } + return $this->maxLength[$name]; + } + + /** + * Offset has errata + */ + public function offsetHasErrata(string|int $name): bool + { + if (is_int($name)) { + $name = $this->positions[$name]; + } + return isset($this->errata[$name]); + } + + /** + * Offset has max length + */ + public function offsetHasMaxLength(string|int $name): bool + { + if (is_int($name)) { + $name = $this->positions[$name]; + } + return isset($this->maxLength[$name]); } /** @@ -131,7 +259,7 @@ public function offsetSet(mixed $name, mixed $value, mixed $errata = null, mixed $name = (string) $name; } } elseif (is_string($name)) { - if ($name[0] === ':') { + if (':' === $name[0]) { $normalizedName = substr($name, 1); if (isset($this->nameMapping[$normalizedName])) { $name = $this->nameMapping[$normalizedName]; @@ -142,10 +270,10 @@ public function offsetSet(mixed $name, mixed $value, mixed $errata = null, mixed $isNewPosition = ! isset($this->data[$name]); - if (is_string($value) && isset($value[0]) && $value[0] === ':') { + if (is_string($value) && isset($value[0]) && ':' === $value[0]) { $this->nameMapping[substr($value, 1)] = $name; } - } elseif ($name === null) { + } elseif (null === $name) { $name = (string) count($this->data); } else { throw new Exception\InvalidArgumentException('Keys must be string, integer or null'); @@ -157,37 +285,24 @@ public function offsetSet(mixed $name, mixed $value, mixed $errata = null, mixed $this->data[$name] = $value; - if ($errata !== null) { + if (null !== $errata) { $this->errata[$name] = $errata; } - if ($maxLength !== null) { + if (null !== $maxLength) { $this->maxLength[$name] = $maxLength; } } /** - * Offset unset + * Offset set errata */ - #[Override] - #[ReturnTypeWillChange] - public function offsetUnset(mixed $name): void + public function offsetSetErrata(string|int $name, mixed $errata): void { - if (is_int($name) && isset($this->positions[$name])) { + if (is_int($name)) { $name = $this->positions[$name]; } - unset($this->data[$name]); - } - - /** - * Set from array - */ - public function setFromArray(array $data): static - { - foreach ($data as $n => $v) { - $this->offsetSet($n, $v); - } - return $this; + $this->errata[$name] = $errata; } /** @@ -201,181 +316,75 @@ public function offsetSetMaxLength(string|int $name, mixed $maxLength): void $this->maxLength[$name] = $maxLength; } - /** - * Offset get max length - * - * @throws Exception\InvalidArgumentException - */ - public function offsetGetMaxLength(string|int $name): mixed - { - if (is_int($name)) { - $name = $this->positions[$name]; - } - if (! array_key_exists($name, $this->data)) { - throw new Exception\InvalidArgumentException('Data does not exist for this name/position'); - } - return $this->maxLength[$name]; - } - - /** - * Offset has max length - */ - public function offsetHasMaxLength(string|int $name): bool - { - if (is_int($name)) { - $name = $this->positions[$name]; - } - return isset($this->maxLength[$name]); - } - - /** - * Offset unset max length - * - * @throws Exception\InvalidArgumentException - */ - public function offsetUnsetMaxLength(string|int $name): void - { - if (is_int($name)) { - $name = $this->positions[$name]; - } - if (! array_key_exists($name, $this->maxLength)) { - throw new Exception\InvalidArgumentException('Data does not exist for this name/position'); - } - $this->maxLength[$name] = null; - } - - /** - * Get max length iterator - * - * @return ArrayIterator - */ - public function getMaxLengthIterator(): ArrayIterator + public function offsetSetReference(string|int $name, string|int $from): void { - return new ArrayIterator($this->maxLength); + $this->data[$name] = &$this->data[$from]; } /** - * Offset set errata + * Offset unset */ - public function offsetSetErrata(string|int $name, mixed $errata): void + #[Override] + #[ReturnTypeWillChange] + public function offsetUnset(mixed $name): void { - if (is_int($name)) { + if (is_int($name) && isset($this->positions[$name])) { $name = $this->positions[$name]; } - $this->errata[$name] = $errata; + unset($this->data[$name]); } /** - * Offset get errata + * Offset unset errata * * @throws Exception\InvalidArgumentException */ - public function offsetGetErrata(string|int $name): mixed + public function offsetUnsetErrata(string|int $name): void { if (is_int($name)) { $name = $this->positions[$name]; } - if (! array_key_exists($name, $this->data)) { + if (! array_key_exists($name, $this->errata)) { throw new Exception\InvalidArgumentException('Data does not exist for this name/position'); } - return $this->errata[$name]; - } - - /** - * Offset has errata - */ - public function offsetHasErrata(string|int $name): bool - { - if (is_int($name)) { - $name = $this->positions[$name]; - } - return isset($this->errata[$name]); + $this->errata[$name] = null; } /** - * Offset unset errata + * Offset unset max length * * @throws Exception\InvalidArgumentException */ - public function offsetUnsetErrata(string|int $name): void + public function offsetUnsetMaxLength(string|int $name): void { if (is_int($name)) { $name = $this->positions[$name]; } - if (! array_key_exists($name, $this->errata)) { + if (! array_key_exists($name, $this->maxLength)) { throw new Exception\InvalidArgumentException('Data does not exist for this name/position'); } - $this->errata[$name] = null; - } - - /** - * Get errata iterator - * - * @return ArrayIterator - */ - public function getErrataIterator(): ArrayIterator - { - return new ArrayIterator($this->errata); - } - - /** - * getNamedArray - * - * @return array - */ - public function getNamedArray(): array - { - return $this->data; - } - - /** - * getNamedArray - * - * @return array - */ - public function getPositionalArray(): array - { - return array_values($this->data); - } - - /** - * count - */ - #[Override] - #[ReturnTypeWillChange] - public function count(): int - { - return count($this->data); - } - - /** - * Current - */ - #[Override] - #[ReturnTypeWillChange] - public function current(): mixed - { - return current($this->data); + $this->maxLength[$name] = null; } /** - * Next + * Rewind */ #[Override] #[ReturnTypeWillChange] - public function next(): void + public function rewind(): void { - next($this->data); + reset($this->data); } /** - * Key + * Set from array */ - #[Override] - #[ReturnTypeWillChange] - public function key(): int|string|null + public function setFromArray(array $data): static { - return key($this->data); + foreach ($data as $n => $v) { + $this->offsetSet($n, $v); + } + return $this; } /** @@ -387,14 +396,4 @@ public function valid(): bool { return current($this->data) !== false; } - - /** - * Rewind - */ - #[Override] - #[ReturnTypeWillChange] - public function rewind(): void - { - reset($this->data); - } } diff --git a/src/Adapter/Platform/AbstractPlatform.php b/src/Adapter/Platform/AbstractPlatform.php index ef695d551..d21592164 100644 --- a/src/Adapter/Platform/AbstractPlatform.php +++ b/src/Adapter/Platform/AbstractPlatform.php @@ -36,6 +36,59 @@ abstract class AbstractPlatform implements PlatformInterface /** @var array */ private const SAFE_WORDS = ['*' => true, ' ' => true, '.' => true, 'as' => true]; + /** + * {@inheritDoc} + */ + #[Override] + public function getIdentifierSeparator(): string + { + return '.'; + } + + /** + * {@inheritDoc} + */ + #[Override] + public function getQuoteIdentifierSymbol(): string + { + return $this->quoteIdentifier[0]; + } + + /** + * {@inheritDoc} + */ + #[Override] + public function getQuoteValueSymbol(): string + { + return '\''; + } + + /** + * {@inheritDoc} + */ + #[Override] + public function quoteIdentifier(string $identifier): string + { + if (! $this->quoteIdentifiers) { + return $identifier; + } + + return ( + $this->quoteIdentifier[0] + . str_replace($this->quoteIdentifier[0], $this->quoteIdentifierTo, $identifier) + . $this->quoteIdentifier[1] + ); + } + + /** + * {@inheritDoc} + */ + #[Override] + public function quoteIdentifierChain(array|string $identifierChain): string + { + return '"' . implode('"."', (array) str_replace('"', '\\"', $identifierChain)) . '"'; + } + /** * {@inheritDoc} */ @@ -55,7 +108,7 @@ public function quoteIdentifierInFragment(string $identifier, array $additionalS $this->quoteIdentifierFragmentPattern, $identifier, -1, - PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY + PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY, ); $quoteStart = $this->quoteIdentifier[0]; @@ -79,42 +132,9 @@ public function quoteIdentifierInFragment(string $identifier, array $additionalS * {@inheritDoc} */ #[Override] - public function quoteIdentifier(string $identifier): string - { - if (! $this->quoteIdentifiers) { - return $identifier; - } - - return $this->quoteIdentifier[0] - . str_replace($this->quoteIdentifier[0], $this->quoteIdentifierTo, $identifier) - . $this->quoteIdentifier[1]; - } - - /** - * {@inheritDoc} - */ - #[Override] - public function quoteIdentifierChain(array|string $identifierChain): string - { - return '"' . implode('"."', (array) str_replace('"', '\\"', $identifierChain)) . '"'; - } - - /** - * {@inheritDoc} - */ - #[Override] - public function getQuoteIdentifierSymbol(): string - { - return $this->quoteIdentifier[0]; - } - - /** - * {@inheritDoc} - */ - #[Override] - public function getQuoteValueSymbol(): string + public function quoteTrustedValue(int|float|string|bool $value): ?string { - return '\''; + return '\'' . addcslashes((string) $value, "\x00\n\r\\'\"\x1a") . '\''; } /** @@ -126,21 +146,12 @@ public function quoteValue(string $value): string if (! isset($this->driver)) { throw VunerablePlatformQuoteException::forPlatformAndMethod( static::class, - __METHOD__ + __METHOD__, ); } return '\'' . addcslashes($value, "\x00\n\r\\'\"\x1a") . '\''; } - /** - * {@inheritDoc} - */ - #[Override] - public function quoteTrustedValue(int|float|string|bool $value): ?string - { - return '\'' . addcslashes((string) $value, "\x00\n\r\\'\"\x1a") . '\''; - } - /** * {@inheritDoc} */ @@ -149,13 +160,4 @@ public function quoteValueList(array|string $valueList): string { return implode(', ', array_map([$this, 'quoteValue'], (array) $valueList)); } - - /** - * {@inheritDoc} - */ - #[Override] - public function getIdentifierSeparator(): string - { - return '.'; - } } diff --git a/src/Adapter/Platform/PlatformInterface.php b/src/Adapter/Platform/PlatformInterface.php index 60c2f27fd..4a11ba25f 100644 --- a/src/Adapter/Platform/PlatformInterface.php +++ b/src/Adapter/Platform/PlatformInterface.php @@ -8,11 +8,26 @@ interface PlatformInterface { + /** + * Get identifier separator + */ + public function getIdentifierSeparator(): string; + /** * Get name */ public function getName(): string; + /** + * Get quote identifier symbol + */ + public function getQuoteIdentifierSymbol(): string; + + /** + * Get quote value symbol + */ + public function getQuoteValueSymbol(): string; + /** * Get Sql platform decorator * @@ -21,11 +36,6 @@ public function getName(): string; */ public function getSqlPlatformDecorator(): PlatformDecoratorInterface; - /** - * Get quote identifier symbol - */ - public function getQuoteIdentifierSymbol(): string; - /** * Quote identifier */ @@ -39,23 +49,23 @@ public function quoteIdentifier(string $identifier): string; public function quoteIdentifierChain(array|string $identifierChain): string; /** - * Get quote value symbol + * Quote identifier in fragment */ - public function getQuoteValueSymbol(): string; + public function quoteIdentifierInFragment(string $identifier, array $additionalSafeWords = []): string; /** - * Quote value + * Quote Trusted Value * - * Will throw a notice when used in a workflow that can be considered "unsafe" + * The ability to quote values without notices */ - public function quoteValue(string $value): string; + public function quoteTrustedValue(int|float|string|bool $value): ?string; /** - * Quote Trusted Value + * Quote value * - * The ability to quote values without notices + * Will throw a notice when used in a workflow that can be considered "unsafe" */ - public function quoteTrustedValue(int|float|string|bool $value): ?string; + public function quoteValue(string $value): string; /** * Quote value list @@ -63,14 +73,4 @@ public function quoteTrustedValue(int|float|string|bool $value): ?string; * @param string|string[] $valueList */ public function quoteValueList(array|string $valueList): string; - - /** - * Get identifier separator - */ - public function getIdentifierSeparator(): string; - - /** - * Quote identifier in fragment - */ - public function quoteIdentifierInFragment(string $identifier, array $additionalSafeWords = []): string; } diff --git a/src/Adapter/Platform/Sql92.php b/src/Adapter/Platform/Sql92.php index 058b1a693..7bade0026 100644 --- a/src/Adapter/Platform/Sql92.php +++ b/src/Adapter/Platform/Sql92.php @@ -13,7 +13,7 @@ class Sql92 extends AbstractPlatform { - public final const PLATFORM_NAME = 'SQL92'; + final public const PLATFORM_NAME = 'SQL92'; /** * {@inheritDoc} @@ -28,23 +28,23 @@ public function getName(): string * {@inheritDoc} */ #[Override] - public function quoteValue(string $value): string + public function getSqlPlatformDecorator(): PlatformDecoratorInterface { - if (! isset($this->driver)) { - throw VunerablePlatformQuoteException::forPlatformAndMethod( - static::class, - __METHOD__ - ); - } - return '\'' . addcslashes($value, "\x00\n\r\\'\"\x1a") . '\''; + return new Platform($this); } /** * {@inheritDoc} */ #[Override] - public function getSqlPlatformDecorator(): PlatformDecoratorInterface + public function quoteValue(string $value): string { - return new Platform($this); + if (! isset($this->driver)) { + throw VunerablePlatformQuoteException::forPlatformAndMethod( + static::class, + __METHOD__, + ); + } + return '\'' . addcslashes($value, "\x00\n\r\\'\"\x1a") . '\''; } } diff --git a/src/Adapter/Profiler/Profiler.php b/src/Adapter/Profiler/Profiler.php index 52ba376cc..573520c17 100644 --- a/src/Adapter/Profiler/Profiler.php +++ b/src/Adapter/Profiler/Profiler.php @@ -4,6 +4,7 @@ namespace PhpDb\Adapter\Profiler; +use Override; use PhpDb\Adapter\Exception; use PhpDb\Adapter\Exception\InvalidArgumentException; use PhpDb\Adapter\ParameterContainer; @@ -32,42 +33,30 @@ class Profiler implements ProfilerInterface protected $currentIndex = 0; /** - * @throws InvalidArgumentException - * @return $this Provides a fluent interface + * @return ProfileShape|null */ - public function profilerStart(string|StatementContainerInterface $target): ProfilerInterface + public function getLastProfile(): ?array { - $profileInformation = [ - 'sql' => '', - 'parameters' => null, - 'start' => microtime(true), - 'end' => null, - 'elapse' => null, - ]; - - if (is_string($target)) { - $profileInformation['sql'] = $target; - } else { - $profileInformation['sql'] = $target->getSql(); - $container = $target->getParameterContainer(); - if ($container !== null) { - $profileInformation['parameters'] = clone $container; - } - } - - $this->profiles[$this->currentIndex] = $profileInformation; + return end($this->profiles); + } - return $this; + /** + * @return ProfilesShape + */ + public function getProfiles(): array + { + return $this->profiles; } /** * @return $this Provides a fluent interface */ + #[Override] public function profilerFinish(): ProfilerInterface { if (! isset($this->profiles[$this->currentIndex])) { throw new Exception\RuntimeException( - 'A profile must be started before ' . __FUNCTION__ . ' can be called.' + 'A profile must be started before ' . __FUNCTION__ . ' can be called.', ); } $current = &$this->profiles[$this->currentIndex]; @@ -78,18 +67,32 @@ public function profilerFinish(): ProfilerInterface } /** - * @return ProfileShape|null + * @throws InvalidArgumentException + * @return $this Provides a fluent interface */ - public function getLastProfile(): ?array + #[Override] + public function profilerStart(string|StatementContainerInterface $target): ProfilerInterface { - return end($this->profiles); - } + $profileInformation = [ + 'sql' => '', + 'parameters' => null, + 'start' => microtime(true), + 'end' => null, + 'elapse' => null, + ]; - /** - * @return ProfilesShape - */ - public function getProfiles(): array - { - return $this->profiles; + if (is_string($target)) { + $profileInformation['sql'] = $target; + } else { + $profileInformation['sql'] = $target->getSql(); + $container = $target->getParameterContainer(); + if (null !== $container) { + $profileInformation['parameters'] = clone $container; + } + } + + $this->profiles[$this->currentIndex] = $profileInformation; + + return $this; } } diff --git a/src/Adapter/Profiler/ProfilerAwareInterface.php b/src/Adapter/Profiler/ProfilerAwareInterface.php index 1c95e227e..eeb13aacc 100644 --- a/src/Adapter/Profiler/ProfilerAwareInterface.php +++ b/src/Adapter/Profiler/ProfilerAwareInterface.php @@ -7,5 +7,5 @@ interface ProfilerAwareInterface { /** Implementation should provide a fluent interface */ - public function setProfiler(ProfilerInterface $profiler): ProfilerAwareInterface; + public function setProfiler(ProfilerInterface $profiler): self; } diff --git a/src/Adapter/Profiler/ProfilerInterface.php b/src/Adapter/Profiler/ProfilerInterface.php index be30e9c05..448a363e5 100644 --- a/src/Adapter/Profiler/ProfilerInterface.php +++ b/src/Adapter/Profiler/ProfilerInterface.php @@ -8,10 +8,10 @@ interface ProfilerInterface { - public function profilerStart(string|StatementContainerInterface $target): ProfilerInterface; - /** * @return $this */ - public function profilerFinish(): ProfilerInterface; + public function profilerFinish(): self; + + public function profilerStart(string|StatementContainerInterface $target): self; } diff --git a/src/Adapter/StatementContainer.php b/src/Adapter/StatementContainer.php index e694cf4d7..e44986b9f 100644 --- a/src/Adapter/StatementContainer.php +++ b/src/Adapter/StatementContainer.php @@ -2,6 +2,8 @@ namespace PhpDb\Adapter; +use Override; + class StatementContainer implements StatementContainerInterface { protected string $sql = ''; @@ -16,28 +18,32 @@ public function __construct(?string $sql = null, ?ParameterContainer $parameterC $this->parameterContainer = $parameterContainer; } - /** - * @param string $sql - */ - public function setSql($sql): StatementContainerInterface + #[Override] + public function getParameterContainer(): ?ParameterContainer { - $this->sql = $sql; - return $this; + return $this->parameterContainer; } + #[Override] public function getSql(): ?string { return $this->sql; } + #[Override] public function setParameterContainer(ParameterContainer $parameterContainer): StatementContainerInterface { $this->parameterContainer = $parameterContainer; return $this; } - public function getParameterContainer(): ?ParameterContainer + /** + * @param string $sql + */ + #[Override] + public function setSql($sql): StatementContainerInterface { - return $this->parameterContainer; + $this->sql = $sql; + return $this; } } diff --git a/src/Adapter/StatementContainerInterface.php b/src/Adapter/StatementContainerInterface.php index 6b037255d..59ca305bd 100644 --- a/src/Adapter/StatementContainerInterface.php +++ b/src/Adapter/StatementContainerInterface.php @@ -6,15 +6,15 @@ interface StatementContainerInterface { - /** Set sql */ - public function setSql(?string $sql): StatementContainerInterface; + /** Get parameter container */ + public function getParameterContainer(): ?ParameterContainer; /** Get sql */ public function getSql(): ?string; /** Set parameter container */ - public function setParameterContainer(ParameterContainer $parameterContainer): StatementContainerInterface; + public function setParameterContainer(ParameterContainer $parameterContainer): self; - /** Get parameter container */ - public function getParameterContainer(): ?ParameterContainer; + /** Set sql */ + public function setSql(?string $sql): self; } diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index eb1758d99..aca6c95f6 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -8,13 +8,6 @@ final class ConfigProvider { public const NAMED_ADAPTER_KEY = 'adapters'; - public function __invoke(): array - { - return [ - 'dependencies' => $this->getDependencies(), - ]; - } - public function getDependencies(): array { return [ @@ -29,4 +22,11 @@ public function getDependencies(): array ], ]; } + + public function __invoke(): array + { + return [ + 'dependencies' => $this->getDependencies(), + ]; + } } diff --git a/src/Container/AbstractAdapterInterfaceFactory.php b/src/Container/AbstractAdapterInterfaceFactory.php index 56ba2574e..640185577 100644 --- a/src/Container/AbstractAdapterInterfaceFactory.php +++ b/src/Container/AbstractAdapterInterfaceFactory.php @@ -6,6 +6,7 @@ use Laminas\ServiceManager\Factory\AbstractFactoryInterface; use Laminas\ServiceManager\ServiceManager; +use Override; use PhpDb\Adapter\Adapter; use PhpDb\Adapter\AdapterInterface; use PhpDb\Adapter\Driver\DriverInterface; @@ -36,17 +37,39 @@ final class AbstractAdapterInterfaceFactory implements AbstractFactoryInterface * * @param string $requestedName */ + #[Override] public function canCreate(ContainerInterface $container, $requestedName): bool { $config = $this->getConfig($container); - if ($config === []) { + if ([] === $config) { return false; } - return isset($config[$requestedName]) - && is_array($config[$requestedName]) - && ! empty($config[$requestedName]); + return ( + isset($config[$requestedName]) + && is_array($config[$requestedName]) + && ! empty($config[$requestedName]) + ); + } + + /** + * Get db configuration, if any + */ + protected function getConfig(ContainerInterface $container): array + { + if (null !== $this->config) { + return $this->config; + } + + if (! $container->has('config')) { + $this->config = []; + return $this->config; + } + + $config = $container->get('config'); + $this->config = $config[AdapterInterface::class][ConfigProvider::NAMED_ADAPTER_KEY] ?? []; + return $this->config; } /** @@ -55,19 +78,20 @@ public function canCreate(ContainerInterface $container, $requestedName): bool * @phpstan-param ContainerInterface&ServiceManager $container * @param string $requestedName */ + #[Override] public function __invoke( ContainerInterface|ServiceManager $container, $requestedName, - ?array $options = null + ?array $options = null, ): AdapterInterface&Adapter { /** @var class-string|class-string|null $driverClass */ $driverClass = $this->config[$requestedName]['driver'] ?? null; - if ($driverClass === null) { + if (null === $driverClass) { throw ContainerException::forService( $requestedName, self::class, - 'no driver configured' + 'no driver configured', ); } @@ -94,23 +118,4 @@ public function __invoke( profiler: $profiler, ); } - - /** - * Get db configuration, if any - */ - protected function getConfig(ContainerInterface $container): array - { - if ($this->config !== null) { - return $this->config; - } - - if (! $container->has('config')) { - $this->config = []; - return $this->config; - } - - $config = $container->get('config'); - $this->config = $config[AdapterInterface::class][ConfigProvider::NAMED_ADAPTER_KEY] ?? []; - return $this->config; - } } diff --git a/src/Container/AdapterInterfaceDelegator.php b/src/Container/AdapterInterfaceDelegator.php index 63a04cb8a..64b8384ad 100644 --- a/src/Container/AdapterInterfaceDelegator.php +++ b/src/Container/AdapterInterfaceDelegator.php @@ -17,14 +17,8 @@ class AdapterInterfaceDelegator { public function __construct( - protected readonly string $adapterName = AdapterInterface::class - ) { - } - - public static function __set_state(array $state): self - { - return new self($state['adapterName'] ?? AdapterInterface::class); - } + protected readonly string $adapterName = AdapterInterface::class, + ) {} /** * @throws ContainerExceptionInterface @@ -34,7 +28,7 @@ public function __invoke( ContainerInterface $container, string $name, callable $callback, - ?array $options = null + ?array $options = null, ): AdapterAwareInterface { $instance = $callback(); @@ -42,14 +36,14 @@ public function __invoke( throw new Exception\RuntimeException(sprintf( 'Delegated service "%s" must implement %s', $name, - AdapterAwareInterface::class + AdapterAwareInterface::class, )); } if (! $container->has($this->adapterName)) { throw new ServiceNotFoundException(sprintf( 'Service "%s" not found in container', - $this->adapterName + $this->adapterName, )); } @@ -63,4 +57,9 @@ public function __invoke( return $instance; } + + public static function __set_state(array $state): self + { + return new self($state['adapterName'] ?? AdapterInterface::class); + } } diff --git a/src/Container/AdapterInterfaceFactory.php b/src/Container/AdapterInterfaceFactory.php index 8969e896e..fe6db7609 100644 --- a/src/Container/AdapterInterfaceFactory.php +++ b/src/Container/AdapterInterfaceFactory.php @@ -26,18 +26,18 @@ public function __invoke( throw ContainerException::forService( $requestedName, self::class, - 'Container is missing a config service' + 'Container is missing a config service', ); } $config = $container->get('config') ?? []; $adapterConfig = $config[AdapterInterface::class] ?? $config[Adapter::class] ?? []; - if ($adapterConfig === []) { + if ([] === $adapterConfig) { throw ContainerException::forService( AdapterInterface::class, self::class, - 'No configuration found for ' . $requestedName + "No configuration found for {$requestedName}", ); } diff --git a/src/Exception/ContainerException.php b/src/Exception/ContainerException.php index 364a6be44..ec21ff90e 100644 --- a/src/Exception/ContainerException.php +++ b/src/Exception/ContainerException.php @@ -14,14 +14,14 @@ final class ContainerException extends SplRuntimeException implements ContainerE public static function forService( string $serviceName, string $factoryClass, - string $reason + string $reason, ): self { return new self( sprintf( 'Failed to create service "%s" in factory %s Reason: %s', $serviceName, $factoryClass, - $reason + $reason, ), 0, ); diff --git a/src/Exception/ErrorException.php b/src/Exception/ErrorException.php index 4943f18c1..f2ea20c5f 100644 --- a/src/Exception/ErrorException.php +++ b/src/Exception/ErrorException.php @@ -6,6 +6,4 @@ use Exception; -class ErrorException extends Exception implements ExceptionInterface -{ -} +class ErrorException extends Exception implements ExceptionInterface {} diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php index 638647809..0da92c8dc 100644 --- a/src/Exception/ExceptionInterface.php +++ b/src/Exception/ExceptionInterface.php @@ -4,6 +4,4 @@ namespace PhpDb\Exception; -interface ExceptionInterface -{ -} +interface ExceptionInterface {} diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index 1c6b454e8..dfd9f7058 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -4,6 +4,4 @@ namespace PhpDb\Exception; -class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface -{ -} +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface {} diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php index 70f437c80..760606199 100644 --- a/src/Exception/RuntimeException.php +++ b/src/Exception/RuntimeException.php @@ -4,6 +4,4 @@ namespace PhpDb\Exception; -class RuntimeException extends \RuntimeException implements ExceptionInterface -{ -} +class RuntimeException extends \RuntimeException implements ExceptionInterface {} diff --git a/src/Exception/UnexpectedValueException.php b/src/Exception/UnexpectedValueException.php index 8f934388b..c281897bc 100644 --- a/src/Exception/UnexpectedValueException.php +++ b/src/Exception/UnexpectedValueException.php @@ -4,6 +4,4 @@ namespace PhpDb\Exception; -class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface -{ -} +class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface {} diff --git a/src/Feature/FeatureInterface.php b/src/Feature/FeatureInterface.php index 5d8200dce..241a91212 100644 --- a/src/Feature/FeatureInterface.php +++ b/src/Feature/FeatureInterface.php @@ -6,8 +6,8 @@ interface FeatureInterface { - public function getName(): string; - /** @return array */ public function getMagicMethodSpecifications(): array; + + public function getName(): string; } diff --git a/src/Metadata/MetadataInterface.php b/src/Metadata/MetadataInterface.php index 671b49f35..ad3dba691 100644 --- a/src/Metadata/MetadataInterface.php +++ b/src/Metadata/MetadataInterface.php @@ -13,59 +13,49 @@ interface MetadataInterface { - /** - * @return string[] - */ - public function getSchemas(): array; + public function getColumn(string $columnName, string $table, ?string $schema = null): ColumnObject; - /** - * @return string[] - */ - public function getTableNames(?string $schema = null, bool $includeViews = false): array; + public function getColumnNames(string $table, ?string $schema = null): array; /** - * @return TableObject[] + * @return ColumnObject[] */ - public function getTables(?string $schema = null, bool $includeViews = false): array; + public function getColumns(string $table, ?string $schema = null): array; - public function getTable(string $tableName, ?string $schema = null): TableObject|ViewObject; + public function getConstraint( + string $constraintName, + string $table, + ?string $schema = null, + ): ConstraintObject; /** - * @return string[] + * @return ConstraintKeyObject[] */ - public function getViewNames(?string $schema = null): array; + public function getConstraintKeys(string $constraint, string $table, ?string $schema = null): array; /** - * @return ViewObject[] + * @return ConstraintObject[] */ - public function getViews(?string $schema = null): array; - - public function getView(string $viewName, ?string $schema = null): ViewObject|TableObject; - - public function getColumnNames(string $table, ?string $schema = null): array; + public function getConstraints(string $table, ?string $schema = null): array; /** - * @return ColumnObject[] + * @return string[] */ - public function getColumns(string $table, ?string $schema = null): array; + public function getSchemas(): array; - public function getColumn(string $columnName, string $table, ?string $schema = null): ColumnObject; + public function getTable(string $tableName, ?string $schema = null): TableObject|ViewObject; /** - * @return ConstraintObject[] + * @return string[] */ - public function getConstraints(string $table, ?string $schema = null): array; - - public function getConstraint( - string $constraintName, - string $table, - ?string $schema = null - ): ConstraintObject; + public function getTableNames(?string $schema = null, bool $includeViews = false): array; /** - * @return ConstraintKeyObject[] + * @return TableObject[] */ - public function getConstraintKeys(string $constraint, string $table, ?string $schema = null): array; + public function getTables(?string $schema = null, bool $includeViews = false): array; + + public function getTrigger(string $triggerName, ?string $schema = null): TriggerObject; /** * @return string[] @@ -77,5 +67,15 @@ public function getTriggerNames(?string $schema = null): array; */ public function getTriggers(?string $schema = null): array; - public function getTrigger(string $triggerName, ?string $schema = null): TriggerObject; + public function getView(string $viewName, ?string $schema = null): ViewObject|TableObject; + + /** + * @return string[] + */ + public function getViewNames(?string $schema = null): array; + + /** + * @return ViewObject[] + */ + public function getViews(?string $schema = null): array; } diff --git a/src/Metadata/Object/AbstractTableObject.php b/src/Metadata/Object/AbstractTableObject.php index 3014fb75d..894f1ff8a 100644 --- a/src/Metadata/Object/AbstractTableObject.php +++ b/src/Metadata/Object/AbstractTableObject.php @@ -27,54 +27,54 @@ public function __construct(?string $name = null) } /** - * Set columns + * Get columns + * + * @return array|null */ - public function setColumns(array $columns): void + public function getColumns(): ?array { - $this->columns = $columns; + return $this->columns; } /** - * Get columns + * Get constraints * * @return array|null */ - public function getColumns(): ?array + public function getConstraints(): ?array { - return $this->columns; + return $this->constraints; } /** - * Set constraints + * Get name */ - public function setConstraints(array $constraints): void + public function getName(): ?string { - $this->constraints = $constraints; + return $this->name; } /** - * Get constraints - * - * @return array|null + * Set columns */ - public function getConstraints(): ?array + public function setColumns(array $columns): void { - return $this->constraints; + $this->columns = $columns; } /** - * Set name + * Set constraints */ - public function setName(string $name): void + public function setConstraints(array $constraints): void { - $this->name = $name; + $this->constraints = $constraints; } /** - * Get name + * Set name */ - public function getName(): ?string + public function setName(string $name): void { - return $this->name; + $this->name = $name; } } diff --git a/src/Metadata/Object/ColumnObject.php b/src/Metadata/Object/ColumnObject.php index 7b7f9f2ea..286b0073c 100644 --- a/src/Metadata/Object/ColumnObject.php +++ b/src/Metadata/Object/ColumnObject.php @@ -16,7 +16,7 @@ class ColumnObject protected ?int $ordinalPosition = null; - protected null|string|int|bool $columnDefault = null; + protected string|int|bool|null $columnDefault = null; protected ?bool $isNullable = null; @@ -42,175 +42,189 @@ public function __construct(string $name, string $tableName, ?string $schemaName $this->setName($name); $this->setTableName($tableName); - if ($schemaName !== null) { + if (null !== $schemaName) { $this->setSchemaName($schemaName); } } /** - * Set name + * @return int|null the $characterMaximumLength */ - public function setName(string $name): void + public function getCharacterMaximumLength(): ?int { - $this->name = $name; + return $this->characterMaximumLength; } /** - * Get name + * @return int|null the $characterOctetLength */ - public function getName(): string + public function getCharacterOctetLength(): ?int { - return $this->name; + return $this->characterOctetLength; } /** - * Get table name + * @return null|string the $columnDefault */ - public function getTableName(): string + public function getColumnDefault(): ?string { - return $this->tableName; + return $this->columnDefault; } /** - * Set table name + * @return null|string the $dataType */ - public function setTableName(string $tableName): static + public function getDataType(): ?string { - $this->tableName = $tableName; - return $this; + return $this->dataType; + } + + public function getErrata(string $errataName): mixed + { + if (array_key_exists($errataName, $this->errata)) { + return $this->errata[$errataName]; + } + + return null; } /** - * Set schema name + * @return array the $errata */ - public function setSchemaName(string $schemaName): void + public function getErratas(): array { - $this->schemaName = $schemaName; + return $this->errata; } /** - * Get schema name + * @return bool|null $isNullable */ - public function getSchemaName(): ?string + public function getIsNullable(): ?bool { - return $this->schemaName; + return $this->isNullable; } /** - * @return int|null $ordinalPosition + * Get name */ - public function getOrdinalPosition(): ?int + public function getName(): string { - return $this->ordinalPosition; + return $this->name; } - public function setOrdinalPosition(?int $ordinalPosition): static + /** + * @return int|null the $numericPrecision + */ + public function getNumericPrecision(): ?int { - $this->ordinalPosition = $ordinalPosition; - return $this; + return $this->numericPrecision; } /** - * @return null|string the $columnDefault + * @return int|null the $numericScale */ - public function getColumnDefault(): ?string + public function getNumericScale(): ?int { - return $this->columnDefault; + return $this->numericScale; } - public function setColumnDefault(null|string|int|bool $columnDefault): static + public function getNumericUnsigned(): ?bool { - $this->columnDefault = $columnDefault; - return $this; + return $this->numericUnsigned; } /** - * @return bool|null $isNullable + * @return int|null $ordinalPosition */ - public function getIsNullable(): ?bool + public function getOrdinalPosition(): ?int { - return $this->isNullable; + return $this->ordinalPosition; } - public function setIsNullable(?bool $isNullable): static + /** + * Get schema name + */ + public function getSchemaName(): ?string { - $this->isNullable = $isNullable; - return $this; + return $this->schemaName; } /** - * @return null|string the $dataType + * Get table name */ - public function getDataType(): ?string + public function getTableName(): string { - return $this->dataType; + return $this->tableName; } - public function setDataType(string $dataType): static + public function isNumericUnsigned(): ?bool { - $this->dataType = $dataType; + return $this->numericUnsigned; + } + + public function setCharacterMaximumLength(?int $characterMaximumLength): static + { + $this->characterMaximumLength = $characterMaximumLength; return $this; } - /** - * @return int|null the $characterMaximumLength - */ - public function getCharacterMaximumLength(): ?int + public function setCharacterOctetLength(?int $characterOctetLength): static { - return $this->characterMaximumLength; + $this->characterOctetLength = $characterOctetLength; + return $this; } - public function setCharacterMaximumLength(?int $characterMaximumLength): static + public function setColumnDefault(string|int|bool|null $columnDefault): static { - $this->characterMaximumLength = $characterMaximumLength; + $this->columnDefault = $columnDefault; return $this; } - /** - * @return int|null the $characterOctetLength - */ - public function getCharacterOctetLength(): ?int + public function setDataType(string $dataType): static { - return $this->characterOctetLength; + $this->dataType = $dataType; + return $this; } - public function setCharacterOctetLength(?int $characterOctetLength): static + public function setErrata(string $errataName, mixed $errataValue): static { - $this->characterOctetLength = $characterOctetLength; + $this->errata[$errataName] = $errataValue; return $this; } - /** - * @return int|null the $numericPrecision - */ - public function getNumericPrecision(): ?int + public function setErratas(array $erratas): static { - return $this->numericPrecision; + foreach ($erratas as $name => $value) { + $this->setErrata($name, $value); + } + + return $this; } - public function setNumericPrecision(?int $numericPrecision): static + public function setIsNullable(?bool $isNullable): static { - $this->numericPrecision = $numericPrecision; + $this->isNullable = $isNullable; return $this; } /** - * @return int|null the $numericScale + * Set name */ - public function getNumericScale(): ?int + public function setName(string $name): void { - return $this->numericScale; + $this->name = $name; } - public function setNumericScale(?int $numericScale): static + public function setNumericPrecision(?int $numericPrecision): static { - $this->numericScale = $numericScale; + $this->numericPrecision = $numericPrecision; return $this; } - public function getNumericUnsigned(): ?bool + public function setNumericScale(?int $numericScale): static { - return $this->numericUnsigned; + $this->numericScale = $numericScale; + return $this; } public function setNumericUnsigned(?bool $numericUnsigned): static @@ -219,40 +233,26 @@ public function setNumericUnsigned(?bool $numericUnsigned): static return $this; } - public function isNumericUnsigned(): ?bool + public function setOrdinalPosition(?int $ordinalPosition): static { - return $this->numericUnsigned; + $this->ordinalPosition = $ordinalPosition; + return $this; } /** - * @return array the $errata + * Set schema name */ - public function getErratas(): array - { - return $this->errata; - } - - public function setErratas(array $erratas): static - { - foreach ($erratas as $name => $value) { - $this->setErrata($name, $value); - } - - return $this; - } - - public function getErrata(string $errataName): mixed + public function setSchemaName(string $schemaName): void { - if (array_key_exists($errataName, $this->errata)) { - return $this->errata[$errataName]; - } - - return null; + $this->schemaName = $schemaName; } - public function setErrata(string $errataName, mixed $errataValue): static + /** + * Set table name + */ + public function setTableName(string $tableName): static { - $this->errata[$errataName] = $errataValue; + $this->tableName = $tableName; return $this; } } diff --git a/src/Metadata/Object/ConstraintKeyObject.php b/src/Metadata/Object/ConstraintKeyObject.php index 53ba7ba76..f9aacf63b 100644 --- a/src/Metadata/Object/ConstraintKeyObject.php +++ b/src/Metadata/Object/ConstraintKeyObject.php @@ -33,9 +33,9 @@ class ConstraintKeyObject /** * Constructor */ - public function __construct(protected string $columnName) - { - } + public function __construct( + protected string $columnName, + ) {} /** * Get column name @@ -46,29 +46,27 @@ public function getColumnName(): string } /** - * Set column name + * get foreign key delete rule */ - public function setColumnName(string $columnName): static + public function getForeignKeyDeleteRule(): ?string { - $this->columnName = $columnName; - return $this; + return $this->foreignKeyDeleteRule; } /** - * Get ordinal position + * Get foreign key update rule */ - public function getOrdinalPosition(): ?int + public function getForeignKeyUpdateRule(): ?string { - return $this->ordinalPosition; + return $this->foreignKeyUpdateRule; } /** - * Set ordinal position + * Get ordinal position */ - public function setOrdinalPosition(int $ordinalPosition): static + public function getOrdinalPosition(): ?int { - $this->ordinalPosition = $ordinalPosition; - return $this; + return $this->ordinalPosition; } /** @@ -80,94 +78,96 @@ public function getPositionInUniqueConstraint(): ?bool } /** - * Set position in unique constraint + * Get referenced column name */ - public function setPositionInUniqueConstraint(bool $positionInUniqueConstraint): static + public function getReferencedColumnName(): ?string { - $this->positionInUniqueConstraint = $positionInUniqueConstraint; - return $this; + return $this->referencedColumnName; } /** - * Get referenced table schema + * Get referenced table name */ - public function getReferencedTableSchema(): ?string + public function getReferencedTableName(): ?string { - return $this->referencedTableSchema; + return $this->referencedTableName; } /** - * Set referenced table schema + * Get referenced table schema */ - public function setReferencedTableSchema(string $referencedTableSchema): static + public function getReferencedTableSchema(): ?string { - $this->referencedTableSchema = $referencedTableSchema; - return $this; + return $this->referencedTableSchema; } /** - * Get referenced table name + * Set column name */ - public function getReferencedTableName(): ?string + public function setColumnName(string $columnName): static { - return $this->referencedTableName; + $this->columnName = $columnName; + return $this; } /** - * Set Referenced table name + * Set foreign key delete rule */ - public function setReferencedTableName(string $referencedTableName): static + public function setForeignKeyDeleteRule(string $foreignKeyDeleteRule): void { - $this->referencedTableName = $referencedTableName; - return $this; + $this->foreignKeyDeleteRule = $foreignKeyDeleteRule; } /** - * Get referenced column name + * set foreign key update rule */ - public function getReferencedColumnName(): ?string + public function setForeignKeyUpdateRule(string $foreignKeyUpdateRule): void { - return $this->referencedColumnName; + $this->foreignKeyUpdateRule = $foreignKeyUpdateRule; } /** - * Set referenced column name + * Set ordinal position */ - public function setReferencedColumnName(string $referencedColumnName): static + public function setOrdinalPosition(int $ordinalPosition): static { - $this->referencedColumnName = $referencedColumnName; + $this->ordinalPosition = $ordinalPosition; return $this; } /** - * set foreign key update rule + * Set position in unique constraint */ - public function setForeignKeyUpdateRule(string $foreignKeyUpdateRule): void + public function setPositionInUniqueConstraint(bool $positionInUniqueConstraint): static { - $this->foreignKeyUpdateRule = $foreignKeyUpdateRule; + $this->positionInUniqueConstraint = $positionInUniqueConstraint; + return $this; } /** - * Get foreign key update rule + * Set referenced column name */ - public function getForeignKeyUpdateRule(): ?string + public function setReferencedColumnName(string $referencedColumnName): static { - return $this->foreignKeyUpdateRule; + $this->referencedColumnName = $referencedColumnName; + return $this; } /** - * Set foreign key delete rule + * Set Referenced table name */ - public function setForeignKeyDeleteRule(string $foreignKeyDeleteRule): void + public function setReferencedTableName(string $referencedTableName): static { - $this->foreignKeyDeleteRule = $foreignKeyDeleteRule; + $this->referencedTableName = $referencedTableName; + return $this; } /** - * get foreign key delete rule + * Set referenced table schema */ - public function getForeignKeyDeleteRule(): ?string + public function setReferencedTableSchema(string $referencedTableSchema): static { - return $this->foreignKeyDeleteRule; + $this->referencedTableSchema = $referencedTableSchema; + return $this; } } diff --git a/src/Metadata/Object/ConstraintObject.php b/src/Metadata/Object/ConstraintObject.php index b96a28351..549c18918 100644 --- a/src/Metadata/Object/ConstraintObject.php +++ b/src/Metadata/Object/ConstraintObject.php @@ -43,254 +43,254 @@ public function __construct(string $name, string $tableName, ?string $schemaName $this->setName($name); $this->setTableName($tableName); - if ($schemaName !== null) { + if (null !== $schemaName) { $this->setSchemaName($schemaName); } } /** - * Set name + * Get Check Clause. */ - public function setName(string $name): void + public function getCheckClause(): ?string { - $this->name = $name; + return $this->checkClause; } /** - * Get name + * Get Columns. + * + * @return string[] */ - public function getName(): string + public function getColumns(): array { - return $this->name; + return $this->columns; } /** - * Set schema name + * Get Delete Rule. */ - public function setSchemaName(string $schemaName): void + public function getDeleteRule(): ?string { - $this->schemaName = $schemaName; + return $this->deleteRule; } /** - * Get schema name + * Get Match Option. */ - public function getSchemaName(): ?string + public function getMatchOption(): ?string { - return $this->schemaName; + return $this->matchOption; } /** - * Get table name + * Get name */ - public function getTableName(): string + public function getName(): string { - return $this->tableName; + return $this->name; } /** - * Set table name + * Get Referenced Columns. + * + * @return string[]|null */ - public function setTableName(string $tableName): static + public function getReferencedColumns(): ?array { - $this->tableName = $tableName; - return $this; + return $this->referencedColumns; } /** - * Set type + * Get Referenced Table Name. */ - public function setType(string $type): void + public function getReferencedTableName(): ?string { - $this->type = $type; + return $this->referencedTableName; } /** - * Get type + * Get Referenced Table Schema. */ - public function getType(): ?string + public function getReferencedTableSchema(): ?string { - return $this->type; + return $this->referencedTableSchema; } - public function hasColumns(): bool + /** + * Get schema name + */ + public function getSchemaName(): ?string { - return $this->columns !== []; + return $this->schemaName; } /** - * Get Columns. - * - * @return string[] + * Get table name */ - public function getColumns(): array + public function getTableName(): string { - return $this->columns; + return $this->tableName; } /** - * Set Columns. - * - * @param string[] $columns + * Get type */ - public function setColumns(array $columns): static + public function getType(): ?string { - $this->columns = $columns; - return $this; + return $this->type; } /** - * Get Referenced Table Schema. + * Get Update Rule. */ - public function getReferencedTableSchema(): ?string + public function getUpdateRule(): ?string { - return $this->referencedTableSchema; + return $this->updateRule; } - /** - * Set Referenced Table Schema. - */ - public function setReferencedTableSchema(string $referencedTableSchema): static + public function hasColumns(): bool { - $this->referencedTableSchema = $referencedTableSchema; - return $this; + return [] !== $this->columns; } /** - * Get Referenced Table Name. + * Is foreign key */ - public function getReferencedTableName(): ?string + public function isCheck(): bool { - return $this->referencedTableName; + return 'CHECK' === $this->type; } /** - * Set Referenced Table Name. + * Is foreign key */ - public function setReferencedTableName(string $referencedTableName): static + public function isForeignKey(): bool { - $this->referencedTableName = $referencedTableName; - return $this; + return 'FOREIGN KEY' === $this->type; } /** - * Get Referenced Columns. - * - * @return string[]|null + * Is primary key */ - public function getReferencedColumns(): ?array + public function isPrimaryKey(): bool { - return $this->referencedColumns; + return 'PRIMARY KEY' === $this->type; } /** - * Set Referenced Columns. - * - * @param string[] $referencedColumns + * Is unique key */ - public function setReferencedColumns(array $referencedColumns): static + public function isUnique(): bool { - $this->referencedColumns = $referencedColumns; - return $this; + return 'UNIQUE' === $this->type; } /** - * Get Match Option. + * Set Check Clause. */ - public function getMatchOption(): ?string + public function setCheckClause(string $checkClause): static { - return $this->matchOption; + $this->checkClause = $checkClause; + return $this; } /** - * Set Match Option. + * Set Columns. + * + * @param string[] $columns */ - public function setMatchOption(string $matchOption): static + public function setColumns(array $columns): static { - $this->matchOption = $matchOption; + $this->columns = $columns; return $this; } /** - * Get Update Rule. + * Set Delete Rule. */ - public function getUpdateRule(): ?string + public function setDeleteRule(string $deleteRule): static { - return $this->updateRule; + $this->deleteRule = $deleteRule; + return $this; } /** - * Set Update Rule. + * Set Match Option. */ - public function setUpdateRule(string $updateRule): static + public function setMatchOption(string $matchOption): static { - $this->updateRule = $updateRule; + $this->matchOption = $matchOption; return $this; } /** - * Get Delete Rule. + * Set name */ - public function getDeleteRule(): ?string + public function setName(string $name): void { - return $this->deleteRule; + $this->name = $name; } /** - * Set Delete Rule. + * Set Referenced Columns. + * + * @param string[] $referencedColumns */ - public function setDeleteRule(string $deleteRule): static + public function setReferencedColumns(array $referencedColumns): static { - $this->deleteRule = $deleteRule; + $this->referencedColumns = $referencedColumns; return $this; } /** - * Get Check Clause. + * Set Referenced Table Name. */ - public function getCheckClause(): ?string + public function setReferencedTableName(string $referencedTableName): static { - return $this->checkClause; + $this->referencedTableName = $referencedTableName; + return $this; } /** - * Set Check Clause. + * Set Referenced Table Schema. */ - public function setCheckClause(string $checkClause): static + public function setReferencedTableSchema(string $referencedTableSchema): static { - $this->checkClause = $checkClause; + $this->referencedTableSchema = $referencedTableSchema; return $this; } /** - * Is primary key + * Set schema name */ - public function isPrimaryKey(): bool + public function setSchemaName(string $schemaName): void { - return 'PRIMARY KEY' === $this->type; + $this->schemaName = $schemaName; } /** - * Is unique key + * Set table name */ - public function isUnique(): bool + public function setTableName(string $tableName): static { - return 'UNIQUE' === $this->type; + $this->tableName = $tableName; + return $this; } /** - * Is foreign key + * Set type */ - public function isForeignKey(): bool + public function setType(string $type): void { - return 'FOREIGN KEY' === $this->type; + $this->type = $type; } /** - * Is foreign key + * Set Update Rule. */ - public function isCheck(): bool + public function setUpdateRule(string $updateRule): static { - return 'CHECK' === $this->type; + $this->updateRule = $updateRule; + return $this; } } diff --git a/src/Metadata/Object/TableObject.php b/src/Metadata/Object/TableObject.php index 2ae562eb0..0c767221f 100644 --- a/src/Metadata/Object/TableObject.php +++ b/src/Metadata/Object/TableObject.php @@ -4,6 +4,4 @@ namespace PhpDb\Metadata\Object; -class TableObject extends AbstractTableObject -{ -} +class TableObject extends AbstractTableObject {} diff --git a/src/Metadata/Object/TriggerObject.php b/src/Metadata/Object/TriggerObject.php index 9de786565..20e7650e6 100644 --- a/src/Metadata/Object/TriggerObject.php +++ b/src/Metadata/Object/TriggerObject.php @@ -39,147 +39,141 @@ class TriggerObject protected ?DateTime $created = null; /** - * Get Name. + * Get Action Condition. */ - public function getName(): ?string + public function getActionCondition(): ?string { - return $this->name; + return $this->actionCondition; } /** - * Set Name. + * Get Action Order. */ - public function setName(string $name): static + public function getActionOrder(): ?string { - $this->name = $name; - return $this; + return $this->actionOrder; } /** - * Get Event Manipulation. + * Get Action Orientation. */ - public function getEventManipulation(): ?string + public function getActionOrientation(): ?string { - return $this->eventManipulation; + return $this->actionOrientation; } /** - * Set Event Manipulation. + * Get Action Reference New Row. */ - public function setEventManipulation(string $eventManipulation): static + public function getActionReferenceNewRow(): ?string { - $this->eventManipulation = $eventManipulation; - return $this; + return $this->actionReferenceNewRow; } /** - * Get Event Object Catalog. + * Get Action Reference New Table. */ - public function getEventObjectCatalog(): ?string + public function getActionReferenceNewTable(): ?string { - return $this->eventObjectCatalog; + return $this->actionReferenceNewTable; } /** - * Set Event Object Catalog. + * Get Action Reference Old Row. */ - public function setEventObjectCatalog(string $eventObjectCatalog): static + public function getActionReferenceOldRow(): ?string { - $this->eventObjectCatalog = $eventObjectCatalog; - return $this; + return $this->actionReferenceOldRow; } /** - * Get Event Object Schema. + * Get Action Reference Old Table. */ - public function getEventObjectSchema(): ?string + public function getActionReferenceOldTable(): ?string { - return $this->eventObjectSchema; + return $this->actionReferenceOldTable; } /** - * Set Event Object Schema. + * Get Action Statement. */ - public function setEventObjectSchema(string $eventObjectSchema): static + public function getActionStatement(): ?string { - $this->eventObjectSchema = $eventObjectSchema; - return $this; + return $this->actionStatement; } /** - * Get Event Object Table. + * Get Action Timing. */ - public function getEventObjectTable(): ?string + public function getActionTiming(): ?string { - return $this->eventObjectTable; + return $this->actionTiming; } /** - * Set Event Object Table. + * Get Created. */ - public function setEventObjectTable(string $eventObjectTable): static + public function getCreated(): ?DateTime { - $this->eventObjectTable = $eventObjectTable; - return $this; + return $this->created; } /** - * Get Action Order. + * Get Event Manipulation. */ - public function getActionOrder(): ?string + public function getEventManipulation(): ?string { - return $this->actionOrder; + return $this->eventManipulation; } /** - * Set Action Order. + * Get Event Object Catalog. */ - public function setActionOrder(string $actionOrder): static + public function getEventObjectCatalog(): ?string { - $this->actionOrder = $actionOrder; - return $this; + return $this->eventObjectCatalog; } /** - * Get Action Condition. + * Get Event Object Schema. */ - public function getActionCondition(): ?string + public function getEventObjectSchema(): ?string { - return $this->actionCondition; + return $this->eventObjectSchema; } /** - * Set Action Condition. + * Get Event Object Table. */ - public function setActionCondition(?string $actionCondition): static + public function getEventObjectTable(): ?string { - $this->actionCondition = $actionCondition; - return $this; + return $this->eventObjectTable; } /** - * Get Action Statement. + * Get Name. */ - public function getActionStatement(): ?string + public function getName(): ?string { - return $this->actionStatement; + return $this->name; } /** - * Set Action Statement. + * Set Action Condition. */ - public function setActionStatement(string $actionStatement): static + public function setActionCondition(?string $actionCondition): static { - $this->actionStatement = $actionStatement; + $this->actionCondition = $actionCondition; return $this; } /** - * Get Action Orientation. + * Set Action Order. */ - public function getActionOrientation(): ?string + public function setActionOrder(string $actionOrder): static { - return $this->actionOrientation; + $this->actionOrder = $actionOrder; + return $this; } /** @@ -192,28 +186,34 @@ public function setActionOrientation(string $actionOrientation): static } /** - * Get Action Timing. + * Set Action Reference New Row. + * + * @return $this Provides a fluent interface */ - public function getActionTiming(): ?string + public function setActionReferenceNewRow(string $actionReferenceNewRow): static { - return $this->actionTiming; + $this->actionReferenceNewRow = $actionReferenceNewRow; + return $this; } /** - * Set Action Timing. + * Set Action Reference New Table. */ - public function setActionTiming(string $actionTiming): static + public function setActionReferenceNewTable(?string $actionReferenceNewTable): static { - $this->actionTiming = $actionTiming; + $this->actionReferenceNewTable = $actionReferenceNewTable; return $this; } /** - * Get Action Reference Old Table. + * Set Action Reference Old Row. + * + * @return $this Provides a fluent interface */ - public function getActionReferenceOldTable(): ?string + public function setActionReferenceOldRow(string $actionReferenceOldRow): static { - return $this->actionReferenceOldTable; + $this->actionReferenceOldRow = $actionReferenceOldRow; + return $this; } /** @@ -226,76 +226,76 @@ public function setActionReferenceOldTable(?string $actionReferenceOldTable): st } /** - * Get Action Reference New Table. + * Set Action Statement. */ - public function getActionReferenceNewTable(): ?string + public function setActionStatement(string $actionStatement): static { - return $this->actionReferenceNewTable; + $this->actionStatement = $actionStatement; + return $this; } /** - * Set Action Reference New Table. + * Set Action Timing. */ - public function setActionReferenceNewTable(?string $actionReferenceNewTable): static + public function setActionTiming(string $actionTiming): static { - $this->actionReferenceNewTable = $actionReferenceNewTable; + $this->actionTiming = $actionTiming; return $this; } /** - * Get Action Reference Old Row. + * Set Created. + * + * @return $this Provides a fluent interface */ - public function getActionReferenceOldRow(): ?string + public function setCreated(?DateTime $created): static { - return $this->actionReferenceOldRow; + $this->created = $created; + return $this; } /** - * Set Action Reference Old Row. - * - * @return $this Provides a fluent interface + * Set Event Manipulation. */ - public function setActionReferenceOldRow(string $actionReferenceOldRow): static + public function setEventManipulation(string $eventManipulation): static { - $this->actionReferenceOldRow = $actionReferenceOldRow; + $this->eventManipulation = $eventManipulation; return $this; } /** - * Get Action Reference New Row. + * Set Event Object Catalog. */ - public function getActionReferenceNewRow(): ?string + public function setEventObjectCatalog(string $eventObjectCatalog): static { - return $this->actionReferenceNewRow; + $this->eventObjectCatalog = $eventObjectCatalog; + return $this; } /** - * Set Action Reference New Row. - * - * @return $this Provides a fluent interface + * Set Event Object Schema. */ - public function setActionReferenceNewRow(string $actionReferenceNewRow): static + public function setEventObjectSchema(string $eventObjectSchema): static { - $this->actionReferenceNewRow = $actionReferenceNewRow; + $this->eventObjectSchema = $eventObjectSchema; return $this; } /** - * Get Created. + * Set Event Object Table. */ - public function getCreated(): ?DateTime + public function setEventObjectTable(string $eventObjectTable): static { - return $this->created; + $this->eventObjectTable = $eventObjectTable; + return $this; } /** - * Set Created. - * - * @return $this Provides a fluent interface + * Set Name. */ - public function setCreated(?DateTime $created): static + public function setName(string $name): static { - $this->created = $created; + $this->name = $name; return $this; } } diff --git a/src/Metadata/Object/ViewObject.php b/src/Metadata/Object/ViewObject.php index bc6f001e7..d561571b9 100644 --- a/src/Metadata/Object/ViewObject.php +++ b/src/Metadata/Object/ViewObject.php @@ -12,23 +12,24 @@ class ViewObject extends AbstractTableObject protected ?bool $isUpdatable = null; - public function getViewDefinition(): ?string + public function getCheckOption(): ?string { - return $this->viewDefinition; + return $this->checkOption; } - /** - * @param null|string $viewDefinition to set - */ - public function setViewDefinition(?string $viewDefinition): static + public function getIsUpdatable(): ?bool { - $this->viewDefinition = $viewDefinition; - return $this; + return $this->isUpdatable; } - public function getCheckOption(): ?string + public function getViewDefinition(): ?string { - return $this->checkOption; + return $this->viewDefinition; + } + + public function isUpdatable(): ?bool + { + return $this->isUpdatable; } /** @@ -40,16 +41,6 @@ public function setCheckOption(?string $checkOption): static return $this; } - public function getIsUpdatable(): ?bool - { - return $this->isUpdatable; - } - - public function isUpdatable(): ?bool - { - return $this->isUpdatable; - } - /** * @param bool $isUpdatable to set */ @@ -58,4 +49,13 @@ public function setIsUpdatable(?bool $isUpdatable): static $this->isUpdatable = $isUpdatable; return $this; } + + /** + * @param null|string $viewDefinition to set + */ + public function setViewDefinition(?string $viewDefinition): static + { + $this->viewDefinition = $viewDefinition; + return $this; + } } diff --git a/src/Metadata/Source/AbstractSource.php b/src/Metadata/Source/AbstractSource.php index 3e1222a31..9c730d908 100644 --- a/src/Metadata/Source/AbstractSource.php +++ b/src/Metadata/Source/AbstractSource.php @@ -58,231 +58,176 @@ abstract class AbstractSource implements MetadataInterface protected array $data = []; public function __construct( - protected AdapterInterface&SchemaAwareInterface $adapter + protected AdapterInterface&SchemaAwareInterface $adapter, ) { $this->defaultSchema = $this->adapter->getCurrentSchema() ?: self::DEFAULT_SCHEMA; } - /** - * {@inheritdoc} - */ #[Override] - public function getSchemas(): array - { - $this->loadSchemaData(); - - return $this->data['schemas']; - } - - /** - * {@inheritdoc} - */ - #[Override] - public function getTableNames(?string $schema = null, bool $includeViews = false): array + public function getColumn(string $columnName, string $table, ?string $schema = null): ColumnObject { - if ($schema === null) { + if (null === $schema) { $schema = $this->defaultSchema; } - $this->loadTableNameData($schema); - - if ($includeViews) { - return array_keys($this->data['table_names'][$schema]); - } + $this->loadColumnData($table, $schema); - $tableNames = []; - foreach ($this->data['table_names'][$schema] as $tableName => $data) { - if ('BASE TABLE' === $data['table_type']) { - $tableNames[] = $tableName; - } + if (! isset($this->data['columns'][$schema][$table][$columnName])) { + throw new Exception('A column by that name was not found.'); } - return $tableNames; - } + $info = $this->data['columns'][$schema][$table][$columnName]; - /** - * {@inheritdoc} - */ - #[Override] - public function getTables(?string $schema = null, bool $includeViews = false): array - { - if ($schema === null) { - $schema = $this->defaultSchema; - } + $column = new ColumnObject($columnName, $table, $schema); - $tables = []; - foreach ($this->getTableNames($schema, $includeViews) as $tableName) { - $tables[] = $this->getTable($tableName, $schema); - } + $column->setOrdinalPosition($info['ordinal_position'] ? (int) $info['ordinal_position'] : null); + $column->setColumnDefault($info['column_default']); + $column->setIsNullable($info['is_nullable']); + $column->setDataType($info['data_type']); + $column->setCharacterMaximumLength( + $info['character_maximum_length'] ? (int) $info['character_maximum_length'] : null, + ); + $column->setCharacterOctetLength( + $info['character_octet_length'] ? (int) $info['character_octet_length'] : null, + ); + $column->setNumericPrecision( + $info['numeric_precision'] ? (int) $info['numeric_precision'] : null, + ); + $column->setNumericScale( + $info['numeric_scale'] ? (int) $info['numeric_scale'] : null, + ); + $column->setNumericUnsigned($info['numeric_unsigned']); + $column->setErratas($info['erratas']); - return $tables; + return $column; } #[Override] - public function getTable(string $tableName, ?string $schema = null): TableObject|ViewObject + public function getColumnNames(string $table, ?string $schema = null): array { - if ($schema === null) { + if (null === $schema) { $schema = $this->defaultSchema; } - $this->loadTableNameData($schema); - - if (! isset($this->data['table_names'][$schema][$tableName])) { - throw new Exception('Table "' . $tableName . '" does not exist'); - } + $this->loadColumnData($table, $schema); - $data = $this->data['table_names'][$schema][$tableName]; - switch ($data['table_type']) { - case 'BASE TABLE': - $table = new TableObject($tableName); - break; - case 'VIEW': - $table = new ViewObject($tableName); - $table->setViewDefinition($data['view_definition']); - $table->setCheckOption($data['check_option']); - $table->setIsUpdatable($data['is_updatable']); - break; - default: - throw new Exception( - 'Table "' . $tableName . '" is of an unsupported type "' . $data['table_type'] . '"' - ); + if (! isset($this->data['columns'][$schema][$table])) { + throw new Exception('"' . $table . '" does not exist'); } - $table->setColumns($this->getColumns($tableName, $schema)); - $table->setConstraints($this->getConstraints($tableName, $schema)); - return $table; + return array_keys($this->data['columns'][$schema][$table]); } /** * {@inheritdoc} */ #[Override] - public function getViewNames(?string $schema = null): array + public function getColumns(string $table, ?string $schema = null): array { - if ($schema === null) { + if (null === $schema) { $schema = $this->defaultSchema; } - $this->loadTableNameData($schema); - - $viewNames = []; - foreach ($this->data['table_names'][$schema] as $tableName => $data) { - if ('VIEW' === $data['table_type']) { - $viewNames[] = $tableName; - } - } - - return $viewNames; - } - - /** - * {@inheritdoc} - */ - #[Override] - public function getViews(?string $schema = null): array - { - if ($schema === null) { - $schema = $this->defaultSchema; - } + $this->loadColumnData($table, $schema); - $views = []; - foreach ($this->getViewNames($schema) as $tableName) { - $views[] = $this->getTable($tableName, $schema); + $columns = []; + foreach ($this->getColumnNames($table, $schema) as $columnName) { + $columns[] = $this->getColumn($columnName, $table, $schema); } - return $views; + return $columns; } #[Override] - public function getView(string $viewName, ?string $schema = null): ViewObject|TableObject - { - if ($schema === null) { + public function getConstraint( + string $constraintName, + string $table, + ?string $schema = null, + ): ConstraintObject { + if (null === $schema) { $schema = $this->defaultSchema; } - $this->loadTableNameData($schema); + $this->loadConstraintData($table, $schema); - $tableNames = $this->data['table_names'][$schema]; - if (isset($tableNames[$viewName]) && 'VIEW' === $tableNames[$viewName]['table_type']) { - return $this->getTable($viewName, $schema); + if (! isset($this->data['constraints'][$schema][$table][$constraintName])) { + throw new Exception('Cannot find a constraint by that name in this table'); } - throw new Exception('View "' . $viewName . '" does not exist'); - } - - #[Override] - public function getColumnNames(string $table, ?string $schema = null): array - { - if ($schema === null) { - $schema = $this->defaultSchema; - } + $info = $this->data['constraints'][$schema][$table][$constraintName]; + $constraint = new ConstraintObject($constraintName, $table, $schema); - $this->loadColumnData($table, $schema); + foreach ([ + 'constraint_type' => 'setType', + 'match_option' => 'setMatchOption', + 'update_rule' => 'setUpdateRule', + 'delete_rule' => 'setDeleteRule', + 'columns' => 'setColumns', + 'referenced_table_schema' => 'setReferencedTableSchema', + 'referenced_table_name' => 'setReferencedTableName', + 'referenced_columns' => 'setReferencedColumns', + 'check_clause' => 'setCheckClause', + ] as $key => $setMethod) { + if (! isset($info[$key])) { + continue; + } - if (! isset($this->data['columns'][$schema][$table])) { - throw new Exception('"' . $table . '" does not exist'); + $constraint->{$setMethod}($info[$key]); } - return array_keys($this->data['columns'][$schema][$table]); + return $constraint; } /** * {@inheritdoc} */ #[Override] - public function getColumns(string $table, ?string $schema = null): array + public function getConstraintKeys(string $constraint, string $table, ?string $schema = null): array { - if ($schema === null) { + if (null === $schema) { $schema = $this->defaultSchema; } - $this->loadColumnData($table, $schema); - - $columns = []; - foreach ($this->getColumnNames($table, $schema) as $columnName) { - $columns[] = $this->getColumn($columnName, $table, $schema); - } + $this->loadConstraintReferences($table, $schema); - return $columns; - } + // organize references first + $references = []; + foreach ($this->data['constraint_references'][$schema] as $refKeyInfo) { + if ($refKeyInfo['constraint_name'] !== $constraint) { + continue; + } - #[Override] - public function getColumn(string $columnName, string $table, ?string $schema = null): ColumnObject - { - if ($schema === null) { - $schema = $this->defaultSchema; + $references[$refKeyInfo['constraint_name']] = $refKeyInfo; } - $this->loadColumnData($table, $schema); + $this->loadConstraintDataKeys($schema); - if (! isset($this->data['columns'][$schema][$table][$columnName])) { - throw new Exception('A column by that name was not found.'); - } + $keys = []; + foreach ($this->data['constraint_keys'][$schema] as $constraintKeyInfo) { + if ( + ! ( - $info = $this->data['columns'][$schema][$table][$columnName]; + $constraintKeyInfo['table_name'] === $table + && $constraintKeyInfo['constraint_name'] === $constraint - $column = new ColumnObject($columnName, $table, $schema); + ) + ) { + continue; + } - $column->setOrdinalPosition($info['ordinal_position'] ? (int) $info['ordinal_position'] : null); - $column->setColumnDefault($info['column_default']); - $column->setIsNullable($info['is_nullable']); - $column->setDataType($info['data_type']); - $column->setCharacterMaximumLength( - $info['character_maximum_length'] ? (int) $info['character_maximum_length'] : null - ); - $column->setCharacterOctetLength( - $info['character_octet_length'] ? (int) $info['character_octet_length'] : null - ); - $column->setNumericPrecision( - $info['numeric_precision'] ? (int) $info['numeric_precision'] : null - ); - $column->setNumericScale( - $info['numeric_scale'] ? (int) $info['numeric_scale'] : null - ); - $column->setNumericUnsigned($info['numeric_unsigned']); - $column->setErratas($info['erratas']); + $keys[] = $key = new ConstraintKeyObject($constraintKeyInfo['column_name']); + $key->setOrdinalPosition($constraintKeyInfo['ordinal_position']); + if (isset($references[$constraint])) { + //$key->setReferencedTableSchema($constraintKeyInfo['referenced_table_schema']); + $key->setForeignKeyUpdateRule($references[$constraint]['update_rule']); + $key->setForeignKeyDeleteRule($references[$constraint]['delete_rule']); + //$key->setReferencedTableSchema($references[$constraint]['referenced_table_schema']); + $key->setReferencedTableName($references[$constraint]['referenced_table_name']); + $key->setReferencedColumnName($references[$constraint]['referenced_column_name']); + } + } - return $column; + return $keys; } /** @@ -291,7 +236,7 @@ public function getColumn(string $columnName, string $table, ?string $schema = n #[Override] public function getConstraints(string $table, ?string $schema = null): array { - if ($schema === null) { + if (null === $schema) { $schema = $this->defaultSchema; } @@ -305,124 +250,102 @@ public function getConstraints(string $table, ?string $schema = null): array return $constraints; } + /** + * {@inheritdoc} + */ #[Override] - public function getConstraint( - string $constraintName, - string $table, - ?string $schema = null - ): ConstraintObject { - if ($schema === null) { + public function getSchemas(): array + { + $this->loadSchemaData(); + + return $this->data['schemas']; + } + + #[Override] + public function getTable(string $tableName, ?string $schema = null): TableObject|ViewObject + { + if (null === $schema) { $schema = $this->defaultSchema; } - $this->loadConstraintData($table, $schema); + $this->loadTableNameData($schema); - if (! isset($this->data['constraints'][$schema][$table][$constraintName])) { - throw new Exception('Cannot find a constraint by that name in this table'); + if (! isset($this->data['table_names'][$schema][$tableName])) { + throw new Exception('Table "' . $tableName . '" does not exist'); } - $info = $this->data['constraints'][$schema][$table][$constraintName]; - $constraint = new ConstraintObject($constraintName, $table, $schema); - - foreach ( - [ - 'constraint_type' => 'setType', - 'match_option' => 'setMatchOption', - 'update_rule' => 'setUpdateRule', - 'delete_rule' => 'setDeleteRule', - 'columns' => 'setColumns', - 'referenced_table_schema' => 'setReferencedTableSchema', - 'referenced_table_name' => 'setReferencedTableName', - 'referenced_columns' => 'setReferencedColumns', - 'check_clause' => 'setCheckClause', - ] as $key => $setMethod - ) { - if (isset($info[$key])) { - $constraint->{$setMethod}($info[$key]); - } + $data = $this->data['table_names'][$schema][$tableName]; + switch ($data['table_type']) { + case 'BASE TABLE': + $table = new TableObject($tableName); + break; + case 'VIEW': + $table = new ViewObject($tableName); + $table->setViewDefinition($data['view_definition']); + $table->setCheckOption($data['check_option']); + $table->setIsUpdatable($data['is_updatable']); + break; + default: + throw new Exception( + 'Table "' . $tableName . '" is of an unsupported type "' . $data['table_type'] . '"', + ); } - return $constraint; + $table->setColumns($this->getColumns($tableName, $schema)); + $table->setConstraints($this->getConstraints($tableName, $schema)); + return $table; } /** * {@inheritdoc} */ #[Override] - public function getConstraintKeys(string $constraint, string $table, ?string $schema = null): array + public function getTableNames(?string $schema = null, bool $includeViews = false): array { - if ($schema === null) { + if (null === $schema) { $schema = $this->defaultSchema; } - $this->loadConstraintReferences($table, $schema); + $this->loadTableNameData($schema); - // organize references first - $references = []; - foreach ($this->data['constraint_references'][$schema] as $refKeyInfo) { - if ($refKeyInfo['constraint_name'] === $constraint) { - $references[$refKeyInfo['constraint_name']] = $refKeyInfo; - } + if ($includeViews) { + return array_keys($this->data['table_names'][$schema]); } - $this->loadConstraintDataKeys($schema); - - $keys = []; - foreach ($this->data['constraint_keys'][$schema] as $constraintKeyInfo) { - if ($constraintKeyInfo['table_name'] === $table && $constraintKeyInfo['constraint_name'] === $constraint) { - $keys[] = $key = new ConstraintKeyObject($constraintKeyInfo['column_name']); - $key->setOrdinalPosition($constraintKeyInfo['ordinal_position']); - if (isset($references[$constraint])) { - //$key->setReferencedTableSchema($constraintKeyInfo['referenced_table_schema']); - $key->setForeignKeyUpdateRule($references[$constraint]['update_rule']); - $key->setForeignKeyDeleteRule($references[$constraint]['delete_rule']); - //$key->setReferencedTableSchema($references[$constraint]['referenced_table_schema']); - $key->setReferencedTableName($references[$constraint]['referenced_table_name']); - $key->setReferencedColumnName($references[$constraint]['referenced_column_name']); - } + $tableNames = []; + foreach ($this->data['table_names'][$schema] as $tableName => $data) { + if ('BASE TABLE' !== $data['table_type']) { + continue; } - } - - return $keys; - } - /** - * {@inheritdoc} - */ - #[Override] - public function getTriggerNames(?string $schema = null): array - { - if ($schema === null) { - $schema = $this->defaultSchema; + $tableNames[] = $tableName; } - $this->loadTriggerData($schema); - - return array_keys($this->data['triggers'][$schema]); + return $tableNames; } /** * {@inheritdoc} */ #[Override] - public function getTriggers(?string $schema = null): array + public function getTables(?string $schema = null, bool $includeViews = false): array { - if ($schema === null) { + if (null === $schema) { $schema = $this->defaultSchema; } - $triggers = []; - foreach ($this->getTriggerNames($schema) as $triggerName) { - $triggers[] = $this->getTrigger($triggerName, $schema); + $tables = []; + foreach ($this->getTableNames($schema, $includeViews) as $tableName) { + $tables[] = $this->getTable($tableName, $schema); } - return $triggers; + return $tables; } #[Override] public function getTrigger(string $triggerName, ?string $schema = null): TriggerObject { - if ($schema === null) { + if (null === $schema) { $schema = $this->defaultSchema; } @@ -456,37 +379,102 @@ public function getTrigger(string $triggerName, ?string $schema = null): Trigger } /** - * Prepare data hierarchy + * {@inheritdoc} */ - protected function prepareDataHierarchy(string $type): void + #[Override] + public function getTriggerNames(?string $schema = null): array { - $data = &$this->data; - foreach (func_get_args() as $key) { - if (! isset($data[$key])) { - $data[$key] = []; - } + if (null === $schema) { + $schema = $this->defaultSchema; + } - $data = &$data[$key]; + $this->loadTriggerData($schema); + + return array_keys($this->data['triggers'][$schema]); + } + + /** + * {@inheritdoc} + */ + #[Override] + public function getTriggers(?string $schema = null): array + { + if (null === $schema) { + $schema = $this->defaultSchema; } + + $triggers = []; + foreach ($this->getTriggerNames($schema) as $triggerName) { + $triggers[] = $this->getTrigger($triggerName, $schema); + } + + return $triggers; + } + + #[Override] + public function getView(string $viewName, ?string $schema = null): ViewObject|TableObject + { + if (null === $schema) { + $schema = $this->defaultSchema; + } + + $this->loadTableNameData($schema); + + $tableNames = $this->data['table_names'][$schema]; + if (isset($tableNames[$viewName]) && 'VIEW' === $tableNames[$viewName]['table_type']) { + return $this->getTable($viewName, $schema); + } + + throw new Exception('View "' . $viewName . '" does not exist'); } /** - * Load schema data + * {@inheritdoc} */ - abstract protected function loadSchemaData(): void; + #[Override] + public function getViewNames(?string $schema = null): array + { + if (null === $schema) { + $schema = $this->defaultSchema; + } + + $this->loadTableNameData($schema); + + $viewNames = []; + foreach ($this->data['table_names'][$schema] as $tableName => $data) { + if ('VIEW' !== $data['table_type']) { + continue; + } + + $viewNames[] = $tableName; + } + + return $viewNames; + } /** - * Load table name data + * {@inheritdoc} */ - protected function loadTableNameData(string $schema): void + #[Override] + public function getViews(?string $schema = null): array { - if (isset($this->data['table_names'][$schema])) { - return; + if (null === $schema) { + $schema = $this->defaultSchema; } - $this->prepareDataHierarchy('table_names', $schema); + $views = []; + foreach ($this->getViewNames($schema) as $tableName) { + $views[] = $this->getTable($tableName, $schema); + } + + return $views; } + /** + * Load schema data + */ + abstract protected function loadSchemaData(): void; + /** * Load column data */ @@ -535,6 +523,18 @@ protected function loadConstraintReferences(string $table, string $schema): void $this->prepareDataHierarchy('constraint_references', $schema); } + /** + * Load table name data + */ + protected function loadTableNameData(string $schema): void + { + if (isset($this->data['table_names'][$schema])) { + return; + } + + $this->prepareDataHierarchy('table_names', $schema); + } + /** * Load trigger data */ @@ -546,4 +546,19 @@ protected function loadTriggerData(string $schema): void $this->prepareDataHierarchy('triggers', $schema); } + + /** + * Prepare data hierarchy + */ + protected function prepareDataHierarchy(string $type): void + { + $data = &$this->data; + foreach (func_get_args() as $key) { + if (! isset($data[$key])) { + $data[$key] = []; + } + + $data = &$data[$key]; + } + } } diff --git a/src/ResultSet/AbstractResultSet.php b/src/ResultSet/AbstractResultSet.php index b909f4c1d..af98fb3ba 100644 --- a/src/ResultSet/AbstractResultSet.php +++ b/src/ResultSet/AbstractResultSet.php @@ -43,70 +43,65 @@ abstract class AbstractResultSet implements ResultSetInterface protected int $position = 0; /** - * Set the data source for the result set - * - * @throws InvalidArgumentException|Exception + * @throws RuntimeException */ - #[Override] - public function initialize(iterable $dataSource): ResultSetInterface + public function buffer(): ResultSetInterface { - // reset buffering - if (is_array($this->buffer)) { - $this->buffer = []; + if ($this->buffer === -2) { + throw new RuntimeException('Buffering must be enabled before iteration is started'); } - if ($dataSource instanceof ResultInterface) { - $this->fieldCount = $dataSource->getFieldCount(); - $this->dataSource = $dataSource; - if ($dataSource->isBuffered()) { - $this->buffer = -1; - } - - if (is_array($this->buffer)) { + if (null === $this->buffer) { + $this->buffer = []; + if ($this->dataSource instanceof ResultInterface) { $this->dataSource->rewind(); } - - return $this; - } - - if (is_array($dataSource)) { - // its safe to get numbers from an array - $first = current($dataSource); - reset($dataSource); - $this->fieldCount = $first === false ? 0 : count($first); - $this->dataSource = new ArrayIterator($dataSource); - $this->buffer = -1; // array's are a natural buffer - } elseif ($dataSource instanceof IteratorAggregate) { - /** @phpstan-ignore assign.propertyType */ - $this->dataSource = $dataSource->getIterator(); - } else { - /** @phpstan-ignore assign.propertyType */ - $this->dataSource = $dataSource; } return $this; } /** - * @throws RuntimeException + * Countable: return count of rows */ - public function buffer(): ResultSetInterface + #[Override] + #[ReturnTypeWillChange] + public function count(): ?int { - if ($this->buffer === -2) { - throw new RuntimeException('Buffering must be enabled before iteration is started'); - } elseif ($this->buffer === null) { - $this->buffer = []; - if ($this->dataSource instanceof ResultInterface) { - $this->dataSource->rewind(); - } + if (null !== $this->count) { + return $this->count; } - return $this; + if ($this->dataSource instanceof Countable) { + $this->count = count($this->dataSource); + } + + return $this->count; } - public function isBuffered(): bool + /** + * Iterator: get current item + */ + #[Override] + public function current(): array|object|null { - return $this->buffer === -1 || is_array($this->buffer); + if (-1 === $this->buffer) { + // datasource was an array when the resultset was initialized + return $this->dataSource->current(); + } + + if (null === $this->buffer) { + $this->buffer = -2; // implicitly disable buffering from here on + } elseif (is_array($this->buffer) && isset($this->buffer[$this->position])) { + return $this->buffer[$this->position]; + } + + $data = $this->dataSource->current(); + if (is_array($this->buffer)) { + $this->buffer[$this->position] = $data; + } + + return is_array($data) ? $data : null; } /** @@ -150,20 +145,53 @@ public function getFieldCount(): int } /** - * Iterator: move pointer to next item + * Set the data source for the result set + * + * @throws InvalidArgumentException|Exception */ #[Override] - public function next(): void + public function initialize(iterable $dataSource): ResultSetInterface { - if ($this->buffer === null) { - $this->buffer = -2; // implicitly disable buffering from here on + // reset buffering + if (is_array($this->buffer)) { + $this->buffer = []; } - if (! is_array($this->buffer) || $this->position === $this->dataSource->key()) { - $this->dataSource->next(); + if ($dataSource instanceof ResultInterface) { + $this->fieldCount = $dataSource->getFieldCount(); + $this->dataSource = $dataSource; + if ($dataSource->isBuffered()) { + $this->buffer = -1; + } + + if (is_array($this->buffer)) { + $this->dataSource->rewind(); + } + + return $this; } - $this->position++; + if (is_array($dataSource)) { + // its safe to get numbers from an array + $first = current($dataSource); + reset($dataSource); + $this->fieldCount = false === $first ? 0 : count($first); + $this->dataSource = new ArrayIterator($dataSource); + $this->buffer = -1; // array's are a natural buffer + } elseif ($dataSource instanceof IteratorAggregate) { + /** @phpstan-ignore assign.propertyType */ + $this->dataSource = $dataSource->getIterator(); + } else { + /** @phpstan-ignore assign.propertyType */ + $this->dataSource = $dataSource; + } + + return $this; + } + + public function isBuffered(): bool + { + return $this->buffer === -1 || is_array($this->buffer); } /** @@ -176,41 +204,20 @@ public function key(): int } /** - * Iterator: get current item + * Iterator: move pointer to next item */ #[Override] - public function current(): array|object|null + public function next(): void { - if (-1 === $this->buffer) { - // datasource was an array when the resultset was initialized - return $this->dataSource->current(); - } - - if ($this->buffer === null) { + if (null === $this->buffer) { $this->buffer = -2; // implicitly disable buffering from here on - } elseif (is_array($this->buffer) && isset($this->buffer[$this->position])) { - return $this->buffer[$this->position]; } - $data = $this->dataSource->current(); - if (is_array($this->buffer)) { - $this->buffer[$this->position] = $data; - } - - return is_array($data) ? $data : null; - } - - /** - * Iterator: is pointer valid? - */ - #[Override] - public function valid(): bool - { - if (is_array($this->buffer) && isset($this->buffer[$this->position])) { - return true; + if (! is_array($this->buffer) || $this->position === $this->dataSource->key()) { + $this->dataSource->next(); } - return $this->dataSource->valid(); + $this->position++; } /** @@ -226,24 +233,6 @@ public function rewind(): void $this->position = 0; } - /** - * Countable: return count of rows - */ - #[Override] - #[ReturnTypeWillChange] - public function count(): ?int - { - if ($this->count !== null) { - return $this->count; - } - - if ($this->dataSource instanceof Countable) { - $this->count = count($this->dataSource); - } - - return $this->count; - } - /** * Cast result set to array of arrays * @@ -261,13 +250,13 @@ public function toArray(): array if ( ! is_object($row) - || ( - ! method_exists($row, 'toArray') - && ! method_exists($row, 'getArrayCopy') - ) + || ( + ! method_exists($row, 'toArray') + && ! method_exists($row, 'getArrayCopy') + ) ) { throw new RuntimeException( - 'Rows as part of this DataSource, with type ' . gettype($row) . ' cannot be cast to an array' + 'Rows as part of this DataSource, with type ' . gettype($row) . ' cannot be cast to an array', ); } @@ -276,4 +265,17 @@ public function toArray(): array return $return; } + + /** + * Iterator: is pointer valid? + */ + #[Override] + public function valid(): bool + { + if (is_array($this->buffer) && isset($this->buffer[$this->position])) { + return true; + } + + return $this->dataSource->valid(); + } } diff --git a/src/ResultSet/Exception/ExceptionInterface.php b/src/ResultSet/Exception/ExceptionInterface.php index 99af05fb7..a37c8aeb7 100644 --- a/src/ResultSet/Exception/ExceptionInterface.php +++ b/src/ResultSet/Exception/ExceptionInterface.php @@ -6,6 +6,4 @@ use PhpDb\Exception; -interface ExceptionInterface extends Exception\ExceptionInterface -{ -} +interface ExceptionInterface extends Exception\ExceptionInterface {} diff --git a/src/ResultSet/Exception/InvalidArgumentException.php b/src/ResultSet/Exception/InvalidArgumentException.php index 6524aa411..dd58a69da 100644 --- a/src/ResultSet/Exception/InvalidArgumentException.php +++ b/src/ResultSet/Exception/InvalidArgumentException.php @@ -6,6 +6,4 @@ use PhpDb\Exception; -class InvalidArgumentException extends Exception\InvalidArgumentException implements ExceptionInterface -{ -} +class InvalidArgumentException extends Exception\InvalidArgumentException implements ExceptionInterface {} diff --git a/src/ResultSet/Exception/RuntimeException.php b/src/ResultSet/Exception/RuntimeException.php index 4f40887fa..62f61f09d 100644 --- a/src/ResultSet/Exception/RuntimeException.php +++ b/src/ResultSet/Exception/RuntimeException.php @@ -6,6 +6,4 @@ use PhpDb\Exception; -class RuntimeException extends Exception\RuntimeException implements ExceptionInterface -{ -} +class RuntimeException extends Exception\RuntimeException implements ExceptionInterface {} diff --git a/src/ResultSet/HydratingResultSet.php b/src/ResultSet/HydratingResultSet.php index 3f4baba1e..1ff81611d 100644 --- a/src/ResultSet/HydratingResultSet.php +++ b/src/ResultSet/HydratingResultSet.php @@ -15,17 +15,28 @@ class HydratingResultSet extends AbstractResultSet { public function __construct( private ?HydratorInterface $hydrator = null, - private ?object $rowPrototype = null - ) { - } + private ?object $rowPrototype = null, + ) {} /** - * Set the hydrator to use for each row object + * Iterator: get current item */ - public function setHydrator(HydratorInterface $hydrator): ResultSetInterface + #[Override] + public function current(): ?object { - $this->hydrator = $hydrator; - return $this; + if (null === $this->buffer) { + $this->buffer = -2; // implicitly disable buffering from here on + } elseif (is_array($this->buffer) && isset($this->buffer[$this->position])) { + return $this->buffer[$this->position]; + } + $data = $this->dataSource->current(); + $current = is_array($data) ? $this->getHydrator()->hydrate($data, clone $this->getRowPrototype()) : null; + + if (is_array($this->buffer)) { + $this->buffer[$this->position] = $current; + } + + return $current; } /** @@ -36,12 +47,10 @@ public function getHydrator(): HydratorInterface return $this->hydrator ??= new ArraySerializableHydrator(); } - /** {@inheritDoc} */ - #[Override] - public function setRowPrototype(object $rowPrototype): ResultSetInterface + /** @deprecated use getRowPrototype() */ + public function getObjectPrototype(): ?object { - $this->rowPrototype = $rowPrototype; - return $this; + return $this->getRowPrototype(); } /** {@inheritDoc} */ @@ -51,37 +60,27 @@ public function getRowPrototype(): object return $this->rowPrototype ??= new ArrayObject(); } - /** @deprecated use setRowPrototype() */ - public function setObjectPrototype(object $objectPrototype): ResultSetInterface + /** + * Set the hydrator to use for each row object + */ + public function setHydrator(HydratorInterface $hydrator): ResultSetInterface { - return $this->setRowPrototype($objectPrototype); + $this->hydrator = $hydrator; + return $this; } - /** @deprecated use getRowPrototype() */ - public function getObjectPrototype(): ?object + /** @deprecated use setRowPrototype() */ + public function setObjectPrototype(object $objectPrototype): ResultSetInterface { - return $this->getRowPrototype(); + return $this->setRowPrototype($objectPrototype); } - /** - * Iterator: get current item - */ + /** {@inheritDoc} */ #[Override] - public function current(): ?object + public function setRowPrototype(object $rowPrototype): ResultSetInterface { - if ($this->buffer === null) { - $this->buffer = -2; // implicitly disable buffering from here on - } elseif (is_array($this->buffer) && isset($this->buffer[$this->position])) { - return $this->buffer[$this->position]; - } - $data = $this->dataSource->current(); - $current = is_array($data) ? $this->getHydrator()->hydrate($data, clone $this->getRowPrototype()) : null; - - if (is_array($this->buffer)) { - $this->buffer[$this->position] = $current; - } - - return $current; + $this->rowPrototype = $rowPrototype; + return $this; } /** diff --git a/src/ResultSet/ResultSet.php b/src/ResultSet/ResultSet.php index afb35160f..b245f1a78 100644 --- a/src/ResultSet/ResultSet.php +++ b/src/ResultSet/ResultSet.php @@ -20,28 +20,38 @@ public function __construct( private ResultSetReturnType|string $returnType = ResultSetReturnType::ArrayObject, private ArrayObject|RowPrototypeInterface|null $rowPrototype = new ArrayObject( [], - ArrayObject::ARRAY_AS_PROPS - ) + ArrayObject::ARRAY_AS_PROPS, + ), ) { if (is_string($this->returnType)) { $this->returnType = ResultSetReturnType::from($this->returnType); } } - /** {@inheritDoc} */ + /** + * Iterator: get current item + */ #[Override] - public function setRowPrototype(ArrayObject|RowPrototypeInterface $rowPrototype): ResultSetInterface + public function current(): array|ArrayObject|RowPrototypeInterface|null { - $this->rowPrototype = $rowPrototype; + $data = parent::current(); - return $this; + if (ResultSetReturnType::ArrayObject === $this->returnType && is_array($data)) { + $ao = clone $this->getRowPrototype(); + $ao->exchangeArray($data); + + return $ao; + } + + return $data; } - /** {@inheritDoc} */ - #[Override] - public function getRowPrototype(): ArrayObject|RowPrototypeInterface + /** + * @deprecated use getRowPrototype() + */ + public function getArrayObjectPrototype(): ArrayObject|RowPrototypeInterface { - return $this->rowPrototype; + return $this->getRowPrototype(); } /** @@ -52,22 +62,11 @@ public function getReturnType(): ResultSetReturnType return $this->returnType; } - /** - * Iterator: get current item - */ + /** {@inheritDoc} */ #[Override] - public function current(): array|ArrayObject|RowPrototypeInterface|null + public function getRowPrototype(): ArrayObject|RowPrototypeInterface { - $data = parent::current(); - - if ($this->returnType === ResultSetReturnType::ArrayObject && is_array($data)) { - $ao = clone $this->getRowPrototype(); - $ao->exchangeArray($data); - - return $ao; - } - - return $data; + return $this->rowPrototype; } /** @@ -80,11 +79,12 @@ public function setArrayObjectPrototype(ArrayObject|RowPrototypeInterface $array return $this->setRowPrototype($arrayObjectPrototype); } - /** - * @deprecated use getRowPrototype() - */ - public function getArrayObjectPrototype(): ArrayObject|RowPrototypeInterface + /** {@inheritDoc} */ + #[Override] + public function setRowPrototype(ArrayObject|RowPrototypeInterface $rowPrototype): ResultSetInterface { - return $this->getRowPrototype(); + $this->rowPrototype = $rowPrototype; + + return $this; } } diff --git a/src/ResultSet/ResultSetInterface.php b/src/ResultSet/ResultSetInterface.php index 9a3245ccf..bf6e94836 100644 --- a/src/ResultSet/ResultSetInterface.php +++ b/src/ResultSet/ResultSetInterface.php @@ -10,11 +10,6 @@ interface ResultSetInterface extends Iterator, Countable { - /** - * Can be anything iterable|array - */ - public function initialize(iterable $dataSource): ResultSetInterface; - /** * Field terminology is more correct as information coming back * from the database might be a column, and/or the result of an @@ -23,16 +18,21 @@ public function initialize(iterable $dataSource): ResultSetInterface; public function getFieldCount(): int; /** - * Set the row object prototype - * - * @throws Exception\InvalidArgumentException + * Get the row object prototype + */ + public function getRowPrototype(): ?object; + + /** + * Can be anything iterable|array */ - public function setRowPrototype(ArrayObject|RowPrototypeInterface $rowPrototype): ResultSetInterface; + public function initialize(iterable $dataSource): self; /** - * Get the row object prototype + * Set the row object prototype + * + * @throws Exception\InvalidArgumentException */ - public function getRowPrototype(): ?object; + public function setRowPrototype(ArrayObject|RowPrototypeInterface $rowPrototype): self; /** * Get all rows as an array diff --git a/src/RowGateway/AbstractRowGateway.php b/src/RowGateway/AbstractRowGateway.php index 0d5f639ba..191fe3b37 100644 --- a/src/RowGateway/AbstractRowGateway.php +++ b/src/RowGateway/AbstractRowGateway.php @@ -30,6 +30,53 @@ abstract class AbstractRowGateway implements ArrayAccess, Countable, RowGatewayI protected ?Feature\FeatureSet $featureSet = null; + #[Override] + #[ReturnTypeWillChange] + public function count(): int + { + return count($this->data); + } + + #[Override] + public function delete(): int + { + $this->initialize(); + + $where = []; + foreach ($this->primaryKeyColumn as $pkColumn) { + $where[$pkColumn] = $this->primaryKeyData[$pkColumn] ?? null; + } + + // @todo determine if we need to do a select to ensure 1 row will be affected + + $rowsAffected = 0; + $statement = $this->sql->prepareStatementForSqlObject($this->sql->delete()->where($where)); + $result = $statement->execute(); + + $rowsAffected = $result->getAffectedRows(); + if (1 === $rowsAffected) { + $this->primaryKeyData = null; + } + + return $rowsAffected; + } + + /** + * docs: Behaviour has changed - this no longer returns RowGatewayInterface but + * instead an array of the old data as per original PHP spec. + * + * @return array + */ + #[Override] + public function exchangeArray(array $array): array + { + $oldData = $this->data; + + $this->populate($array, true); + + return $oldData; + } + /** * initialize() */ @@ -46,15 +93,15 @@ public function initialize(): void $this->featureSet->setRowGateway($this); $this->featureSet->apply('preInitialize', []); - if ($this->table === null) { + if (null === $this->table) { throw new Exception\RuntimeException('This row object does not have a valid table set.'); } - if ($this->primaryKeyColumn === null) { + if (null === $this->primaryKeyColumn) { throw new Exception\RuntimeException('This row object does not have a primary key column set.'); } - if ($this->sql === null) { + if (null === $this->sql) { throw new Exception\RuntimeException('This row object does not have a Sql object set.'); } @@ -63,6 +110,58 @@ public function initialize(): void $this->isInitialized = true; } + /** + * Offset Exists + * + * @param string $offset + */ + #[Override] + #[ReturnTypeWillChange] + public function offsetExists($offset): bool + { + return array_key_exists($offset, $this->data); + } + + /** + * Offset get + * + * @param string $offset + */ + #[Override] + #[ReturnTypeWillChange] + public function offsetGet($offset): mixed + { + return $this->data[$offset]; + } + + /** + * Offset set + * + * @param string $offset + */ + #[Override] + #[ReturnTypeWillChange] + public function offsetSet($offset, mixed $value): static + { + $this->data[$offset] = $value; + + return $this; + } + + /** + * Offset unset + * + * @param string $offset + */ + #[Override] + #[ReturnTypeWillChange] + public function offsetUnset($offset): static + { + $this->data[$offset] = null; + + return $this; + } + /** * Populate Data */ @@ -71,7 +170,7 @@ public function populate(array $rowData, bool $rowExistsInDatabase = false): Row $this->initialize(); $this->data = $rowData; - if ($rowExistsInDatabase === true) { + if (true === $rowExistsInDatabase) { $this->processPrimaryKeyData(); } else { $this->primaryKeyData = null; @@ -80,19 +179,9 @@ public function populate(array $rowData, bool $rowExistsInDatabase = false): Row return $this; } - /** - * docs: Behaviour has changed - this no longer returns RowGatewayInterface but - * instead an array of the old data as per original PHP spec. - * - * @return array - */ - public function exchangeArray(array $array): array + public function rowExistsInDatabase(): bool { - $oldData = $this->data; - - $this->populate($array, true); - - return $oldData; + return null !== $this->primaryKeyData; } #[Override] @@ -123,9 +212,11 @@ public function save(): int if ($isPkModified) { foreach ($this->primaryKeyColumn as $pkColumn) { - if ($data[$pkColumn] !== $this->primaryKeyData[$pkColumn]) { - $where[$pkColumn] = $data[$pkColumn]; + if ($data[$pkColumn] === $this->primaryKeyData[$pkColumn]) { + continue; } + + $where[$pkColumn] = $data[$pkColumn]; } } } else { @@ -159,91 +250,25 @@ public function save(): int } #[Override] - public function delete(): int - { - $this->initialize(); - - $where = []; - foreach ($this->primaryKeyColumn as $pkColumn) { - $where[$pkColumn] = $this->primaryKeyData[$pkColumn] ?? null; - } - - // @todo determine if we need to do a select to ensure 1 row will be affected - - $rowsAffected = 0; - $statement = $this->sql->prepareStatementForSqlObject($this->sql->delete()->where($where)); - $result = $statement->execute(); - - $rowsAffected = $result->getAffectedRows(); - if ($rowsAffected === 1) { - $this->primaryKeyData = null; - } - - return $rowsAffected; - } - - /** - * Offset Exists - * - * @param string $offset - */ - #[Override] - #[ReturnTypeWillChange] - public function offsetExists($offset): bool - { - return array_key_exists($offset, $this->data); - } - - /** - * Offset get - * - * @param string $offset - */ - #[Override] - #[ReturnTypeWillChange] - public function offsetGet($offset): mixed - { - return $this->data[$offset]; - } - - /** - * Offset set - * - * @param string $offset - */ - #[Override] - #[ReturnTypeWillChange] - public function offsetSet($offset, mixed $value): static + public function toArray(): array { - $this->data[$offset] = $value; - - return $this; + return $this->data; } /** - * Offset unset - * - * @param string $offset + * @throws Exception\RuntimeException */ - #[Override] - #[ReturnTypeWillChange] - public function offsetUnset($offset): static - { - $this->data[$offset] = null; - - return $this; - } - - #[Override] - #[ReturnTypeWillChange] - public function count(): int - { - return count($this->data); - } - - public function toArray(): array + protected function processPrimaryKeyData(): void { - return $this->data; + $this->primaryKeyData = []; + foreach ($this->primaryKeyColumn as $column) { + if (! isset($this->data[$column])) { + throw new Exception\RuntimeException( + "While processing primary key data, a known key {$column} was not found in the data array", + ); + } + $this->primaryKeyData[$column] = $this->data[$column]; + } } /** @@ -254,12 +279,7 @@ public function __get(string $name): mixed if (array_key_exists($name, $this->data)) { return $this->data[$name]; } - throw new Exception\InvalidArgumentException('Not a valid column in this row: ' . $name); - } - - public function __set(string $name, mixed $value): void - { - $this->offsetSet($name, $value); + throw new Exception\InvalidArgumentException("Not a valid column in this row: {$name}"); } public function __isset(string $name): bool @@ -267,29 +287,13 @@ public function __isset(string $name): bool return $this->offsetExists($name); } - public function __unset(string $name): void - { - $this->offsetUnset($name); - } - - public function rowExistsInDatabase(): bool + public function __set(string $name, mixed $value): void { - return $this->primaryKeyData !== null; + $this->offsetSet($name, $value); } - /** - * @throws Exception\RuntimeException - */ - protected function processPrimaryKeyData(): void + public function __unset(string $name): void { - $this->primaryKeyData = []; - foreach ($this->primaryKeyColumn as $column) { - if (! isset($this->data[$column])) { - throw new Exception\RuntimeException( - 'While processing primary key data, a known key ' . $column . ' was not found in the data array' - ); - } - $this->primaryKeyData[$column] = $this->data[$column]; - } + $this->offsetUnset($name); } } diff --git a/src/RowGateway/Exception/ExceptionInterface.php b/src/RowGateway/Exception/ExceptionInterface.php index eb68e4fe4..0512c8be8 100644 --- a/src/RowGateway/Exception/ExceptionInterface.php +++ b/src/RowGateway/Exception/ExceptionInterface.php @@ -6,6 +6,4 @@ use PhpDb\Exception; -interface ExceptionInterface extends Exception\ExceptionInterface -{ -} +interface ExceptionInterface extends Exception\ExceptionInterface {} diff --git a/src/RowGateway/Exception/InvalidArgumentException.php b/src/RowGateway/Exception/InvalidArgumentException.php index 1987b53f9..b0d2289d3 100644 --- a/src/RowGateway/Exception/InvalidArgumentException.php +++ b/src/RowGateway/Exception/InvalidArgumentException.php @@ -6,6 +6,4 @@ use PhpDb\Exception; -class InvalidArgumentException extends Exception\InvalidArgumentException implements ExceptionInterface -{ -} +class InvalidArgumentException extends Exception\InvalidArgumentException implements ExceptionInterface {} diff --git a/src/RowGateway/Exception/RuntimeException.php b/src/RowGateway/Exception/RuntimeException.php index f2816cd00..365b44111 100644 --- a/src/RowGateway/Exception/RuntimeException.php +++ b/src/RowGateway/Exception/RuntimeException.php @@ -6,6 +6,4 @@ use PhpDb\Exception; -class RuntimeException extends Exception\RuntimeException implements ExceptionInterface -{ -} +class RuntimeException extends Exception\RuntimeException implements ExceptionInterface {} diff --git a/src/RowGateway/Feature/AbstractFeature.php b/src/RowGateway/Feature/AbstractFeature.php index e5c53c6cd..fbf926567 100644 --- a/src/RowGateway/Feature/AbstractFeature.php +++ b/src/RowGateway/Feature/AbstractFeature.php @@ -4,6 +4,7 @@ namespace PhpDb\RowGateway\Feature; +use Override; use PhpDb\RowGateway\AbstractRowGateway; use PhpDb\RowGateway\Exception; use PhpDb\RowGateway\Exception\RuntimeException; @@ -14,27 +15,31 @@ abstract class AbstractFeature extends AbstractRowGateway implements FeatureInte protected array $sharedData = []; - public function getName(): string + /** @return array */ + #[Override] + public function getMagicMethodSpecifications(): array { - return static::class; + return []; } - public function setRowGateway(AbstractRowGateway $rowGateway): void + #[Override] + public function getName(): string { - $this->rowGateway = $rowGateway; + return static::class; } /** * @throws RuntimeException */ + #[Override] public function initialize(): void { throw new Exception\RuntimeException('This method is not intended to be called on this object.'); } - /** @return array */ - public function getMagicMethodSpecifications(): array + #[Override] + public function setRowGateway(AbstractRowGateway $rowGateway): void { - return []; + $this->rowGateway = $rowGateway; } } diff --git a/src/RowGateway/Feature/FeatureSet.php b/src/RowGateway/Feature/FeatureSet.php index 46613db08..213e1b58b 100644 --- a/src/RowGateway/Feature/FeatureSet.php +++ b/src/RowGateway/Feature/FeatureSet.php @@ -24,32 +24,20 @@ class FeatureSet public function __construct(array $features = []) { - if ($features !== []) { + if ([] !== $features) { $this->addFeatures($features); } } - public function setRowGateway(AbstractRowGateway $rowGateway): static + public function addFeature(FeatureInterface $feature): static { - $this->rowGateway = $rowGateway; - foreach ($this->features as $feature) { + $this->features[] = $feature; + if (null !== $this->rowGateway) { $feature->setRowGateway($this->rowGateway); } return $this; } - public function getFeatureByClassName(string $featureClassName): ?FeatureInterface - { - $feature = null; - foreach ($this->features as $potentialFeature) { - if ($potentialFeature instanceof $featureClassName) { - $feature = $potentialFeature; - break; - } - } - return $feature; - } - public function addFeatures(array $features): static { foreach ($features as $feature) { @@ -58,30 +46,23 @@ public function addFeatures(array $features): static return $this; } - public function addFeature(FeatureInterface $feature): static - { - $this->features[] = $feature; - if ($this->rowGateway !== null) { - $feature->setRowGateway($this->rowGateway); - } - return $this; - } - public function apply(string $method, array $args): void { foreach ($this->features as $feature) { - if (method_exists($feature, $method)) { - $return = $feature->$method(...$args); - if ($return === self::APPLY_HALT) { - break; - } + if (! method_exists($feature, $method)) { + continue; + } + + $return = $feature->$method(...$args); + if (self::APPLY_HALT === $return) { + break; } } } - public function canCallMagicGet(string $property): false + public function callMagicCall(string $method, array $arguments): mixed { - return false; + return null; } public function callMagicGet(string $property): mixed @@ -89,23 +70,46 @@ public function callMagicGet(string $property): mixed return null; } - public function canCallMagicSet(string $property): false + public function callMagicSet(string $property, mixed $value): mixed + { + return null; + } + + public function canCallMagicCall(string $method): bool { return false; } - public function callMagicSet(string $property, mixed $value): mixed + public function canCallMagicGet(string $property): false { - return null; + return false; } - public function canCallMagicCall(string $method): bool + public function canCallMagicSet(string $property): false { return false; } - public function callMagicCall(string $method, array $arguments): mixed + public function getFeatureByClassName(string $featureClassName): ?FeatureInterface { - return null; + $feature = null; + foreach ($this->features as $potentialFeature) { + if (! $potentialFeature instanceof $featureClassName) { + continue; + } + + $feature = $potentialFeature; + break; + } + return $feature; + } + + public function setRowGateway(AbstractRowGateway $rowGateway): static + { + $this->rowGateway = $rowGateway; + foreach ($this->features as $feature) { + $feature->setRowGateway($this->rowGateway); + } + return $this; } } diff --git a/src/RowGateway/RowGateway.php b/src/RowGateway/RowGateway.php index 75ba32a6f..bebabcb0a 100644 --- a/src/RowGateway/RowGateway.php +++ b/src/RowGateway/RowGateway.php @@ -20,11 +20,11 @@ class RowGateway extends AbstractRowGateway public function __construct( string|array|null $primaryKeyColumn, string|TableIdentifier $table, - Sql|AdapterInterface $adapterOrSql + Sql|AdapterInterface $adapterOrSql, ) { // setup primary key if (is_string($primaryKeyColumn)) { - $primaryKeyColumn = $primaryKeyColumn !== '' ? (array) $primaryKeyColumn : null; + $primaryKeyColumn = '' !== $primaryKeyColumn ? (array) $primaryKeyColumn : null; } $this->primaryKeyColumn = $primaryKeyColumn; @@ -40,7 +40,7 @@ public function __construct( if ($this->sql->getTable() !== $this->table) { throw new Exception\InvalidArgumentException( - 'The Sql object provided does not have a table that matches this row object' + 'The Sql object provided does not have a table that matches this row object', ); } diff --git a/src/RowGateway/RowGatewayInterface.php b/src/RowGateway/RowGatewayInterface.php index 70788aa48..e96b96528 100644 --- a/src/RowGateway/RowGatewayInterface.php +++ b/src/RowGateway/RowGatewayInterface.php @@ -8,7 +8,7 @@ interface RowGatewayInterface extends RowPrototypeInterface { - public function save(): int; - public function delete(): int; + + public function save(): int; } diff --git a/src/Sql/AbstractExpression.php b/src/Sql/AbstractExpression.php index f4808bdfb..e5ea98fcc 100644 --- a/src/Sql/AbstractExpression.php +++ b/src/Sql/AbstractExpression.php @@ -9,20 +9,20 @@ abstract class AbstractExpression implements ExpressionInterface protected ?string $specification = null; /** - * Set specification string to override the default + * Get specification override, or null if not set */ - public function setSpecification(string $specification): static + public function getSpecification(): ?string { - $this->specification = $specification; - - return $this; + return $this->specification; } /** - * Get specification override, or null if not set + * Set specification string to override the default */ - public function getSpecification(): ?string + public function setSpecification(string $specification): static { - return $this->specification; + $this->specification = $specification; + + return $this; } } diff --git a/src/Sql/AbstractPreparableSql.php b/src/Sql/AbstractPreparableSql.php index d2eb3409b..178623ce1 100644 --- a/src/Sql/AbstractPreparableSql.php +++ b/src/Sql/AbstractPreparableSql.php @@ -14,7 +14,7 @@ abstract class AbstractPreparableSql extends AbstractSql implements PreparableSq #[Override] public function prepareStatement( AdapterInterface $adapter, - StatementContainerInterface $statementContainer + StatementContainerInterface $statementContainer, ): StatementContainerInterface { $parameterContainer = $statementContainer->getParameterContainer(); @@ -25,7 +25,7 @@ public function prepareStatement( } $statementContainer->setSql( - $this->buildSqlString($adapter->getPlatform(), $adapter->getDriver(), $parameterContainer) + $this->buildSqlString($adapter->getPlatform(), $adapter->getDriver(), $parameterContainer), ); return $statementContainer; diff --git a/src/Sql/AbstractSql.php b/src/Sql/AbstractSql.php index eb5350a66..7c9140da3 100644 --- a/src/Sql/AbstractSql.php +++ b/src/Sql/AbstractSql.php @@ -65,22 +65,22 @@ public function getSqlString(?PlatformInterface $adapterPlatform = null): string protected function buildSqlString( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): string { $this->localizeVariables(); $sqls = []; foreach ($this->specifications as $name => $specification) { - $result = $this->{'process' . $name}( + $result = $this->{"process{$name}"}( $platform, $driver, - $parameterContainer + $parameterContainer, ); if (is_array($result)) { $sqls[$name] = $this->createSqlFromSpecificationAndParameters($specification, $result); - } elseif ($result !== null) { + } elseif (null !== $result) { $sqls[$name] = $result; } } @@ -89,13 +89,116 @@ protected function buildSqlString( } /** - * Render table with alias in from/join parts + * @throws Exception\RuntimeException + */ + protected function createSqlFromSpecificationAndParameters(array|string $specifications, array $parameters): string + { + if (is_string($specifications)) { + return vsprintf($specifications, $parameters); + } + + $parametersCount = count($parameters); + + foreach ($specifications as $specificationString => $paramSpecs) { + if (count($paramSpecs) === $parametersCount) { + break; + } + + unset($specificationString, $paramSpecs); + } + + if (! isset($specificationString)) { + throw new Exception\RuntimeException( + 'A number of parameters was found that is not supported by this specification', + ); + } + + $topParameters = []; + foreach ($parameters as $position => $paramsForPosition) { + if (isset($paramSpecs[$position]['combinedby'])) { + $multiParamValues = []; + foreach ($paramsForPosition as $multiParamsForPosition) { + if (is_array($multiParamsForPosition)) { + $ppCount = count($multiParamsForPosition); + } else { + $ppCount = 1; + $multiParamsForPosition = [$multiParamsForPosition]; + } + + if (! isset($paramSpecs[$position][$ppCount])) { + throw new Exception\RuntimeException(sprintf( + 'A number of parameters (%d) was found that is not supported by this specification', + $ppCount, + )); + } + + $multiParamValues[] = vsprintf($paramSpecs[$position][$ppCount], $multiParamsForPosition); + } + + $topParameters[] = implode($paramSpecs[$position]['combinedby'], $multiParamValues); + } elseif (null !== $paramSpecs[$position]) { + $ppCount = count($paramsForPosition); + if (! isset($paramSpecs[$position][$ppCount])) { + throw new Exception\RuntimeException(sprintf( + 'A number of parameters (%d) was found that is not supported by this specification', + $ppCount, + )); + } + + $topParameters[] = vsprintf($paramSpecs[$position][$ppCount], $paramsForPosition); + } else { + $topParameters[] = $paramsForPosition; + } + } + + return vsprintf($specificationString, $topParameters); + } + + /** + * Flattens expression values, expanding Values arguments * - * @todo move TableIdentifier concatenation here + * @param ArgumentInterface[] $arguments + * @return ArgumentInterface[] */ - protected function renderTable(string $table, ?string $alias = null): string + protected function flattenExpressionValues(array $arguments): array { - return $alias ? "{$table} AS {$alias}" : $table; + $hasValues = false; + foreach ($arguments as $argument) { + if (! $argument instanceof Values) { + continue; + } + + $hasValues = true; + break; + } + + if (! $hasValues) { + return $arguments; + } + + $values = []; + foreach ($arguments as $argument) { + if ($argument instanceof Values) { + foreach ($argument->getValue() as $v) { + $values[] = new Value($v); + } + } else { + $values[] = $argument; + } + } + + return $values; + } + + protected function localizeVariables(): void + { + if (! $this instanceof PlatformDecoratorInterface) { + return; + } + + foreach (get_object_vars($this->subject) as $name => $value) { + $this->{$name} = $value; + } } /** @@ -106,7 +209,7 @@ protected function processExpression( PlatformInterface $platform, ?DriverInterface $driver = null, ?ParameterContainer $parameterContainer = null, - ?string $namedParameterPrefix = null + ?string $namedParameterPrefix = null, ): string { static $runtimeExpressionPrefix = 0; @@ -114,16 +217,17 @@ protected function processExpression( $specification = $expressionData['spec']; $expressionValues = $expressionData['values']; - if ($expressionValues === []) { + if ([] === $expressionValues) { return str_replace('%%', '%', $specification); } - if ($namedParameterPrefix === null || $namedParameterPrefix === '') { + if (null === $namedParameterPrefix || '' === $namedParameterPrefix) { $namedParameterPrefix = $parameterContainer ? 'expr' . $runtimeExpressionPrefix++ . 'Param' : ''; } else { - $namedParameterPrefix = $this->processInfo['paramPrefix'] + $namedParameterPrefix = + $this->processInfo['paramPrefix'] . str_replace([' ', "\t", "\n", "\r"], '__', $namedParameterPrefix); } @@ -143,7 +247,7 @@ protected function processExpression( $namedParameterPrefix, $expressionParamIndex, $driver, - $parameterContainer + $parameterContainer, ) : $platform->quoteValue((string) $argument->getValue()), $argument instanceof Identifier => $platform->quoteIdentifierInFragment($argument->getValue()), @@ -155,7 +259,7 @@ protected function processExpression( $vIndex, $platform, $driver, - $parameterContainer + $parameterContainer, ), default => throw new Exception\InvalidArgumentException('Unknown argument type'), }; @@ -164,85 +268,35 @@ protected function processExpression( return vsprintf($specification, $values); } - /** - * Flattens expression values, expanding Values arguments - * - * @param ArgumentInterface[] $arguments - * @return ArgumentInterface[] - */ - protected function flattenExpressionValues(array $arguments): array - { - $hasValues = false; - foreach ($arguments as $argument) { - if ($argument instanceof Values) { - $hasValues = true; - break; - } - } - - if (! $hasValues) { - return $arguments; - } - - $values = []; - foreach ($arguments as $argument) { - if ($argument instanceof Values) { - foreach ($argument->getValue() as $v) { - $values[] = new Value($v); - } - } else { - $values[] = $argument; - } - } - - return $values; - } - protected function processExpressionOrSelect( ArgumentInterface $argument, string $namedParameterPrefix, int $vIndex, PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): string { $value = $argument->getValue(); return match (true) { - $value instanceof Select => '(' - . $this->processSubSelect($value, $platform, $driver, $parameterContainer) - . ')', + $value instanceof Select => "({$this->processSubSelect($value, $platform, $driver, $parameterContainer)})", $value instanceof ExpressionInterface => $this->processExpression( $value, $platform, $driver, $parameterContainer, - "{$namedParameterPrefix}{$vIndex}subpart" + "{$namedParameterPrefix}{$vIndex}subpart", ), default => throw new ValueError('Invalid Argument type'), }; } - protected function processIdentifiersArgument( - ArgumentInterface $argument, - PlatformInterface $platform - ): string { - $identifiers = $argument->getValue(); - $processedIdentifiers = []; - - foreach ($identifiers as $identifier) { - $processedIdentifiers[] = $platform->quoteIdentifierInFragment($identifier); - } - - return implode(', ', $processedIdentifiers); - } - protected function processExpressionParameterName( int|float|string|bool $value, string $namedParameterPrefix, int &$expressionParamIndex, DriverInterface $driver, - ParameterContainer $parameterContainer + ParameterContainer $parameterContainer, ): ?string { $name = $namedParameterPrefix . $expressionParamIndex++; $parameterContainer->offsetSet($name, $value); @@ -250,99 +304,18 @@ protected function processExpressionParameterName( return $driver->formatParameterName($name); } - /** - * @throws Exception\RuntimeException - */ - protected function createSqlFromSpecificationAndParameters(array|string $specifications, array $parameters): string - { - if (is_string($specifications)) { - return vsprintf($specifications, $parameters); - } - - $parametersCount = count($parameters); - - foreach ($specifications as $specificationString => $paramSpecs) { - if ($parametersCount === count($paramSpecs)) { - break; - } - - unset($specificationString, $paramSpecs); - } - - if (! isset($specificationString)) { - throw new Exception\RuntimeException( - 'A number of parameters was found that is not supported by this specification' - ); - } - - $topParameters = []; - foreach ($parameters as $position => $paramsForPosition) { - if (isset($paramSpecs[$position]['combinedby'])) { - $multiParamValues = []; - foreach ($paramsForPosition as $multiParamsForPosition) { - if (is_array($multiParamsForPosition)) { - $ppCount = count($multiParamsForPosition); - } else { - $ppCount = 1; - $multiParamsForPosition = [$multiParamsForPosition]; - } - - if (! isset($paramSpecs[$position][$ppCount])) { - throw new Exception\RuntimeException(sprintf( - 'A number of parameters (%d) was found that is not supported by this specification', - $ppCount - )); - } - - $multiParamValues[] = vsprintf($paramSpecs[$position][$ppCount], $multiParamsForPosition); - } - - $topParameters[] = implode($paramSpecs[$position]['combinedby'], $multiParamValues); - } elseif ($paramSpecs[$position] !== null) { - $ppCount = count($paramsForPosition); - if (! isset($paramSpecs[$position][$ppCount])) { - throw new Exception\RuntimeException(sprintf( - 'A number of parameters (%d) was found that is not supported by this specification', - $ppCount - )); - } - - $topParameters[] = vsprintf($paramSpecs[$position][$ppCount], $paramsForPosition); - } else { - $topParameters[] = $paramsForPosition; - } - } - - return vsprintf($specificationString, $topParameters); - } - - protected function processSubSelect( - Select $subselect, + protected function processIdentifiersArgument( + ArgumentInterface $argument, PlatformInterface $platform, - ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null ): string { - if ($this instanceof PlatformDecoratorInterface) { - $decorator = clone $this; - $decorator->setSubject($subselect); - } else { - $decorator = $subselect; - } - - if ($parameterContainer instanceof ParameterContainer) { - $processInfoContext = $decorator instanceof PlatformDecoratorInterface ? $subselect : $decorator; - $this->processInfo['subselectCount']++; - $processInfoContext->processInfo['subselectCount'] = $this->processInfo['subselectCount']; - $processInfoContext->processInfo['paramPrefix'] = 'subselect' - . $processInfoContext->processInfo['subselectCount']; - - $sql = $decorator->buildSqlString($platform, $driver, $parameterContainer); - $this->processInfo['subselectCount'] = $decorator->processInfo['subselectCount']; + $identifiers = $argument->getValue(); + $processedIdentifiers = []; - return $sql; + foreach ($identifiers as $identifier) { + $processedIdentifiers[] = $platform->quoteIdentifierInFragment($identifier); } - return $decorator->buildSqlString($platform, $driver, $parameterContainer); + return implode(', ', $processedIdentifiers); } /** @@ -352,9 +325,9 @@ protected function processJoin( ?Join $joins, PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null - ): array|null { - if ($joins === null || $joins->count() === 0) { + ?ParameterContainer $parameterContainer = null, + ): ?array { + if (null === $joins || $joins->count() === 0) { return null; } @@ -373,11 +346,14 @@ protected function processJoin( $joinName = $joinName->getExpression(); } elseif ($joinName instanceof TableIdentifier) { $joinName = $joinName->getTableAndSchema(); - $joinName = ($joinName[1] + $joinName = ( + $joinName[1] ? $platform->quoteIdentifier($joinName[1]) . $platform->getIdentifierSeparator() - : '') . $platform->quoteIdentifier($joinName[0]); + : '' + ) + . $platform->quoteIdentifier($joinName[0]); } elseif ($joinName instanceof Select) { - $joinName = '(' . $this->processSubSelect($joinName, $platform, $driver, $parameterContainer) . ')'; + $joinName = "({$this->processSubSelect($joinName, $platform, $driver, $parameterContainer)})"; } else { $joinName = $platform->quoteIdentifier($joinName); } @@ -393,12 +369,12 @@ protected function processJoin( $platform, $driver, $parameterContainer, - 'join' . ($j + 1) . 'part' + 'join' . ($j + 1) . 'part', ); } else { $joinSpecArgArray[$j][] = $platform->quoteIdentifierInFragment( $join['on'], - ['=', 'AND', 'OR', '(', ')', 'BETWEEN', '<', '>'] + ['=', 'AND', 'OR', '(', ')', 'BETWEEN', '<', '>'], ); } } @@ -406,18 +382,56 @@ protected function processJoin( return [$joinSpecArgArray]; } + protected function processSubSelect( + Select $subselect, + PlatformInterface $platform, + ?DriverInterface $driver = null, + ?ParameterContainer $parameterContainer = null, + ): string { + if ($this instanceof PlatformDecoratorInterface) { + $decorator = clone $this; + $decorator->setSubject($subselect); + } else { + $decorator = $subselect; + } + + if ($parameterContainer instanceof ParameterContainer) { + $processInfoContext = $decorator instanceof PlatformDecoratorInterface ? $subselect : $decorator; + $this->processInfo['subselectCount']++; + $processInfoContext->processInfo['subselectCount'] = $this->processInfo['subselectCount']; + $processInfoContext->processInfo['paramPrefix'] = "subselect{$processInfoContext->processInfo['subselectCount']}"; + + $sql = $decorator->buildSqlString($platform, $driver, $parameterContainer); + $this->processInfo['subselectCount'] = $decorator->processInfo['subselectCount']; + + return $sql; + } + + return $decorator->buildSqlString($platform, $driver, $parameterContainer); + } + + /** + * Render table with alias in from/join parts + * + * @todo move TableIdentifier concatenation here + */ + protected function renderTable(string $table, ?string $alias = null): string + { + return $alias ? "{$table} AS {$alias}" : $table; + } + protected function resolveColumnValue( Select|array|string|int|bool|ExpressionInterface|null $column, PlatformInterface $platform, ?DriverInterface $driver = null, ?ParameterContainer $parameterContainer = null, - ?string $namedParameterPrefix = null + ?string $namedParameterPrefix = null, ): string { $namedParameterPrefix = $namedParameterPrefix ? $this->processInfo['paramPrefix'] . $namedParameterPrefix : $namedParameterPrefix; - $isIdentifier = false; - $fromTable = ''; + $isIdentifier = false; + $fromTable = ''; if (is_array($column)) { $isIdentifier = (bool) ($column['isIdentifier'] ?? false); $fromTable = $column['fromTable'] ?? ''; @@ -429,10 +443,10 @@ protected function resolveColumnValue( } if ($column instanceof Select) { - return '(' . $this->processSubSelect($column, $platform, $driver, $parameterContainer) . ')'; + return "({$this->processSubSelect($column, $platform, $driver, $parameterContainer)})"; } - if ($column === null) { + if (null === $column) { return 'NULL'; } @@ -445,7 +459,7 @@ protected function resolveTable( Select|string|TableIdentifier|null $table, PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): string|array|null { $schema = null; if ($table instanceof TableIdentifier) { @@ -453,7 +467,7 @@ protected function resolveTable( } if ($table instanceof Select) { - $table = '(' . $this->processSubSelect($table, $platform, $driver, $parameterContainer) . ')'; + $table = "({$this->processSubSelect($table, $platform, $driver, $parameterContainer)})"; } elseif ($table) { $table = $platform->quoteIdentifier($table); } @@ -464,15 +478,4 @@ protected function resolveTable( return $table; } - - protected function localizeVariables(): void - { - if (! $this instanceof PlatformDecoratorInterface) { - return; - } - - foreach (get_object_vars($this->subject) as $name => $value) { - $this->{$name} = $value; - } - } } diff --git a/src/Sql/Argument.php b/src/Sql/Argument.php index c2d970672..1e9e70155 100644 --- a/src/Sql/Argument.php +++ b/src/Sql/Argument.php @@ -16,16 +16,6 @@ */ final class Argument { - public static function value(null|string|int|float|bool $value): Value - { - return new Value($value); - } - - public static function values(array $values): Values - { - return new Values($values); - } - public static function identifier(string $identifier): Identifier { return new Identifier($identifier); @@ -48,4 +38,14 @@ public static function select(ExpressionInterface|SqlInterface $select): Select { return new Select($select); } + + public static function value(string|int|float|bool|null $value): Value + { + return new Value($value); + } + + public static function values(array $values): Values + { + return new Values($values); + } } diff --git a/src/Sql/Argument/Identifier.php b/src/Sql/Argument/Identifier.php index da3926152..016938480 100644 --- a/src/Sql/Argument/Identifier.php +++ b/src/Sql/Argument/Identifier.php @@ -4,6 +4,7 @@ namespace PhpDb\Sql\Argument; +use Override; use PhpDb\Sql\ArgumentInterface; use PhpDb\Sql\ArgumentType; @@ -15,22 +16,24 @@ final readonly class Identifier implements ArgumentInterface { public function __construct( - private string $identifier - ) { + private string $identifier, + ) {} + + #[Override] + public function getSpecification(): string + { + return '%s'; } + #[Override] public function getType(): ArgumentType { return ArgumentType::Identifier; } + #[Override] public function getValue(): string { return $this->identifier; } - - public function getSpecification(): string - { - return '%s'; - } } diff --git a/src/Sql/Argument/Identifiers.php b/src/Sql/Argument/Identifiers.php index 54d80d982..2e27955ed 100644 --- a/src/Sql/Argument/Identifiers.php +++ b/src/Sql/Argument/Identifiers.php @@ -4,6 +4,7 @@ namespace PhpDb\Sql\Argument; +use Override; use PhpDb\Sql\ArgumentInterface; use PhpDb\Sql\ArgumentType; @@ -31,6 +32,16 @@ public function __construct(array $identifiers) $this->identifiers = array_values($identifiers); } + #[Override] + public function getSpecification(): string + { + $count = count($this->identifiers); + return $count > 0 + ? '(' . implode(', ', array_fill(0, $count, '%s')) . ')' + : '(NULL)'; + } + + #[Override] public function getType(): ArgumentType { return ArgumentType::Identifiers; @@ -39,16 +50,9 @@ public function getType(): ArgumentType /** * @return list */ + #[Override] public function getValue(): array { return $this->identifiers; } - - public function getSpecification(): string - { - $count = count($this->identifiers); - return $count > 0 - ? '(' . implode(', ', array_fill(0, $count, '%s')) . ')' - : '(NULL)'; - } } diff --git a/src/Sql/Argument/Literal.php b/src/Sql/Argument/Literal.php index 497e6d9c6..73274026c 100644 --- a/src/Sql/Argument/Literal.php +++ b/src/Sql/Argument/Literal.php @@ -4,6 +4,7 @@ namespace PhpDb\Sql\Argument; +use Override; use PhpDb\Sql\ArgumentInterface; use PhpDb\Sql\ArgumentType; @@ -16,22 +17,24 @@ final readonly class Literal implements ArgumentInterface { public function __construct( - private string $literal - ) { + private string $literal, + ) {} + + #[Override] + public function getSpecification(): string + { + return '%s'; } + #[Override] public function getType(): ArgumentType { return ArgumentType::Literal; } + #[Override] public function getValue(): string { return $this->literal; } - - public function getSpecification(): string - { - return '%s'; - } } diff --git a/src/Sql/Argument/Select.php b/src/Sql/Argument/Select.php index 2a760ce9d..d77f852c7 100644 --- a/src/Sql/Argument/Select.php +++ b/src/Sql/Argument/Select.php @@ -4,6 +4,7 @@ namespace PhpDb\Sql\Argument; +use Override; use PhpDb\Sql\ArgumentInterface; use PhpDb\Sql\ArgumentType; use PhpDb\Sql\ExpressionInterface; @@ -17,22 +18,24 @@ final readonly class Select implements ArgumentInterface { public function __construct( - private ExpressionInterface|SqlInterface $select - ) { + private ExpressionInterface|SqlInterface $select, + ) {} + + #[Override] + public function getSpecification(): string + { + return '%s'; } + #[Override] public function getType(): ArgumentType { return ArgumentType::Select; } + #[Override] public function getValue(): ExpressionInterface|SqlInterface { return $this->select; } - - public function getSpecification(): string - { - return '%s'; - } } diff --git a/src/Sql/Argument/Value.php b/src/Sql/Argument/Value.php index 4c13c2ffe..9092166d2 100644 --- a/src/Sql/Argument/Value.php +++ b/src/Sql/Argument/Value.php @@ -4,6 +4,7 @@ namespace PhpDb\Sql\Argument; +use Override; use PhpDb\Sql\ArgumentInterface; use PhpDb\Sql\ArgumentType; @@ -18,22 +19,24 @@ * @param null|string|int|float|bool $value Scalar value, or null */ public function __construct( - private null|string|int|float|bool $value - ) { + private string|int|float|bool|null $value, + ) {} + + #[Override] + public function getSpecification(): string + { + return '%s'; } + #[Override] public function getType(): ArgumentType { return ArgumentType::Value; } - public function getValue(): null|string|int|float|bool + #[Override] + public function getValue(): string|int|float|bool|null { return $this->value; } - - public function getSpecification(): string - { - return '%s'; - } } diff --git a/src/Sql/Argument/Values.php b/src/Sql/Argument/Values.php index 6a0c34062..23e869db5 100644 --- a/src/Sql/Argument/Values.php +++ b/src/Sql/Argument/Values.php @@ -4,6 +4,7 @@ namespace PhpDb\Sql\Argument; +use Override; use PhpDb\Sql\ArgumentInterface; use PhpDb\Sql\ArgumentType; @@ -31,6 +32,16 @@ public function __construct(array $values) $this->values = array_values($values); } + #[Override] + public function getSpecification(): string + { + $count = count($this->values); + return $count > 0 + ? '(' . implode(', ', array_fill(0, $count, '%s')) . ')' + : '(NULL)'; + } + + #[Override] public function getType(): ArgumentType { return ArgumentType::Values; @@ -39,16 +50,9 @@ public function getType(): ArgumentType /** * @return list */ + #[Override] public function getValue(): array { return $this->values; } - - public function getSpecification(): string - { - $count = count($this->values); - return $count > 0 - ? '(' . implode(', ', array_fill(0, $count, '%s')) . ')' - : '(NULL)'; - } } diff --git a/src/Sql/ArgumentInterface.php b/src/Sql/ArgumentInterface.php index 9a9b356ba..b4fe488c9 100644 --- a/src/Sql/ArgumentInterface.php +++ b/src/Sql/ArgumentInterface.php @@ -6,9 +6,9 @@ interface ArgumentInterface { + public function getSpecification(): string; + public function getType(): ArgumentType; public function getValue(): ExpressionInterface|SqlInterface|string|int|float|bool|array|null; - - public function getSpecification(): string; } diff --git a/src/Sql/Combine.php b/src/Sql/Combine.php index aafb7fac9..ceb269cad 100644 --- a/src/Sql/Combine.php +++ b/src/Sql/Combine.php @@ -11,7 +11,6 @@ use function array_key_exists; use function array_keys; -use function array_merge; use function is_array; use function str_replace; use function strtoupper; @@ -43,13 +42,40 @@ class Combine extends AbstractPreparableSql public function __construct( Select|array|null $select = null, string $type = self::COMBINE_UNION, - string $modifier = '' + string $modifier = '', ) { if ($select) { $this->combine($select, $type, $modifier); } } + public function alignColumns(): static + { + if (! $this->combine) { + return $this; + } + + $allColumns = []; + foreach ($this->combine as $combine) { + $allColumns = [ + ...$allColumns, + ...$combine['select']->getRawState(self::COLUMNS), + ]; + } + + foreach ($this->combine as $combine) { + $combineColumns = $combine['select']->getRawState(self::COLUMNS); + $aligned = []; + foreach (array_keys($allColumns) as $alias) { + $aligned[$alias] = $combineColumns[$alias] ?? new Predicate\Expression('NULL'); + } + + $combine['select']->columns($aligned, false); + } + + return $this; + } + /** * Create combine clause * @@ -66,7 +92,7 @@ public function combine(Select|array $select, string $type = self::COMBINE_UNION $this->combine( $combine[0], $combine[1] ?? $type, - $combine[2] ?? $modifier + $combine[2] ?? $modifier, ); } @@ -82,19 +108,25 @@ public function combine(Select|array $select, string $type = self::COMBINE_UNION } /** - * Create union clause + * Create except clause */ - public function union(Select|array $select, string $modifier = ''): static + public function except(Select|array $select, string $modifier = ''): static { - return $this->combine($select, self::COMBINE_UNION, $modifier); + return $this->combine($select, self::COMBINE_EXCEPT, $modifier); } /** - * Create except clause + * Get raw state */ - public function except(Select|array $select, string $modifier = ''): static + public function getRawState(?string $key = null): mixed { - return $this->combine($select, self::COMBINE_EXCEPT, $modifier); + $rawState = [ + self::COMBINE => $this->combine, + self::COLUMNS => $this->combine + ? $this->combine[0]['select']->getRawState(self::COLUMNS) + : [], + ]; + return isset($key) && array_key_exists($key, $rawState) ? $rawState[$key] : $rawState; } /** @@ -105,6 +137,14 @@ public function intersect(Select|array $select, string $modifier = ''): static return $this->combine($select, self::COMBINE_INTERSECT, $modifier); } + /** + * Create union clause + */ + public function union(Select|array $select, string $modifier = ''): static + { + return $this->combine($select, self::COMBINE_UNION, $modifier); + } + /** * Build sql string */ @@ -112,7 +152,7 @@ public function intersect(Select|array $select, string $modifier = ''): static protected function buildSqlString( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): string { if (! $this->combine) { return ''; @@ -120,62 +160,21 @@ protected function buildSqlString( $sql = ''; foreach ($this->combine as $i => $combine) { - $type = $i === 0 + $type = 0 === $i ? '' : strtoupper( $combine['modifier'] ? "{$combine['type']} {$combine['modifier']}" - : $combine['type'] + : $combine['type'], ); $select = $this->processSubSelect($combine['select'], $platform, $driver, $parameterContainer); - $sql .= str_replace( + $sql .= str_replace( ['%1$s', '%2$s'], [$type, $select], - $this->specifications[self::COMBINE] + $this->specifications[self::COMBINE], ); } return trim($sql, ' '); } - - public function alignColumns(): static - { - if (! $this->combine) { - return $this; - } - - $allColumns = []; - foreach ($this->combine as $combine) { - $allColumns = array_merge( - $allColumns, - $combine['select']->getRawState(self::COLUMNS) - ); - } - - foreach ($this->combine as $combine) { - $combineColumns = $combine['select']->getRawState(self::COLUMNS); - $aligned = []; - foreach (array_keys($allColumns) as $alias) { - $aligned[$alias] = $combineColumns[$alias] ?? new Predicate\Expression('NULL'); - } - - $combine['select']->columns($aligned, false); - } - - return $this; - } - - /** - * Get raw state - */ - public function getRawState(?string $key = null): mixed - { - $rawState = [ - self::COMBINE => $this->combine, - self::COLUMNS => $this->combine - ? $this->combine[0]['select']->getRawState(self::COLUMNS) - : [], - ]; - return isset($key) && array_key_exists($key, $rawState) ? $rawState[$key] : $rawState; - } } diff --git a/src/Sql/Ddl/AlterTable.php b/src/Sql/Ddl/AlterTable.php index f85849f4a..b824cade4 100644 --- a/src/Sql/Ddl/AlterTable.php +++ b/src/Sql/Ddl/AlterTable.php @@ -97,16 +97,16 @@ public function __construct(string|TableIdentifier $table = '') } } - public function setTable(string|TableIdentifier $name): static + public function addColumn(Column\ColumnInterface $column): static { - $this->table = $name; + $this->addColumns[] = $column; return $this; } - public function addColumn(Column\ColumnInterface $column): static + public function addConstraint(Constraint\ConstraintInterface $constraint): static { - $this->addColumns[] = $column; + $this->addConstraints[] = $constraint; return $this; } @@ -132,13 +132,6 @@ public function dropConstraint(string $name): static return $this; } - public function addConstraint(Constraint\ConstraintInterface $constraint): static - { - $this->addConstraints[] = $constraint; - - return $this; - } - /** * @return static Provides a fluent interface */ @@ -149,18 +142,6 @@ public function dropIndex(string $name): static return $this; } - public function setOption(string $name, Literal|bool|int|string $value): static - { - $this->options[$name] = $value; - return $this; - } - - public function setOptions(array $options): static - { - $this->options = $options; - return $this; - } - public function getOptions(): array { return $this->options; @@ -182,10 +163,23 @@ public function getRawState(?string $key = null): array|string return isset($key) && array_key_exists($key, $rawState) ? $rawState[$key] : $rawState; } - /** @return string[] */ - protected function processTable(?PlatformInterface $adapterPlatform = null): array + public function setOption(string $name, Literal|bool|int|string $value): static { - return [$this->resolveTable($this->table, $adapterPlatform)]; + $this->options[$name] = $value; + return $this; + } + + public function setOptions(array $options): static + { + $this->options = $options; + return $this; + } + + public function setTable(string|TableIdentifier $name): static + { + $this->table = $name; + + return $this; } /** @@ -202,6 +196,20 @@ protected function processAddColumns(?PlatformInterface $adapterPlatform = null) return [$sqls]; } + /** + * @return string[][] + * @psalm-return list{list{0?: string,...}} + */ + protected function processAddConstraints(?PlatformInterface $adapterPlatform = null): array + { + $sqls = []; + foreach ($this->addConstraints as $constraint) { + $sqls[] = $this->processExpression($constraint, $adapterPlatform); + } + + return [$sqls]; + } + /** * @return string[][][] * @psalm-return list{list{0?: list{string, string},...}} @@ -233,20 +241,6 @@ protected function processDropColumns(?PlatformInterface $adapterPlatform = null return [$sqls]; } - /** - * @return string[][] - * @psalm-return list{list{0?: string,...}} - */ - protected function processAddConstraints(?PlatformInterface $adapterPlatform = null): array - { - $sqls = []; - foreach ($this->addConstraints as $constraint) { - $sqls[] = $this->processExpression($constraint, $adapterPlatform); - } - - return [$sqls]; - } - /** * @return string[][] * @psalm-return list{list{0?: string,...}} @@ -275,6 +269,12 @@ protected function processDropIndexes(?PlatformInterface $adapterPlatform = null return [$sqls]; } + /** @return string[] */ + protected function processTable(?PlatformInterface $adapterPlatform = null): array + { + return [$this->resolveTable($this->table, $adapterPlatform)]; + } + /** * @return string[][]|null */ @@ -296,7 +296,7 @@ protected function processTableOptions(?PlatformInterface $adapterPlatform = nul } else { $value = $adapterPlatform->quoteTrustedValue($value); } - $sqls[] = $key . ' = ' . $value; + $sqls[] = "{$key} = {$value}"; } return [$sqls]; diff --git a/src/Sql/Ddl/Column/AbstractLengthColumn.php b/src/Sql/Ddl/Column/AbstractLengthColumn.php index b140712ac..3eeb65b4e 100644 --- a/src/Sql/Ddl/Column/AbstractLengthColumn.php +++ b/src/Sql/Ddl/Column/AbstractLengthColumn.php @@ -20,40 +20,40 @@ public function __construct( ?int $length = null, bool $nullable = false, mixed $default = null, - array $options = [] + array $options = [], ) { $this->setLength($length); parent::__construct($name, $nullable, $default, $options); } - public function setLength(?int $length = 0): static + /** @inheritDoc */ + #[Override] + public function getExpressionData(): array { - $this->length = $length; + $expressionData = parent::getExpressionData(); - return $this; + if ($this->getLengthExpression() !== '' && $this->getLengthExpression() !== '0') { + array_splice($expressionData['values'], 2, 0, [new Literal($this->getLengthExpression())]); + } + + return $expressionData; } - public function getLength(): int|null + public function getLength(): ?int { return $this->length; } - protected function getLengthExpression(): string + public function setLength(?int $length = 0): static { - return (string) $this->length; + $this->length = $length; + + return $this; } - /** @inheritDoc */ - #[Override] - public function getExpressionData(): array + protected function getLengthExpression(): string { - $expressionData = parent::getExpressionData(); - - if ($this->getLengthExpression() !== '' && $this->getLengthExpression() !== '0') { - array_splice($expressionData['values'], 2, 0, [new Literal($this->getLengthExpression())]); - } - - return $expressionData; + return (string) $this->length; } } diff --git a/src/Sql/Ddl/Column/AbstractPrecisionColumn.php b/src/Sql/Ddl/Column/AbstractPrecisionColumn.php index e8d4bee00..2930f2096 100644 --- a/src/Sql/Ddl/Column/AbstractPrecisionColumn.php +++ b/src/Sql/Ddl/Column/AbstractPrecisionColumn.php @@ -16,19 +16,19 @@ public function __construct( ?int $decimal = null, bool $nullable = false, mixed $default = null, - array $options = [] + array $options = [], ) { $this->setDecimal($decimal); parent::__construct($name, $digits, $nullable, $default, $options); } - public function setDigits(?int $digits): static + public function getDecimal(): ?int { - return $this->setLength($digits); + return $this->decimal; } - public function getDigits(): int|null + public function getDigits(): ?int { return $this->getLength(); } @@ -40,16 +40,16 @@ public function setDecimal(?int $decimal): static return $this; } - public function getDecimal(): ?int + public function setDigits(?int $digits): static { - return $this->decimal; + return $this->setLength($digits); } #[Override] protected function getLengthExpression(): string { - if ($this->decimal !== null) { - return $this->length . ',' . $this->decimal; + if (null !== $this->decimal) { + return "{$this->length},{$this->decimal}"; } return (string) $this->length; diff --git a/src/Sql/Ddl/Column/AbstractTimestampColumn.php b/src/Sql/Ddl/Column/AbstractTimestampColumn.php index 79e7bb295..4709509bc 100644 --- a/src/Sql/Ddl/Column/AbstractTimestampColumn.php +++ b/src/Sql/Ddl/Column/AbstractTimestampColumn.php @@ -20,7 +20,7 @@ public function getExpressionData(): array $options = $this->getOptions(); if (isset($options['on_update'])) { - $expressionData['spec'] .= ' %s'; + $expressionData['spec'] .= ' %s'; $expressionData['values'][] = new Literal('ON UPDATE CURRENT_TIMESTAMP'); } diff --git a/src/Sql/Ddl/Column/Column.php b/src/Sql/Ddl/Column/Column.php index 42c42770f..4fec18d38 100644 --- a/src/Sql/Ddl/Column/Column.php +++ b/src/Sql/Ddl/Column/Column.php @@ -34,7 +34,7 @@ public function __construct( string $name = '', bool $nullable = false, mixed $default = null, - array $options = [] + array $options = [], ) { $this->setName($name); $this->setNullable($nullable); @@ -42,33 +42,10 @@ public function __construct( $this->setOptions($options); } - public function setName(string $name): static - { - $this->name = $name; - return $this; - } - - #[Override] - public function getName(): string - { - return $this->name; - } - - public function setNullable(bool $nullable): static - { - $this->isNullable = $nullable; - return $this; - } - - #[Override] - public function isNullable(): bool + public function addConstraint(ConstraintInterface $constraint): static { - return $this->isNullable; - } + $this->constraints[] = $constraint; - public function setDefault(string|int|float|bool|Literal|Value|null $default): static - { - $this->default = $default; return $this; } @@ -78,31 +55,6 @@ public function getDefault(): string|int|float|bool|Literal|Value|null return $this->default; } - public function setOptions(array $options): static - { - $this->options = $options; - return $this; - } - - public function setOption(string $name, bool|string $value): static - { - $this->options[$name] = $value; - return $this; - } - - #[Override] - public function getOptions(): array - { - return $this->options; - } - - public function addConstraint(ConstraintInterface $constraint): static - { - $this->constraints[] = $constraint; - - return $this; - } - /** @inheritDoc */ #[Override] public function getExpressionData(): array @@ -113,13 +65,13 @@ public function getExpressionData(): array new Literal($this->type), ]; - if ($this->isNullable === false) { + if (false === $this->isNullable) { $specParts[] = 'NOT NULL'; } else { $specParts[] = 'NULL'; } - if ($this->default !== null) { + if (null !== $this->default) { $specParts[] = 'DEFAULT %s'; $values[] = $this->default instanceof ArgumentInterface ? $this->default @@ -141,4 +93,52 @@ public function getExpressionData(): array 'values' => $values, ]; } + + #[Override] + public function getName(): string + { + return $this->name; + } + + #[Override] + public function getOptions(): array + { + return $this->options; + } + + #[Override] + public function isNullable(): bool + { + return $this->isNullable; + } + + public function setDefault(string|int|float|bool|Literal|Value|null $default): static + { + $this->default = $default; + return $this; + } + + public function setName(string $name): static + { + $this->name = $name; + return $this; + } + + public function setNullable(bool $nullable): static + { + $this->isNullable = $nullable; + return $this; + } + + public function setOption(string $name, bool|string $value): static + { + $this->options[$name] = $value; + return $this; + } + + public function setOptions(array $options): static + { + $this->options = $options; + return $this; + } } diff --git a/src/Sql/Ddl/Column/ColumnInterface.php b/src/Sql/Ddl/Column/ColumnInterface.php index b9665c5d8..9e5e69e5e 100644 --- a/src/Sql/Ddl/Column/ColumnInterface.php +++ b/src/Sql/Ddl/Column/ColumnInterface.php @@ -13,11 +13,11 @@ */ interface ColumnInterface extends ExpressionInterface { - public function getName(): string; - - public function isNullable(): bool; - public function getDefault(): string|int|float|bool|Literal|Value|null; + public function getName(): string; + public function getOptions(): array; + + public function isNullable(): bool; } diff --git a/src/Sql/Ddl/Column/Integer.php b/src/Sql/Ddl/Column/Integer.php index 84ee8f371..4c5175154 100644 --- a/src/Sql/Ddl/Column/Integer.php +++ b/src/Sql/Ddl/Column/Integer.php @@ -16,7 +16,7 @@ public function getExpressionData(): array $options = $this->getOptions(); if (isset($options['length'])) { - $expressionData['spec'] .= ' (' . $options['length'] . ')'; + $expressionData['spec'] .= " ({$options['length']})"; } return $expressionData; diff --git a/src/Sql/Ddl/Constraint/AbstractConstraint.php b/src/Sql/Ddl/Constraint/AbstractConstraint.php index aee749a69..946d149d1 100644 --- a/src/Sql/Ddl/Constraint/AbstractConstraint.php +++ b/src/Sql/Ddl/Constraint/AbstractConstraint.php @@ -24,42 +24,25 @@ abstract class AbstractConstraint implements ConstraintInterface protected array $columns = []; - public function __construct(null|array|string $columns = null, ?string $name = null) + public function __construct(array|string|null $columns = null, ?string $name = null) { - if ($columns !== null) { + if (null !== $columns) { $this->setColumns($columns); } - if ($name !== null) { + if (null !== $name) { $this->setName($name); } } - public function setName(string $name): static - { - $this->name = $name; - return $this; - } - - public function getName(): string - { - return $this->name; - } - - public function setColumns(string|array $columns): static - { - $this->columns = (array) $columns; - - return $this; - } - public function addColumn(string $column): static { $this->columns[] = $column; return $this; } - #[Override] public function getColumns(): array + #[Override] + public function getColumns(): array { return $this->columns; } @@ -71,17 +54,17 @@ public function getExpressionData(): array $specParts = []; $values = []; - if ($this->name !== '') { + if ('' !== $this->name) { $specParts[] = $this->namedSpecification; $values[] = new Identifier($this->name); } - if ($this->specification !== '') { + if ('' !== $this->specification) { $specParts[] = $this->specification; } $columnCount = count($this->columns); - if ($columnCount !== 0) { + if (0 !== $columnCount) { $columnSpec = array_fill(0, $columnCount, '%s'); $specParts[] = str_replace('%s', implode(', ', $columnSpec), $this->columnSpecification); for ($i = 0; $i < $columnCount; $i++) { @@ -94,4 +77,22 @@ public function getExpressionData(): array 'values' => $values, ]; } + + public function getName(): string + { + return $this->name; + } + + public function setColumns(string|array $columns): static + { + $this->columns = (array) $columns; + + return $this; + } + + public function setName(string $name): static + { + $this->name = $name; + return $this; + } } diff --git a/src/Sql/Ddl/Constraint/Check.php b/src/Sql/Ddl/Constraint/Check.php index 86413b07f..a6608f9b0 100644 --- a/src/Sql/Ddl/Constraint/Check.php +++ b/src/Sql/Ddl/Constraint/Check.php @@ -28,17 +28,18 @@ public function __construct($expression, ?string $name) } /** @inheritDoc */ - #[Override] public function getExpressionData(): array + #[Override] + public function getExpressionData(): array { $specParts = []; $values = []; - if ($this->name !== '') { + if ('' !== $this->name) { $specParts[] = $this->namedSpecification; $values[] = new Identifier($this->name); } - if ($this->expression !== '') { + if ('' !== $this->expression) { $specParts[] = $this->specification; $values[] = new Literal($this->expression); } diff --git a/src/Sql/Ddl/Constraint/ForeignKey.php b/src/Sql/Ddl/Constraint/ForeignKey.php index bcbad0f3d..9a28096fe 100644 --- a/src/Sql/Ddl/Constraint/ForeignKey.php +++ b/src/Sql/Ddl/Constraint/ForeignKey.php @@ -39,56 +39,68 @@ public function __construct( string|array $columns, string $referenceTable, array|string|null $referenceColumn, - null|string $onDeleteRule = null, - null|string $onUpdateRule = null + ?string $onDeleteRule = null, + ?string $onUpdateRule = null, ) { parent::__construct($columns, $name); $this->setReferenceTable($referenceTable); - if ($referenceColumn !== null) { + if (null !== $referenceColumn) { $this->setReferenceColumn($referenceColumn); } - if ($onDeleteRule !== null) { + if (null !== $onDeleteRule) { $this->setOnDeleteRule($onDeleteRule); } - if ($onUpdateRule !== null) { + if (null !== $onUpdateRule) { $this->setOnUpdateRule($onUpdateRule); } } - public function getReferenceTable(): string + /** @inheritDoc */ + #[Override] + public function getExpressionData(): array { - return $this->referenceTable; - } + $expressionData = parent::getExpressionData(); + $colCount = count($this->referenceColumn); - public function setReferenceTable(string $referenceTable): static - { - $this->referenceTable = $referenceTable; + $expressionData['spec'] .= " {$this->referenceSpecification[0]}"; + $expressionData['values'][] = new Identifier($this->referenceTable); - return $this; + if (0 !== $colCount) { + $expressionData['spec'] .= ' (' . implode(', ', array_fill(0, $colCount, '%s')) . ')'; + foreach ($this->referenceColumn as $column) { + $expressionData['values'][] = new Identifier($column); + } + } + + $expressionData['spec'] .= " {$this->referenceSpecification[1]}"; + $expressionData['values'][] = new Literal($this->onDeleteRule); + $expressionData['values'][] = new Literal($this->onUpdateRule); + + return $expressionData; } - public function getReferenceColumn(): array + public function getOnDeleteRule(): string { - return $this->referenceColumn; + return $this->onDeleteRule; } - /** - * @param string[]|string $referenceColumn - */ - public function setReferenceColumn(array|string $referenceColumn): static + public function getOnUpdateRule(): string { - $this->referenceColumn = (array) $referenceColumn; + return $this->onUpdateRule; + } - return $this; + public function getReferenceColumn(): array + { + return $this->referenceColumn; } - public function getOnDeleteRule(): string + public function getReferenceTable(): string { - return $this->onDeleteRule; + return $this->referenceTable; } public function setOnDeleteRule(string $onDeleteRule): static @@ -98,11 +110,6 @@ public function setOnDeleteRule(string $onDeleteRule): static return $this; } - public function getOnUpdateRule(): string - { - return $this->onUpdateRule; - } - public function setOnUpdateRule(string $onUpdateRule): static { $this->onUpdateRule = $onUpdateRule; @@ -110,27 +117,20 @@ public function setOnUpdateRule(string $onUpdateRule): static return $this; } - /** @inheritDoc */ - #[Override] - public function getExpressionData(): array + /** + * @param string[]|string $referenceColumn + */ + public function setReferenceColumn(array|string $referenceColumn): static { - $expressionData = parent::getExpressionData(); - $colCount = count($this->referenceColumn); - - $expressionData['spec'] .= ' ' . $this->referenceSpecification[0]; - $expressionData['values'][] = new Identifier($this->referenceTable); + $this->referenceColumn = (array) $referenceColumn; - if ($colCount !== 0) { - $expressionData['spec'] .= ' (' . implode(', ', array_fill(0, $colCount, '%s')) . ')'; - foreach ($this->referenceColumn as $column) { - $expressionData['values'][] = new Identifier($column); - } - } + return $this; + } - $expressionData['spec'] .= ' ' . $this->referenceSpecification[1]; - $expressionData['values'][] = new Literal($this->onDeleteRule); - $expressionData['values'][] = new Literal($this->onUpdateRule); + public function setReferenceTable(string $referenceTable): static + { + $this->referenceTable = $referenceTable; - return $expressionData; + return $this; } } diff --git a/src/Sql/Ddl/CreateTable.php b/src/Sql/Ddl/CreateTable.php index 6d0ddb2bb..70596816d 100644 --- a/src/Sql/Ddl/CreateTable.php +++ b/src/Sql/Ddl/CreateTable.php @@ -63,44 +63,53 @@ public function __construct(string|TableIdentifier $table = '', bool $isTemporar $this->setTemporary($isTemporary); } - public function ifNotExists(bool $ifNotExists = true): static + public function addColumn(Column\ColumnInterface $column): static { - $this->ifNotExists = $ifNotExists; + $this->columns[] = $column; return $this; } - public function getIfNotExists(): bool + public function addConstraint(Constraint\ConstraintInterface $constraint): static { - return $this->ifNotExists; + $this->constraints[] = $constraint; + return $this; } - public function setTemporary(string|int|bool $temporary): static + public function getIfNotExists(): bool { - $this->isTemporary = (bool) $temporary; - return $this; + return $this->ifNotExists; } - public function isTemporary(): bool + public function getOptions(): array { - return $this->isTemporary; + return $this->options; } - public function setTable(string $name): static + /** + * @return ((Column\ColumnInterface|string)[]|Column\ColumnInterface|string)[]|string + * @psalm-return array|string>|string + */ + public function getRawState(?string $key = null): array|string { - $this->table = $name; - return $this; + $rawState = [ + self::COLUMNS => $this->columns, + self::CONSTRAINTS => $this->constraints, + self::TABLE => $this->table, + self::TABLE_OPTIONS => $this->options, + ]; + + return isset($key) && array_key_exists($key, $rawState) ? $rawState[$key] : $rawState; } - public function addColumn(Column\ColumnInterface $column): static + public function ifNotExists(bool $ifNotExists = true): static { - $this->columns[] = $column; + $this->ifNotExists = $ifNotExists; return $this; } - public function addConstraint(Constraint\ConstraintInterface $constraint): static + public function isTemporary(): bool { - $this->constraints[] = $constraint; - return $this; + return $this->isTemporary; } public function setOption(string $name, Literal|bool|int|string $value): static @@ -115,37 +124,16 @@ public function setOptions(array $options): static return $this; } - public function getOptions(): array - { - return $this->options; - } - - /** - * @return ((Column\ColumnInterface|string)[]|Column\ColumnInterface|string)[]|string - * @psalm-return array|string>|string - */ - public function getRawState(?string $key = null): array|string + public function setTable(string $name): static { - $rawState = [ - self::COLUMNS => $this->columns, - self::CONSTRAINTS => $this->constraints, - self::TABLE => $this->table, - self::TABLE_OPTIONS => $this->options, - ]; - - return isset($key) && array_key_exists($key, $rawState) ? $rawState[$key] : $rawState; + $this->table = $name; + return $this; } - /** - * @return string[] - */ - protected function processTable(?PlatformInterface $adapterPlatform = null): array + public function setTemporary(string|int|bool $temporary): static { - return [ - $this->isTemporary ? 'TEMPORARY ' : '', - $this->ifNotExists ? 'IF NOT EXISTS ' : '', - $this->resolveTable($this->table, $adapterPlatform), - ]; + $this->isTemporary = (bool) $temporary; + return $this; } /** @@ -166,7 +154,7 @@ protected function processColumns(?PlatformInterface $adapterPlatform = null): ? return [$sqls]; } - protected function processCombinedby(?PlatformInterface $adapterPlatform = null): string|null + protected function processCombinedby(?PlatformInterface $adapterPlatform = null): ?string { if ($this->constraints && $this->columns) { return $this->specifications['combinedBy']; @@ -201,6 +189,18 @@ protected function processStatementEnd(?PlatformInterface $adapterPlatform = nul return ["\n)"]; } + /** + * @return string[] + */ + protected function processTable(?PlatformInterface $adapterPlatform = null): array + { + return [ + $this->isTemporary ? 'TEMPORARY ' : '', + $this->ifNotExists ? 'IF NOT EXISTS ' : '', + $this->resolveTable($this->table, $adapterPlatform), + ]; + } + /** * @return string[]|null */ @@ -222,7 +222,7 @@ protected function processTableOptions(?PlatformInterface $adapterPlatform = nul } else { $value = $adapterPlatform->quoteTrustedValue($value); } - $parts[] = $key . ' = ' . $value; + $parts[] = "{$key} = {$value}"; } return [implode(' ', $parts)]; diff --git a/src/Sql/Ddl/DropTable.php b/src/Sql/Ddl/DropTable.php index dd0d85a5a..7f879218e 100644 --- a/src/Sql/Ddl/DropTable.php +++ b/src/Sql/Ddl/DropTable.php @@ -25,15 +25,15 @@ public function __construct(string|TableIdentifier $table = '') $this->table = $table; } - public function ifExists(bool $ifExists = true): static + public function getIfExists(): bool { - $this->ifExists = $ifExists; - return $this; + return $this->ifExists; } - public function getIfExists(): bool + public function ifExists(bool $ifExists = true): static { - return $this->ifExists; + $this->ifExists = $ifExists; + return $this; } /** @return string[] */ diff --git a/src/Sql/Ddl/Index/AbstractIndex.php b/src/Sql/Ddl/Index/AbstractIndex.php index fb179bfd2..bf6cedec9 100644 --- a/src/Sql/Ddl/Index/AbstractIndex.php +++ b/src/Sql/Ddl/Index/AbstractIndex.php @@ -6,6 +6,4 @@ use PhpDb\Sql\Ddl\Constraint\AbstractConstraint; -abstract class AbstractIndex extends AbstractConstraint -{ -} +abstract class AbstractIndex extends AbstractConstraint {} diff --git a/src/Sql/Ddl/Index/Index.php b/src/Sql/Ddl/Index/Index.php index a3802782f..38526ef5d 100644 --- a/src/Sql/Ddl/Index/Index.php +++ b/src/Sql/Ddl/Index/Index.php @@ -20,24 +20,13 @@ class Index extends AbstractIndex protected ?string $type = null; - public function __construct(null|array|string $columns, ?string $name = null, array $lengths = []) + public function __construct(array|string|null $columns, ?string $name = null, array $lengths = []) { parent::__construct($columns, $name); $this->lengths = $lengths; } - public function setType(string $type): static - { - $this->type = $type; - return $this; - } - - public function getType(): ?string - { - return $this->type; - } - /** @inheritDoc */ #[Override] public function getExpressionData(): array @@ -51,7 +40,7 @@ public function getExpressionData(): array $values[] = new Identifier($this->columns[$i]); if (isset($this->lengths[$i])) { - $specPart .= '(' . $this->lengths[$i] . ')'; + $specPart .= "({$this->lengths[$i]})"; } $specParts[] = $specPart; @@ -59,8 +48,8 @@ public function getExpressionData(): array $spec = str_replace('...', implode(', ', $specParts), $this->specification); - if ($this->type !== null) { - $spec .= ' USING %s'; + if (null !== $this->type) { + $spec .= ' USING %s'; $values[] = new Literal($this->type); } @@ -69,4 +58,15 @@ public function getExpressionData(): array 'values' => $values, ]; } + + public function getType(): ?string + { + return $this->type; + } + + public function setType(string $type): static + { + $this->type = $type; + return $this; + } } diff --git a/src/Sql/Delete.php b/src/Sql/Delete.php index 28dd10007..72006aaaa 100644 --- a/src/Sql/Delete.php +++ b/src/Sql/Delete.php @@ -9,8 +9,6 @@ use PhpDb\Adapter\ParameterContainer; use PhpDb\Adapter\Platform\PlatformInterface; use PhpDb\Sql\Predicate\PredicateInterface; -use PhpDb\Sql\TableIdentifier; -use PhpDb\Sql\Where; use function array_key_exists; use function str_replace; @@ -28,7 +26,7 @@ class Delete extends AbstractPreparableSql final public const SPECIFICATION_WHERE = 'where'; - /**@#-*/ + /** @#- */ /** * {@inheritDoc} @@ -54,11 +52,6 @@ public function __construct(string|TableIdentifier|null $table = null) } } - private function getWhere(): Where - { - return $this->where ??= new Where(); - } - /** * Create from statement */ @@ -75,7 +68,7 @@ public function getRawState(?string $key = null): mixed 'table' => $this->table, 'where' => $this->getWhere(), ]; - return $key !== null && array_key_exists($key, $rawState) ? $rawState[$key] : $rawState; + return null !== $key && array_key_exists($key, $rawState) ? $rawState[$key] : $rawState; } /** @@ -85,7 +78,7 @@ public function getRawState(?string $key = null): mixed */ public function where( PredicateInterface|array|Closure|string|Where $predicate, - string $combination = Predicate\PredicateSet::OP_AND + string $combination = Predicate\PredicateSet::OP_AND, ): static { if ($predicate instanceof Where) { $this->where = $predicate; @@ -99,31 +92,36 @@ public function where( protected function processDelete( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): string { return str_replace( '%1$s', $this->resolveTable($this->table, $platform, $driver, $parameterContainer), - $this->specifications[static::SPECIFICATION_DELETE] + $this->specifications[static::SPECIFICATION_DELETE], ); } protected function processWhere( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): ?string { - if ($this->where === null || $this->where->count() === 0) { + if (null === $this->where || $this->where->count() === 0) { return null; } return str_replace( '%1$s', $this->processExpression($this->where, $platform, $driver, $parameterContainer, 'where'), - $this->specifications[static::SPECIFICATION_WHERE] + $this->specifications[static::SPECIFICATION_WHERE], ); } + private function getWhere(): Where + { + return $this->where ??= new Where(); + } + /** * Property overloading * Overloads "where" only. diff --git a/src/Sql/Exception/ExceptionInterface.php b/src/Sql/Exception/ExceptionInterface.php index 1016078d6..bb84751dd 100644 --- a/src/Sql/Exception/ExceptionInterface.php +++ b/src/Sql/Exception/ExceptionInterface.php @@ -6,6 +6,4 @@ use PhpDb\Exception; -interface ExceptionInterface extends Exception\ExceptionInterface -{ -} +interface ExceptionInterface extends Exception\ExceptionInterface {} diff --git a/src/Sql/Exception/InvalidArgumentException.php b/src/Sql/Exception/InvalidArgumentException.php index 5701f35e0..1f76dc1e7 100644 --- a/src/Sql/Exception/InvalidArgumentException.php +++ b/src/Sql/Exception/InvalidArgumentException.php @@ -6,6 +6,4 @@ use PhpDb\Exception; -class InvalidArgumentException extends Exception\InvalidArgumentException implements ExceptionInterface -{ -} +class InvalidArgumentException extends Exception\InvalidArgumentException implements ExceptionInterface {} diff --git a/src/Sql/Exception/RuntimeException.php b/src/Sql/Exception/RuntimeException.php index 8cdf14347..2a84cec0b 100644 --- a/src/Sql/Exception/RuntimeException.php +++ b/src/Sql/Exception/RuntimeException.php @@ -6,6 +6,4 @@ use PhpDb\Exception; -class RuntimeException extends Exception\RuntimeException implements ExceptionInterface -{ -} +class RuntimeException extends Exception\RuntimeException implements ExceptionInterface {} diff --git a/src/Sql/Expression.php b/src/Sql/Expression.php index 9903e21df..1c96c1d3c 100644 --- a/src/Sql/Expression.php +++ b/src/Sql/Expression.php @@ -35,9 +35,9 @@ class Expression extends AbstractExpression */ public function __construct( string $expression = '', - null|bool|string|float|int|array|ArgumentInterface|ExpressionInterface $parameters = [] + bool|string|float|int|array|ArgumentInterface|ExpressionInterface|null $parameters = [], ) { - if ($expression !== '') { + if ('' !== $expression) { $this->setExpression($expression); } @@ -53,54 +53,11 @@ public function __construct( $this->setParameters($parameters); } - /** - * @throws Exception\InvalidArgumentException - */ - public function setExpression(string $expression): self - { - if ($expression === '') { - throw new Exception\InvalidArgumentException('Supplied expression must not be an empty string.'); - } - - $this->expression = $expression; - return $this; - } - public function getExpression(): string { return $this->expression; } - /** - * @throws Exception\InvalidArgumentException - */ - public function setParameters( - null|bool|string|float|int|array|ExpressionInterface|ArgumentInterface $parameters = [] - ): self { - if (! is_array($parameters)) { - $parameters = [$parameters]; - } - - foreach ($parameters as $parameter) { - if (is_array($parameter)) { - $parameter = new Values($parameter); - } elseif ($parameter instanceof ExpressionInterface) { - $parameter = new SelectArgument($parameter); - } elseif (! $parameter instanceof ArgumentInterface) { - $parameter = new Value($parameter); - } - - $this->parameters[] = $parameter; - } - - return $this; - } - - public function getParameters(): array - { - return $this->parameters; - } - /** * @throws Exception\RuntimeException * @inheritDoc @@ -112,7 +69,7 @@ public function getExpressionData(): array $parametersCount = count($parameters); $specification = str_replace('%', '%%', $this->expression); - if ($parametersCount === 0) { + if (0 === $parametersCount) { return [ 'spec' => $specification, 'values' => [], @@ -126,9 +83,9 @@ public function getExpressionData(): array // faster, if the test fails then resort to regex which are slow and used rarely if ($count !== $parametersCount) { preg_match_all('/:\w*/', $specification, $matches); - if ($parametersCount !== count(array_unique($matches[0]))) { + if (count(array_unique($matches[0])) !== $parametersCount) { throw new Exception\RuntimeException( - 'The number of replacements in the expression does not match the number of parameters' + 'The number of replacements in the expression does not match the number of parameters', ); } } @@ -138,4 +95,47 @@ public function getExpressionData(): array 'values' => $parameters, ]; } + + public function getParameters(): array + { + return $this->parameters; + } + + /** + * @throws Exception\InvalidArgumentException + */ + public function setExpression(string $expression): self + { + if ('' === $expression) { + throw new Exception\InvalidArgumentException('Supplied expression must not be an empty string.'); + } + + $this->expression = $expression; + return $this; + } + + /** + * @throws Exception\InvalidArgumentException + */ + public function setParameters( + bool|string|float|int|array|ExpressionInterface|ArgumentInterface|null $parameters = [], + ): self { + if (! is_array($parameters)) { + $parameters = [$parameters]; + } + + foreach ($parameters as $parameter) { + if (is_array($parameter)) { + $parameter = new Values($parameter); + } elseif ($parameter instanceof ExpressionInterface) { + $parameter = new SelectArgument($parameter); + } elseif (! $parameter instanceof ArgumentInterface) { + $parameter = new Value($parameter); + } + + $this->parameters[] = $parameter; + } + + return $this; + } } diff --git a/src/Sql/Having.php b/src/Sql/Having.php index 4f2aa3fc1..010e74183 100644 --- a/src/Sql/Having.php +++ b/src/Sql/Having.php @@ -4,6 +4,4 @@ namespace PhpDb\Sql; -class Having extends Predicate\Predicate -{ -} +class Having extends Predicate\Predicate {} diff --git a/src/Sql/Insert.php b/src/Sql/Insert.php index 6c0e3a2df..1d39afdb8 100644 --- a/src/Sql/Insert.php +++ b/src/Sql/Insert.php @@ -46,7 +46,7 @@ class Insert extends AbstractPreparableSql protected array $columns = []; - protected null|array|Select $select = null; + protected array|Select|null $select = null; /** * Constructor @@ -58,6 +58,28 @@ public function __construct(string|TableIdentifier|null $table = null) } } + /** + * Specify columns + */ + public function columns(array $columns): static + { + $this->columns = array_flip($columns); + return $this; + } + + /** + * Get raw state + */ + public function getRawState(?string $key = null): TableIdentifier|string|array + { + $rawState = [ + 'table' => $this->table, + 'columns' => array_keys($this->columns), + 'values' => array_values($this->columns), + ]; + return null !== $key && array_key_exists($key, $rawState) ? $rawState[$key] : $rawState; + } + /** * Create INTO clause */ @@ -68,12 +90,11 @@ public function into(TableIdentifier|string|array $table): static } /** - * Specify columns + * Create INTO SELECT clause */ - public function columns(array $columns): static + public function select(Select $select): static { - $this->columns = array_flip($columns); - return $this; + return $this->values($select); } /** @@ -85,9 +106,9 @@ public function columns(array $columns): static public function values(array|Select $values, string $flag = self::VALUES_SET): static { if ($values instanceof Select) { - if ($flag === self::VALUES_MERGE) { + if (self::VALUES_MERGE === $flag) { throw new Exception\InvalidArgumentException( - 'A PhpDb\Sql\Select instance cannot be provided with the merge flag' + 'A PhpDb\Sql\Select instance cannot be provided with the merge flag', ); } @@ -95,14 +116,14 @@ public function values(array|Select $values, string $flag = self::VALUES_SET): s return $this; } - if ($this->select !== null && $flag === self::VALUES_MERGE) { + if (null !== $this->select && self::VALUES_MERGE === $flag) { throw new Exception\InvalidArgumentException( 'An array of values cannot be provided with the merge flag when a PhpDb\Sql\Select' - . ' instance already exists as the value source' + . ' instance already exists as the value source', ); } - if ($flag === self::VALUES_SET) { + if (self::VALUES_SET === $flag) { $this->columns = $this->isAssocativeArray($values) ? $values : array_combine(array_keys($this->columns), array_values($values)); @@ -115,41 +136,10 @@ public function values(array|Select $values, string $flag = self::VALUES_SET): s return $this; } - /** - * Simple test for an associative array - * - * @link http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential - */ - private function isAssocativeArray(array $array): bool - { - return array_keys($array) !== range(0, count($array) - 1); - } - - /** - * Create INTO SELECT clause - */ - public function select(Select $select): static - { - return $this->values($select); - } - - /** - * Get raw state - */ - public function getRawState(?string $key = null): TableIdentifier|string|array - { - $rawState = [ - 'table' => $this->table, - 'columns' => array_keys($this->columns), - 'values' => array_values($this->columns), - ]; - return $key !== null && array_key_exists($key, $rawState) ? $rawState[$key] : $rawState; - } - protected function processInsert( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): ?string { if ($this->select) { return null; @@ -180,7 +170,7 @@ protected function processInsert( $value, $platform, $driver, - $parameterContainer + $parameterContainer, ); } } @@ -192,14 +182,14 @@ protected function processInsert( implode(', ', $columns), implode(', ', $values), ], - $this->specifications[static::SPECIFICATION_INSERT] + $this->specifications[static::SPECIFICATION_INSERT], ); } protected function processSelect( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): ?string { if (! $this->select) { return null; @@ -217,37 +207,36 @@ protected function processSelect( $columns ? "({$columns})" : '', $selectSql, ], - $this->specifications[static::SPECIFICATION_SELECT] + $this->specifications[static::SPECIFICATION_SELECT], ); } /** - * Overloading: variable setting + * Simple test for an associative array * - * Proxies to values, using VALUES_MERGE strategy + * @link http://stackoverflow.com/questions/173400/how-to-check-if-php-array-is-associative-or-sequential */ - public function __set(string $name, mixed $value): void + private function isAssocativeArray(array $array): bool { - $this->columns[$name] = $value; + return array_keys($array) !== range(0, count($array) - 1); } /** - * Overloading: variable unset - * - * Proxies to values and columns + * Overloading: variable retrieval + * Retrieves value by column name * * @throws Exception\InvalidArgumentException - * @return void + * @return string */ - public function __unset(string $name) + public function __get(string $name): mixed { if (! array_key_exists($name, $this->columns)) { throw new Exception\InvalidArgumentException( - 'The key ' . $name . ' was not found in this objects column list' + "The key {$name} was not found in this objects column list", ); } - unset($this->columns[$name]); + return $this->columns[$name]; } /** @@ -263,20 +252,31 @@ public function __isset(string $name) } /** - * Overloading: variable retrieval - * Retrieves value by column name + * Overloading: variable setting + * + * Proxies to values, using VALUES_MERGE strategy + */ + public function __set(string $name, mixed $value): void + { + $this->columns[$name] = $value; + } + + /** + * Overloading: variable unset + * + * Proxies to values and columns * * @throws Exception\InvalidArgumentException - * @return string + * @return void */ - public function __get(string $name): mixed + public function __unset(string $name) { if (! array_key_exists($name, $this->columns)) { throw new Exception\InvalidArgumentException( - 'The key ' . $name . ' was not found in this objects column list' + "The key {$name} was not found in this objects column list", ); } - return $this->columns[$name]; + unset($this->columns[$name]); } } diff --git a/src/Sql/Join.php b/src/Sql/Join.php index 2af0df5ee..84cca0127 100644 --- a/src/Sql/Join.php +++ b/src/Sql/Join.php @@ -53,13 +53,13 @@ class Join implements Iterator, Countable protected array $joins = []; /** - * Rewind iterator. + * Get count of attached predicates */ #[Override] #[ReturnTypeWillChange] - public function rewind(): void + public function count(): int { - $this->position = 0; + return count($this->joins); } /** @@ -72,36 +72,6 @@ public function current(): array return $this->joins[$this->position]; } - /** - * Return the current iterator index. - */ - #[Override] - #[ReturnTypeWillChange] - public function key(): int - { - return $this->position; - } - - /** - * Advance to the next JOIN specification. - */ - #[Override] - #[ReturnTypeWillChange] - public function next(): void - { - ++$this->position; - } - - /** - * Is the iterator at a valid position? - */ - #[Override] - #[ReturnTypeWillChange] - public function valid(): bool - { - return isset($this->joins[$this->position]); - } - public function getJoins(): array { return $this->joins; @@ -122,11 +92,11 @@ public function join( array|string|TableIdentifier $name, string|Predicate\PredicateInterface $on, array|string $columns = [Select::SQL_STAR], - string $type = self::JOIN_INNER + string $type = self::JOIN_INNER, ): static { if (is_array($name) && (! is_string(key($name)) || count($name) !== 1)) { throw new Exception\InvalidArgumentException( - sprintf("join() expects '%s' as a single element associative array", array_shift($name)) + sprintf("join() expects '%s' as a single element associative array", array_shift($name)), ); } @@ -144,6 +114,26 @@ public function join( return $this; } + /** + * Return the current iterator index. + */ + #[Override] + #[ReturnTypeWillChange] + public function key(): int + { + return $this->position; + } + + /** + * Advance to the next JOIN specification. + */ + #[Override] + #[ReturnTypeWillChange] + public function next(): void + { + ++$this->position; + } + /** * Reset to an empty list of JOIN specifications. */ @@ -154,12 +144,22 @@ public function reset(): static } /** - * Get count of attached predicates + * Rewind iterator. */ #[Override] #[ReturnTypeWillChange] - public function count(): int + public function rewind(): void { - return count($this->joins); + $this->position = 0; + } + + /** + * Is the iterator at a valid position? + */ + #[Override] + #[ReturnTypeWillChange] + public function valid(): bool + { + return isset($this->joins[$this->position]); } } diff --git a/src/Sql/Literal.php b/src/Sql/Literal.php index 4a32d715e..cd0d5c7db 100644 --- a/src/Sql/Literal.php +++ b/src/Sql/Literal.php @@ -10,15 +10,20 @@ class Literal implements ExpressionInterface { - public function __construct(protected string $literal = '') - { + public function __construct( + protected string $literal = '', + ) { $this->literal = $literal; } - public function setLiteral(string $literal): static + /** @inheritDoc */ + #[Override] + public function getExpressionData(): array { - $this->literal = $literal; - return $this; + return [ + 'spec' => str_replace('%', '%%', $this->literal), + 'values' => [], + ]; } public function getLiteral(): string @@ -26,13 +31,9 @@ public function getLiteral(): string return $this->literal; } - /** @inheritDoc */ - #[Override] - public function getExpressionData(): array + public function setLiteral(string $literal): static { - return [ - 'spec' => str_replace('%', '%%', $this->literal), - 'values' => [], - ]; + $this->literal = $literal; + return $this; } } diff --git a/src/Sql/Platform/AbstractPlatform.php b/src/Sql/Platform/AbstractPlatform.php index cc8d5d67a..5aee97089 100644 --- a/src/Sql/Platform/AbstractPlatform.php +++ b/src/Sql/Platform/AbstractPlatform.php @@ -4,6 +4,7 @@ namespace PhpDb\Sql\Platform; +use Override; use PhpDb\Adapter\AdapterInterface; use PhpDb\Adapter\Platform\PlatformInterface; use PhpDb\Adapter\StatementContainerInterface; @@ -18,53 +19,59 @@ class AbstractPlatform implements PlatformDecoratorInterface, PreparableSqlInter protected array $decorators = []; /** - * {@inheritDoc} + * @return array|PlatformDecoratorInterface[] */ - public function setSubject($subject): static + public function getDecorators(): array { - $this->subject = $subject; - - return $this; + return $this->decorators; } - public function setTypeDecorator(string $type, PlatformDecoratorInterface $decorator): void + /** + * {@inheritDoc} + * + * @throws Exception\RuntimeException + */ + #[Override] + public function getSqlString(?PlatformInterface $adapterPlatform = null): string { - $this->decorators[$type] = $decorator; + if (! $this->subject instanceof SqlInterface) { + throw new Exception\RuntimeException( + 'The subject does not appear to implement PhpDb\Sql\SqlInterface, thus calling ' + . 'getSqlString() has no effect', + ); + } + + return $this->getTypeDecorator($this->subject)->getSqlString($adapterPlatform); } public function getTypeDecorator( - PreparableSqlInterface|SqlInterface $subject + PreparableSqlInterface|SqlInterface $subject, ): PlatformDecoratorInterface|PreparableSqlInterface|SqlInterface { foreach ($this->decorators as $type => $decorator) { /** @phpstan-ignore-next-line instanceof with string class name is valid */ - if ($subject instanceof $type) { - $decorator->setSubject($subject); - return $decorator; + if (! $subject instanceof $type) { + continue; } + + $decorator->setSubject($subject); + return $decorator; } return $subject; } - /** - * @return array|PlatformDecoratorInterface[] - */ - public function getDecorators(): array - { - return $this->decorators; - } - /** * @throws Exception\RuntimeException */ + #[Override] public function prepareStatement( AdapterInterface $adapter, - StatementContainerInterface $statementContainer + StatementContainerInterface $statementContainer, ): StatementContainerInterface { if (! $this->subject instanceof PreparableSqlInterface) { throw new Exception\RuntimeException( 'The subject does not appear to implement PhpDb\Sql\PreparableSqlInterface, thus calling ' - . 'prepareStatement() has no effect' + . 'prepareStatement() has no effect', ); } @@ -75,18 +82,17 @@ public function prepareStatement( /** * {@inheritDoc} - * - * @throws Exception\RuntimeException */ - public function getSqlString(?PlatformInterface $adapterPlatform = null): string + #[Override] + public function setSubject($subject): static { - if (! $this->subject instanceof SqlInterface) { - throw new Exception\RuntimeException( - 'The subject does not appear to implement PhpDb\Sql\SqlInterface, thus calling ' - . 'getSqlString() has no effect' - ); - } + $this->subject = $subject; - return $this->getTypeDecorator($this->subject)->getSqlString($adapterPlatform); + return $this; + } + + public function setTypeDecorator(string $type, PlatformDecoratorInterface $decorator): void + { + $this->decorators[$type] = $decorator; } } diff --git a/src/Sql/Platform/Platform.php b/src/Sql/Platform/Platform.php index 6ce2f62be..1c073d595 100644 --- a/src/Sql/Platform/Platform.php +++ b/src/Sql/Platform/Platform.php @@ -4,6 +4,7 @@ namespace PhpDb\Sql\Platform; +use Override; use PhpDb\Adapter\AdapterInterface; use PhpDb\Adapter\Platform\PlatformInterface; use PhpDb\Adapter\StatementContainerInterface; @@ -44,18 +45,38 @@ public function __construct(PlatformInterface $platform) $this->defaultPlatform = $platform; } - public function setTypeDecorator( - string $type, - PlatformDecoratorInterface $decorator, - AdapterInterface|PlatformInterface|null $adapterOrPlatform = null - ): void { - $platformName = $this->resolvePlatformName($adapterOrPlatform); - $this->decorators[$platformName][$type] = $decorator; + #[Override] + public function getDecorators(): array + { + $platformName = $this->resolvePlatformName($this->getDefaultPlatform()); + + return $this->decorators[$platformName]; + } + + /** + * {@inheritDoc} + * + * @throws Exception\RuntimeException + */ + #[Override] + public function getSqlString(?PlatformInterface $adapterPlatform = null): string + { + if (! $this->subject instanceof SqlInterface) { + throw new Exception\RuntimeException( + 'The subject does not appear to implement PhpDb\Sql\SqlInterface, thus calling ' + . 'prepareStatement() has no effect', + ); + } + + $adapterPlatform = $this->resolvePlatform($adapterPlatform); + + return $this->getTypeDecorator($this->subject, $adapterPlatform)->getSqlString($adapterPlatform); } + #[Override] public function getTypeDecorator( PreparableSqlInterface|SqlInterface $subject, - AdapterInterface|PlatformInterface|null $adapterOrPlatform = null + AdapterInterface|PlatformInterface|null $adapterOrPlatform = null, ): PlatformDecoratorInterface|PreparableSqlInterface|SqlInterface { $platformName = $this->resolvePlatformName($adapterOrPlatform); @@ -72,35 +93,31 @@ public function getTypeDecorator( /** @var PlatformDecoratorInterface $decorator */ foreach ($this->decorators[$platformName] as $type => $decorator) { - if ($subject instanceof $type && is_a($decorator, $type, true)) { - $decorator->setSubject($subject); - return $decorator; + if (! ($subject instanceof $type && is_a($decorator, $type, true))) { + continue; } + + $decorator->setSubject($subject); + return $decorator; } return $subject; } - public function getDecorators(): array - { - $platformName = $this->resolvePlatformName($this->getDefaultPlatform()); - - return $this->decorators[$platformName]; - } - /** * {@inheritDoc} * * @throws Exception\RuntimeException */ + #[Override] public function prepareStatement( AdapterInterface $adapter, - StatementContainerInterface $statementContainer + StatementContainerInterface $statementContainer, ): StatementContainerInterface { if (! $this->subject instanceof PreparableSqlInterface) { throw new Exception\RuntimeException( 'The subject does not appear to implement PhpDb\Sql\PreparableSqlInterface, thus calling ' - . 'prepareStatement() has no effect' + . 'prepareStatement() has no effect', ); } @@ -109,39 +126,19 @@ public function prepareStatement( return $statementContainer; } - /** - * {@inheritDoc} - * - * @throws Exception\RuntimeException - */ - public function getSqlString(?PlatformInterface $adapterPlatform = null): string - { - if (! $this->subject instanceof SqlInterface) { - throw new Exception\RuntimeException( - 'The subject does not appear to implement PhpDb\Sql\SqlInterface, thus calling ' - . 'prepareStatement() has no effect' - ); - } - - $adapterPlatform = $this->resolvePlatform($adapterPlatform); - - return $this->getTypeDecorator($this->subject, $adapterPlatform)->getSqlString($adapterPlatform); + #[Override] + public function setTypeDecorator( + string $type, + PlatformDecoratorInterface $decorator, + AdapterInterface|PlatformInterface|null $adapterOrPlatform = null, + ): void { + $platformName = $this->resolvePlatformName($adapterOrPlatform); + $this->decorators[$platformName][$type] = $decorator; } - protected function resolvePlatformName(PlatformInterface|AdapterInterface|null $adapterOrPlatform): string + protected function getDefaultPlatform(): PlatformInterface { - if ($adapterOrPlatform === null && $this->cachedPlatformName !== null) { - return $this->cachedPlatformName; - } - - $platformName = $this->resolvePlatform($adapterOrPlatform)->getName(); - $normalized = str_replace([' ', '_'], '', strtolower($platformName)); - - if ($adapterOrPlatform === null) { - $this->cachedPlatformName = $normalized; - } - - return $normalized; + return $this->defaultPlatform; } /** @@ -149,7 +146,7 @@ protected function resolvePlatformName(PlatformInterface|AdapterInterface|null $ */ protected function resolvePlatform(PlatformInterface|AdapterInterface|null $adapterOrPlatform): PlatformInterface { - if ($adapterOrPlatform === null) { + if (null === $adapterOrPlatform) { return $this->getDefaultPlatform(); } @@ -160,8 +157,19 @@ protected function resolvePlatform(PlatformInterface|AdapterInterface|null $adap return $adapterOrPlatform; } - protected function getDefaultPlatform(): PlatformInterface + protected function resolvePlatformName(PlatformInterface|AdapterInterface|null $adapterOrPlatform): string { - return $this->defaultPlatform; + if (null === $adapterOrPlatform && null !== $this->cachedPlatformName) { + return $this->cachedPlatformName; + } + + $platformName = $this->resolvePlatform($adapterOrPlatform)->getName(); + $normalized = str_replace([' ', '_'], '', strtolower($platformName)); + + if (null === $adapterOrPlatform) { + $this->cachedPlatformName = $normalized; + } + + return $normalized; } } diff --git a/src/Sql/Platform/PlatformDecoratorInterface.php b/src/Sql/Platform/PlatformDecoratorInterface.php index b39bedba6..3aa2bd673 100644 --- a/src/Sql/Platform/PlatformDecoratorInterface.php +++ b/src/Sql/Platform/PlatformDecoratorInterface.php @@ -10,6 +10,6 @@ interface PlatformDecoratorInterface { public function setSubject( - SqlInterface|PreparableSqlInterface|null $subject - ): PlatformDecoratorInterface; + SqlInterface|PreparableSqlInterface|null $subject, + ): self; } diff --git a/src/Sql/Predicate/Between.php b/src/Sql/Predicate/Between.php index c6c466bbe..c7f0b097b 100644 --- a/src/Sql/Predicate/Between.php +++ b/src/Sql/Predicate/Between.php @@ -25,33 +25,48 @@ class Between extends AbstractExpression implements PredicateInterface * Constructor */ public function __construct( - null|string|ArgumentInterface $identifier = null, - null|int|float|string|ArgumentInterface $minValue = null, - null|int|float|string|ArgumentInterface $maxValue = null + string|ArgumentInterface|null $identifier = null, + int|float|string|ArgumentInterface|null $minValue = null, + int|float|string|ArgumentInterface|null $maxValue = null, ) { - if ($identifier !== null) { + if (null !== $identifier) { $this->setIdentifier($identifier); } - if ($minValue !== null) { + if (null !== $minValue) { $this->setMinValue($minValue); } - if ($maxValue !== null) { + if (null !== $maxValue) { $this->setMaxValue($maxValue); } } - /** - * Set identifier for comparison - */ - public function setIdentifier(string|ArgumentInterface $identifier): static + /** @inheritDoc */ + #[Override] + public function getExpressionData(): array { - $this->identifier = $identifier instanceof ArgumentInterface - ? $identifier - : new Identifier($identifier); + if (! $this->identifier instanceof ArgumentInterface) { + throw new LogicException('Identifier must be specified'); + } - return $this; + if (! $this->minValue instanceof ArgumentInterface) { + throw new LogicException('minValue must be specified'); + } + + if (! $this->maxValue instanceof ArgumentInterface) { + throw new LogicException('maxValue must be specified'); + } + + $identifierSpec = $this->identifier->getSpecification(); + $minValueSpec = $this->minValue->getSpecification(); + $maxValueSpec = $this->maxValue->getSpecification(); + $spec = "{$identifierSpec} {$this->operator} {$minValueSpec} AND {$maxValueSpec}"; + + return [ + 'spec' => $this->specification ?? $spec, + 'values' => [$this->identifier, $this->minValue, $this->maxValue], + ]; } /** @@ -63,15 +78,11 @@ public function getIdentifier(): ?ArgumentInterface } /** - * Set minimum value for comparison + * Get maximum value for comparison */ - public function setMinValue(null|int|float|string|bool|ArgumentInterface $minValue): static + public function getMaxValue(): ?ArgumentInterface { - $this->minValue = $minValue instanceof ArgumentInterface - ? $minValue - : new Value($minValue); - - return $this; + return $this->maxValue; } /** @@ -82,10 +93,22 @@ public function getMinValue(): ?ArgumentInterface return $this->minValue; } + /** + * Set identifier for comparison + */ + public function setIdentifier(string|ArgumentInterface $identifier): static + { + $this->identifier = $identifier instanceof ArgumentInterface + ? $identifier + : new Identifier($identifier); + + return $this; + } + /** * Set maximum value for comparison */ - public function setMaxValue(null|int|float|string|bool|ArgumentInterface $maxValue): static + public function setMaxValue(int|float|string|bool|ArgumentInterface|null $maxValue): static { $this->maxValue = $maxValue instanceof ArgumentInterface ? $maxValue @@ -95,37 +118,14 @@ public function setMaxValue(null|int|float|string|bool|ArgumentInterface $maxVal } /** - * Get maximum value for comparison + * Set minimum value for comparison */ - public function getMaxValue(): ?ArgumentInterface + public function setMinValue(int|float|string|bool|ArgumentInterface|null $minValue): static { - return $this->maxValue; - } - - /** @inheritDoc */ - #[Override] - public function getExpressionData(): array - { - if (! $this->identifier instanceof ArgumentInterface) { - throw new LogicException('Identifier must be specified'); - } - - if (! $this->minValue instanceof ArgumentInterface) { - throw new LogicException('minValue must be specified'); - } - - if (! $this->maxValue instanceof ArgumentInterface) { - throw new LogicException('maxValue must be specified'); - } - - $identifierSpec = $this->identifier->getSpecification(); - $minValueSpec = $this->minValue->getSpecification(); - $maxValueSpec = $this->maxValue->getSpecification(); - $spec = "{$identifierSpec} {$this->operator} {$minValueSpec} AND {$maxValueSpec}"; + $this->minValue = $minValue instanceof ArgumentInterface + ? $minValue + : new Value($minValue); - return [ - 'spec' => $this->specification ?? $spec, - 'values' => [$this->identifier, $this->minValue, $this->maxValue], - ]; + return $this; } } diff --git a/src/Sql/Predicate/Expression.php b/src/Sql/Predicate/Expression.php index 1533e4f6b..8752b2634 100644 --- a/src/Sql/Predicate/Expression.php +++ b/src/Sql/Predicate/Expression.php @@ -6,6 +6,4 @@ use PhpDb\Sql\Expression as BaseExpression; -class Expression extends BaseExpression implements PredicateInterface -{ -} +class Expression extends BaseExpression implements PredicateInterface {} diff --git a/src/Sql/Predicate/In.php b/src/Sql/Predicate/In.php index e3984af2f..21bea922e 100644 --- a/src/Sql/Predicate/In.php +++ b/src/Sql/Predicate/In.php @@ -17,34 +17,43 @@ class In extends AbstractExpression implements PredicateInterface { protected ?ArgumentInterface $identifier = null; protected ?ArgumentInterface $valueSet = null; - protected string $operator = 'IN'; + protected string $operator = 'IN'; /** * Constructor */ public function __construct( - null|string|ArgumentInterface $identifier = null, - null|array|Select|ArgumentInterface $valueSet = null + string|ArgumentInterface|null $identifier = null, + array|Select|ArgumentInterface|null $valueSet = null, ) { - if ($identifier !== null) { + if (null !== $identifier) { $this->setIdentifier($identifier); } - if ($valueSet !== null) { + if (null !== $valueSet) { $this->setValueSet($valueSet); } } - /** - * Set identifier for comparison - */ - public function setIdentifier(string|ArgumentInterface $identifier): static + /** @inheritDoc */ + #[Override] + public function getExpressionData(): array { - $this->identifier = $identifier instanceof ArgumentInterface - ? $identifier - : new Identifier($identifier); + if (! $this->identifier instanceof ArgumentInterface) { + throw new InvalidArgumentException('Identifier must be specified'); + } - return $this; + if (! $this->valueSet instanceof ArgumentInterface) { + throw new InvalidArgumentException('Value set must be provided for IN predicate'); + } + + $identifierSpec = $this->identifier->getSpecification(); + $valueSetSpec = $this->valueSet->getSpecification(); + + return [ + 'spec' => $this->specification ?? "{$identifierSpec} {$this->operator} {$valueSetSpec}", + 'values' => [$this->identifier, $this->valueSet], + ]; } /** @@ -55,6 +64,26 @@ public function getIdentifier(): ?ArgumentInterface return $this->identifier; } + /** + * Gets set of values in IN comparison + */ + public function getValueSet(): ?ArgumentInterface + { + return $this->valueSet; + } + + /** + * Set identifier for comparison + */ + public function setIdentifier(string|ArgumentInterface $identifier): static + { + $this->identifier = $identifier instanceof ArgumentInterface + ? $identifier + : new Identifier($identifier); + + return $this; + } + /** * Set set of values for IN comparison */ @@ -70,33 +99,4 @@ public function setValueSet(array|Select|ArgumentInterface $valueSet): static return $this; } - - /** - * Gets set of values in IN comparison - */ - public function getValueSet(): ?ArgumentInterface - { - return $this->valueSet; - } - - /** @inheritDoc */ - #[Override] - public function getExpressionData(): array - { - if (! $this->identifier instanceof ArgumentInterface) { - throw new InvalidArgumentException('Identifier must be specified'); - } - - if (! $this->valueSet instanceof ArgumentInterface) { - throw new InvalidArgumentException('Value set must be provided for IN predicate'); - } - - $identifierSpec = $this->identifier->getSpecification(); - $valueSetSpec = $this->valueSet->getSpecification(); - - return [ - 'spec' => $this->specification ?? "{$identifierSpec} {$this->operator} {$valueSetSpec}", - 'values' => [$this->identifier, $this->valueSet], - ]; - } } diff --git a/src/Sql/Predicate/IsNull.php b/src/Sql/Predicate/IsNull.php index 10cae8a7d..5e94af320 100644 --- a/src/Sql/Predicate/IsNull.php +++ b/src/Sql/Predicate/IsNull.php @@ -19,33 +19,13 @@ class IsNull extends AbstractExpression implements PredicateInterface /** * Constructor */ - public function __construct(null|string|ArgumentInterface $identifier = null) + public function __construct(string|ArgumentInterface|null $identifier = null) { - if ($identifier !== null) { + if (null !== $identifier) { $this->setIdentifier($identifier); } } - /** - * Set identifier for comparison - */ - public function setIdentifier(string|ArgumentInterface $identifier): static - { - $this->identifier = $identifier instanceof ArgumentInterface - ? $identifier - : new Identifier($identifier); - - return $this; - } - - /** - * Get identifier of comparison - */ - public function getIdentifier(): ?ArgumentInterface - { - return $this->identifier; - } - /** @inheritDoc */ #[Override] public function getExpressionData(): array @@ -61,4 +41,24 @@ public function getExpressionData(): array 'values' => [$this->identifier], ]; } + + /** + * Get identifier of comparison + */ + public function getIdentifier(): ?ArgumentInterface + { + return $this->identifier; + } + + /** + * Set identifier for comparison + */ + public function setIdentifier(string|ArgumentInterface $identifier): static + { + $this->identifier = $identifier instanceof ArgumentInterface + ? $identifier + : new Identifier($identifier); + + return $this; + } } diff --git a/src/Sql/Predicate/Like.php b/src/Sql/Predicate/Like.php index 44b7bbfcf..7ceebe99d 100644 --- a/src/Sql/Predicate/Like.php +++ b/src/Sql/Predicate/Like.php @@ -13,7 +13,7 @@ class Like extends AbstractExpression implements PredicateInterface { - protected string $operator = 'LIKE'; + protected string $operator = 'LIKE'; protected ?ArgumentInterface $identifier = null; protected ?ArgumentInterface $like = null; @@ -21,18 +21,49 @@ class Like extends AbstractExpression implements PredicateInterface * Constructor */ public function __construct( - null|string|ArgumentInterface $identifier = null, - null|bool|float|int|string|ArgumentInterface $like = null + string|ArgumentInterface|null $identifier = null, + bool|float|int|string|ArgumentInterface|null $like = null, ) { - if ($identifier !== null) { + if (null !== $identifier) { $this->setIdentifier($identifier); } - if ($like !== null) { + if (null !== $like) { $this->setLike($like); } } + /** @inheritDoc */ + #[Override] + public function getExpressionData(): array + { + if (! $this->identifier instanceof ArgumentInterface) { + throw new InvalidArgumentException('Identifier must be specified'); + } + + if (! $this->like instanceof ArgumentInterface) { + throw new InvalidArgumentException('Like expression must be specified'); + } + + $identifierSpec = $this->identifier->getSpecification(); + $likeSpec = $this->like->getSpecification(); + + return [ + 'spec' => $this->specification ?? "{$identifierSpec} {$this->operator} {$likeSpec}", + 'values' => [$this->identifier, $this->like], + ]; + } + + public function getIdentifier(): ?ArgumentInterface + { + return $this->identifier; + } + + public function getLike(): ?ArgumentInterface + { + return $this->like; + } + /** * Set identifier for comparison */ @@ -45,15 +76,10 @@ public function setIdentifier(string|ArgumentInterface $identifier): static return $this; } - public function getIdentifier(): ?ArgumentInterface - { - return $this->identifier; - } - /** * Set like pattern for comparison */ - public function setLike(bool|float|int|null|string|ArgumentInterface $like): static + public function setLike(bool|float|int|string|ArgumentInterface|null $like): static { $this->like = $like instanceof ArgumentInterface ? $like @@ -61,30 +87,4 @@ public function setLike(bool|float|int|null|string|ArgumentInterface $like): sta return $this; } - - public function getLike(): ?ArgumentInterface - { - return $this->like; - } - - /** @inheritDoc */ - #[Override] - public function getExpressionData(): array - { - if (! $this->identifier instanceof ArgumentInterface) { - throw new InvalidArgumentException('Identifier must be specified'); - } - - if (! $this->like instanceof ArgumentInterface) { - throw new InvalidArgumentException('Like expression must be specified'); - } - - $identifierSpec = $this->identifier->getSpecification(); - $likeSpec = $this->like->getSpecification(); - - return [ - 'spec' => $this->specification ?? "{$identifierSpec} {$this->operator} {$likeSpec}", - 'values' => [$this->identifier, $this->like], - ]; - } } diff --git a/src/Sql/Predicate/Literal.php b/src/Sql/Predicate/Literal.php index 36d14da46..475e27593 100644 --- a/src/Sql/Predicate/Literal.php +++ b/src/Sql/Predicate/Literal.php @@ -6,6 +6,4 @@ use PhpDb\Sql\Literal as BaseLiteral; -class Literal extends BaseLiteral implements PredicateInterface -{ -} +class Literal extends BaseLiteral implements PredicateInterface {} diff --git a/src/Sql/Predicate/Operator.php b/src/Sql/Predicate/Operator.php index 0b72f4480..22c5ec38d 100644 --- a/src/Sql/Predicate/Operator.php +++ b/src/Sql/Predicate/Operator.php @@ -40,31 +40,52 @@ class Operator extends AbstractExpression implements PredicateInterface final public const OP_GTE = '>='; - protected ?ArgumentInterface $left = null; - protected ?ArgumentInterface $right = null; - protected string $operator = self::OPERATOR_EQUAL_TO; + protected ?ArgumentInterface $left = null; + protected ?ArgumentInterface $right = null; + protected string $operator = self::OPERATOR_EQUAL_TO; /** * Constructor */ public function __construct( - null|string|ArgumentInterface|ExpressionInterface|SqlInterface $left = null, + string|ArgumentInterface|ExpressionInterface|SqlInterface|null $left = null, string $operator = self::OPERATOR_EQUAL_TO, - null|bool|string|int|float|ArgumentInterface|ExpressionInterface|SqlInterface $right = null + bool|string|int|float|ArgumentInterface|ExpressionInterface|SqlInterface|null $right = null, ) { - if ($left !== null) { + if (null !== $left) { $this->setLeft($left); } - if ($operator !== self::OPERATOR_EQUAL_TO) { + if (self::OPERATOR_EQUAL_TO !== $operator) { $this->setOperator($operator); } - if ($right !== null) { + if (null !== $right) { $this->setRight($right); } } + /** @inheritDoc */ + #[Override] + public function getExpressionData(): array + { + if (! $this->left instanceof ArgumentInterface) { + throw new InvalidArgumentException('Left expression must be specified'); + } + + if (! $this->right instanceof ArgumentInterface) { + throw new InvalidArgumentException('Right expression must be specified'); + } + + $leftSpec = $this->left->getSpecification(); + $rightSpec = $this->right->getSpecification(); + + return [ + 'spec' => $this->specification ?? "{$leftSpec} {$this->operator} {$rightSpec}", + 'values' => [$this->left, $this->right], + ]; + } + /** * Get left side of operator */ @@ -73,6 +94,22 @@ public function getLeft(): ?ArgumentInterface return $this->left; } + /** + * Get operator string + */ + public function getOperator(): string + { + return $this->operator; + } + + /** + * Get right side of operator + */ + public function getRight(): ?ArgumentInterface + { + return $this->right; + } + /** * Set left side of operator */ @@ -89,14 +126,6 @@ public function setLeft(string|ArgumentInterface|ExpressionInterface|SqlInterfac return $this; } - /** - * Get operator string - */ - public function getOperator(): string - { - return $this->operator; - } - /** * Set operator string */ @@ -107,19 +136,11 @@ public function setOperator(string $operator): static return $this; } - /** - * Get right side of operator - */ - public function getRight(): ?ArgumentInterface - { - return $this->right; - } - /** * Set right side of operator */ public function setRight( - null|bool|string|int|float|ArgumentInterface|ExpressionInterface|SqlInterface $right + bool|string|int|float|ArgumentInterface|ExpressionInterface|SqlInterface|null $right, ): static { if ($right instanceof ArgumentInterface) { $this->right = $right; @@ -131,25 +152,4 @@ public function setRight( return $this; } - - /** @inheritDoc */ - #[Override] - public function getExpressionData(): array - { - if (! $this->left instanceof ArgumentInterface) { - throw new InvalidArgumentException('Left expression must be specified'); - } - - if (! $this->right instanceof ArgumentInterface) { - throw new InvalidArgumentException('Right expression must be specified'); - } - - $leftSpec = $this->left->getSpecification(); - $rightSpec = $this->right->getSpecification(); - - return [ - 'spec' => $this->specification ?? "{$leftSpec} {$this->operator} {$rightSpec}", - 'values' => [$this->left, $this->right], - ]; - } } diff --git a/src/Sql/Predicate/Predicate.php b/src/Sql/Predicate/Predicate.php index 2b3a320a6..5a69b2048 100644 --- a/src/Sql/Predicate/Predicate.php +++ b/src/Sql/Predicate/Predicate.php @@ -20,54 +20,27 @@ */ class Predicate extends PredicateSet { - private Predicate|null $unnest = null; + private ?Predicate $unnest = null; - protected string|null $nextPredicateCombineOperator = null; - - protected function getNextPredicateCombineOperator(): string - { - $operator = $this->nextPredicateCombineOperator ?? $this->defaultCombination; - $this->nextPredicateCombineOperator = null; - - return $operator; - } - - /** - * Begin nesting predicates - */ - public function nest(): Predicate - { - $predicateSet = new Predicate(); - $predicateSet->setUnnest($this); - $this->addPredicate($predicateSet, $this->getNextPredicateCombineOperator()); - $this->nextPredicateCombineOperator = null; - - return $predicateSet; - } + protected ?string $nextPredicateCombineOperator = null; /** - * Indicate what predicate will be unnested - */ - public function setUnnest(?Predicate $predicate = null): void - { - /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ - $this->unnest = $predicate; - } - - /** - * Indicate end of nested predicate + * Create "between" predicate + * Utilizes Between predicate + * + * @return $this Provides a fluent interface */ - public function unnest(): Predicate - { - if (! $this->unnest instanceof Predicate) { - throw new RuntimeException('Not nested'); - } - - $unnest = $this->unnest; - /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ - $this->unnest = null; + public function between( + float|int|string|array|ArgumentInterface|null $identifier, + float|int|string|array|ArgumentInterface|null $minValue, + float|int|string|array|ArgumentInterface|null $maxValue, + ): static { + $this->addPredicate( + new Between($identifier, $minValue, $maxValue), + $this->getNextPredicateCombineOperator(), + ); - return $unnest; + return $this; } /** @@ -75,159 +48,173 @@ public function unnest(): Predicate * Utilizes Operator predicate */ public function equalTo( - null|float|int|string|ArgumentInterface $left, - null|float|int|string|ArgumentInterface $right, + float|int|string|ArgumentInterface|null $left, + float|int|string|ArgumentInterface|null $right, ): static { $this->addPredicate( new Operator($left, Operator::OPERATOR_EQUAL_TO, $right), - $this->getNextPredicateCombineOperator() + $this->getNextPredicateCombineOperator(), ); return $this; } /** - * Create "Not Equal To" predicate - * Utilizes Operator predicate + * Create an expression, with parameter placeholders + * + * @return $this Provides a fluent interface */ - public function notEqualTo( - null|float|int|string|ArgumentInterface $left, - null|float|int|string|ArgumentInterface $right + public function expression( + string $expression, + string|float|int|array|ArgumentInterface|ExpressionInterface|null $parameters = [], ): static { - $this->addPredicate( - new Operator($left, Operator::OPERATOR_NOT_EQUAL_TO, $right), - $this->getNextPredicateCombineOperator() - ); + if ([] !== $parameters) { + $this->addPredicate( + new Expression($expression, $parameters), + $this->getNextPredicateCombineOperator(), + ); + } else { + $this->addPredicate( + new Expression($expression), + $this->getNextPredicateCombineOperator(), + ); + } return $this; } /** - * Create "Less Than" predicate + * Create "Greater Than" predicate * Utilizes Operator predicate + * + * @return $this Provides a fluent interface */ - public function lessThan( - null|float|int|string|ArgumentInterface $left, - null|float|int|string|ArgumentInterface $right + public function greaterThan( + float|int|string|ArgumentInterface|null $left, + float|int|string|ArgumentInterface|null $right, ): static { $this->addPredicate( - new Operator($left, Operator::OPERATOR_LESS_THAN, $right), - $this->getNextPredicateCombineOperator() + new Operator($left, Operator::OPERATOR_GREATER_THAN, $right), + $this->getNextPredicateCombineOperator(), ); return $this; } /** - * Create "Greater Than" predicate + * Create "Greater Than Or Equal To" predicate * Utilizes Operator predicate * * @return $this Provides a fluent interface */ - public function greaterThan( - null|float|int|string|ArgumentInterface $left, - null|float|int|string|ArgumentInterface $right + public function greaterThanOrEqualTo( + float|int|string|ArgumentInterface|null $left, + float|int|string|ArgumentInterface|null $right, ): static { $this->addPredicate( - new Operator($left, Operator::OPERATOR_GREATER_THAN, $right), - $this->getNextPredicateCombineOperator() + new Operator($left, Operator::OPERATOR_GREATER_THAN_OR_EQUAL_TO, $right), + $this->getNextPredicateCombineOperator(), ); return $this; } /** - * Create "Less Than Or Equal To" predicate - * Utilizes Operator predicate + * Create "IN" predicate + * Utilizes In predicate * * @return $this Provides a fluent interface */ - public function lessThanOrEqualTo( - null|float|int|string|ArgumentInterface $left, - null|float|int|string|ArgumentInterface $right - ): static { + public function in(float|int|string|ArgumentInterface $identifier, array|ArgumentInterface $valueSet): static + { $this->addPredicate( - new Operator($left, Operator::OPERATOR_LESS_THAN_OR_EQUAL_TO, $right), - $this->getNextPredicateCombineOperator() + new In($identifier, $valueSet), + $this->getNextPredicateCombineOperator(), ); return $this; } /** - * Create "Greater Than Or Equal To" predicate - * Utilizes Operator predicate + * Create "IS NOT NULL" predicate + * Utilizes IsNotNull predicate * * @return $this Provides a fluent interface */ - public function greaterThanOrEqualTo( - null|float|int|string|ArgumentInterface $left, - null|float|int|string|ArgumentInterface $right - ): static { + public function isNotNull(float|int|string|ArgumentInterface $identifier): static + { $this->addPredicate( - new Operator($left, Operator::OPERATOR_GREATER_THAN_OR_EQUAL_TO, $right), - $this->getNextPredicateCombineOperator() + new IsNotNull($identifier), + $this->getNextPredicateCombineOperator(), ); return $this; } /** - * Create "Like" predicate - * Utilizes Like predicate + * Create "IS NULL" predicate + * Utilizes IsNull predicate * * @return $this Provides a fluent interface */ - public function like( - null|float|int|string|ArgumentInterface $identifier, - null|float|int|string|ArgumentInterface $like + public function isNull(float|int|string|ArgumentInterface $identifier): static + { + $this->addPredicate( + new IsNull($identifier), + $this->getNextPredicateCombineOperator(), + ); + + return $this; + } + + /** + * Create "Less Than" predicate + * Utilizes Operator predicate + */ + public function lessThan( + float|int|string|ArgumentInterface|null $left, + float|int|string|ArgumentInterface|null $right, ): static { $this->addPredicate( - new Like($identifier, $like), - $this->getNextPredicateCombineOperator() + new Operator($left, Operator::OPERATOR_LESS_THAN, $right), + $this->getNextPredicateCombineOperator(), ); return $this; } /** - * Create "notLike" predicate - * Utilizes In predicate + * Create "Less Than Or Equal To" predicate + * Utilizes Operator predicate * * @return $this Provides a fluent interface */ - public function notLike( - null|float|int|string|ArgumentInterface $identifier, - null|float|int|string|ArgumentInterface $notLike + public function lessThanOrEqualTo( + float|int|string|ArgumentInterface|null $left, + float|int|string|ArgumentInterface|null $right, ): static { $this->addPredicate( - new NotLike($identifier, $notLike), - $this->getNextPredicateCombineOperator() + new Operator($left, Operator::OPERATOR_LESS_THAN_OR_EQUAL_TO, $right), + $this->getNextPredicateCombineOperator(), ); return $this; } /** - * Create an expression, with parameter placeholders + * Create "Like" predicate + * Utilizes Like predicate * * @return $this Provides a fluent interface */ - public function expression( - string $expression, - null|string|float|int|array|ArgumentInterface|ExpressionInterface $parameters = [] + public function like( + float|int|string|ArgumentInterface|null $identifier, + float|int|string|ArgumentInterface|null $like, ): static { - if ($parameters !== []) { - $this->addPredicate( - new Expression($expression, $parameters), - $this->getNextPredicateCombineOperator() - ); - } else { - $this->addPredicate( - new Expression($expression), - $this->getNextPredicateCombineOperator() - ); - } + $this->addPredicate( + new Like($identifier, $like), + $this->getNextPredicateCombineOperator(), + ); return $this; } @@ -242,55 +229,55 @@ public function literal(string $literal): static { $this->addPredicate( new Literal($literal), - $this->getNextPredicateCombineOperator() + $this->getNextPredicateCombineOperator(), ); return $this; } /** - * Create "IS NULL" predicate - * Utilizes IsNull predicate - * - * @return $this Provides a fluent interface + * Begin nesting predicates */ - public function isNull(float|int|string|ArgumentInterface $identifier): static + public function nest(): self { - $this->addPredicate( - new IsNull($identifier), - $this->getNextPredicateCombineOperator() - ); + $predicateSet = new Predicate(); + $predicateSet->setUnnest($this); + $this->addPredicate($predicateSet, $this->getNextPredicateCombineOperator()); + $this->nextPredicateCombineOperator = null; - return $this; + return $predicateSet; } /** - * Create "IS NOT NULL" predicate - * Utilizes IsNotNull predicate + * Create "NOT BETWEEN" predicate + * Utilizes NotBetween predicate * * @return $this Provides a fluent interface */ - public function isNotNull(float|int|string|ArgumentInterface $identifier): static - { + public function notBetween( + float|int|string|array|ArgumentInterface|null $identifier, + float|int|string|array|ArgumentInterface|null $minValue, + float|int|string|array|ArgumentInterface|null $maxValue, + ): static { $this->addPredicate( - new IsNotNull($identifier), - $this->getNextPredicateCombineOperator() + new NotBetween($identifier, $minValue, $maxValue), + $this->getNextPredicateCombineOperator(), ); return $this; } /** - * Create "IN" predicate - * Utilizes In predicate - * - * @return $this Provides a fluent interface + * Create "Not Equal To" predicate + * Utilizes Operator predicate */ - public function in(float|int|string|ArgumentInterface $identifier, array|ArgumentInterface $valueSet): static - { + public function notEqualTo( + float|int|string|ArgumentInterface|null $left, + float|int|string|ArgumentInterface|null $right, + ): static { $this->addPredicate( - new In($identifier, $valueSet), - $this->getNextPredicateCombineOperator() + new Operator($left, Operator::OPERATOR_NOT_EQUAL_TO, $right), + $this->getNextPredicateCombineOperator(), ); return $this; @@ -306,45 +293,25 @@ public function notIn(float|int|string|ArgumentInterface $identifier, array|Argu { $this->addPredicate( new NotIn($identifier, $valueSet), - $this->getNextPredicateCombineOperator() - ); - - return $this; - } - - /** - * Create "between" predicate - * Utilizes Between predicate - * - * @return $this Provides a fluent interface - */ - public function between( - null|float|int|string|array|ArgumentInterface $identifier, - null|float|int|string|array|ArgumentInterface $minValue, - null|float|int|string|array|ArgumentInterface $maxValue - ): static { - $this->addPredicate( - new Between($identifier, $minValue, $maxValue), - $this->getNextPredicateCombineOperator() + $this->getNextPredicateCombineOperator(), ); return $this; } /** - * Create "NOT BETWEEN" predicate - * Utilizes NotBetween predicate + * Create "notLike" predicate + * Utilizes In predicate * * @return $this Provides a fluent interface */ - public function notBetween( - null|float|int|string|array|ArgumentInterface $identifier, - null|float|int|string|array|ArgumentInterface $minValue, - null|float|int|string|array|ArgumentInterface $maxValue + public function notLike( + float|int|string|ArgumentInterface|null $identifier, + float|int|string|ArgumentInterface|null $notLike, ): static { $this->addPredicate( - new NotBetween($identifier, $minValue, $maxValue), - $this->getNextPredicateCombineOperator() + new NotLike($identifier, $notLike), + $this->getNextPredicateCombineOperator(), ); return $this; @@ -363,17 +330,50 @@ public function predicate(PredicateInterface $predicate): static { $this->addPredicate( $predicate, - $this->getNextPredicateCombineOperator() + $this->getNextPredicateCombineOperator(), ); return $this; } + /** + * Indicate what predicate will be unnested + */ + public function setUnnest(?Predicate $predicate = null): void + { + /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ + $this->unnest = $predicate; + } + + /** + * Indicate end of nested predicate + */ + public function unnest(): self + { + if (! $this->unnest instanceof Predicate) { + throw new RuntimeException('Not nested'); + } + + $unnest = $this->unnest; + /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ + $this->unnest = null; + + return $unnest; + } + + protected function getNextPredicateCombineOperator(): string + { + $operator = $this->nextPredicateCombineOperator ?? $this->defaultCombination; + $this->nextPredicateCombineOperator = null; + + return $operator; + } + /** * Overloading * Overloads "or", "and", "nest", and "unnest" */ - public function __get(string $name): Predicate + public function __get(string $name): self { switch ($name) { case 'or': diff --git a/src/Sql/Predicate/PredicateInterface.php b/src/Sql/Predicate/PredicateInterface.php index b59fbc7cc..d1935e85a 100644 --- a/src/Sql/Predicate/PredicateInterface.php +++ b/src/Sql/Predicate/PredicateInterface.php @@ -6,6 +6,4 @@ use PhpDb\Sql\ExpressionInterface; -interface PredicateInterface extends ExpressionInterface -{ -} +interface PredicateInterface extends ExpressionInterface {} diff --git a/src/Sql/Predicate/PredicateSet.php b/src/Sql/Predicate/PredicateSet.php index a172ddf8d..da34454bc 100644 --- a/src/Sql/Predicate/PredicateSet.php +++ b/src/Sql/Predicate/PredicateSet.php @@ -43,7 +43,7 @@ public function __construct(?array $predicates = null, string $defaultCombinatio { $this->defaultCombination = $defaultCombination; - if ($predicates !== null) { + if (null !== $predicates) { foreach ($predicates as $predicate) { $this->addPredicate($predicate); } @@ -61,7 +61,7 @@ public function addPredicate(PredicateInterface $predicate, ?string $combination self::OP_AND => $this->andPredicate($predicate), self::OP_OR => $this->orPredicate($predicate), default => throw new Exception\InvalidArgumentException( - "Invalid combination: expected 'AND' or 'OR'" + "Invalid combination: expected 'AND' or 'OR'", ), }; @@ -75,7 +75,7 @@ public function addPredicate(PredicateInterface $predicate, ?string $combination */ public function addPredicates( PredicateInterface|Closure|string|array $predicates, - string $combination = self::OP_AND + string $combination = self::OP_AND, ): static { if ($predicates instanceof PredicateInterface) { $this->addPredicate($predicates, $combination); @@ -91,7 +91,8 @@ public function addPredicates( if (is_string($predicates)) { $predicate = str_contains($predicates, Expression::PLACEHOLDER) - ? new PredicateExpression($predicates) : new Literal($predicates); + ? new PredicateExpression($predicates) + : new Literal($predicates); $this->addPredicate($predicate, $combination); return $this; @@ -101,13 +102,13 @@ public function addPredicates( if (is_string($pkey)) { if (str_contains($pkey, '?')) { $predicate = new PredicateExpression($pkey, $pvalue); - } elseif ($pvalue === null) { + } elseif (null === $pvalue) { $predicate = new IsNull($pkey); } elseif (is_array($pvalue)) { $predicate = new In($pkey, $pvalue); } elseif ($pvalue instanceof PredicateInterface) { throw new Exception\InvalidArgumentException( - 'Using Predicate must not use string keys' + 'Using Predicate must not use string keys', ); } else { $predicate = new Operator($pkey, Operator::OP_EQ, $pvalue); @@ -117,11 +118,12 @@ public function addPredicates( } elseif ($pvalue instanceof Expression) { $predicate = new PredicateExpression( $pvalue->getExpression(), - $pvalue->getParameters() + $pvalue->getParameters(), ); } else { $predicate = str_contains($pvalue, Expression::PLACEHOLDER) - ? new Expression($pvalue) : new Literal($pvalue); + ? new Expression($pvalue) + : new Literal($pvalue); } $this->addPredicate($predicate, $combination); @@ -131,31 +133,23 @@ public function addPredicates( } /** - * Return the predicates - */ - public function getPredicates(): array - { - return $this->predicates; - } - - /** - * Add predicate using OR operator + * Add predicate using AND operator */ - public function orPredicate(PredicateInterface $predicate): static + public function andPredicate(PredicateInterface $predicate): static { - $this->predicates[] = [self::OP_OR, $predicate]; + $this->predicates[] = [self::OP_AND, $predicate]; return $this; } /** - * Add predicate using AND operator + * Get count of attached predicates */ - public function andPredicate(PredicateInterface $predicate): static + #[Override] + #[ReturnTypeWillChange] + public function count(): int { - $this->predicates[] = [self::OP_AND, $predicate]; - - return $this; + return count($this->predicates); } /** @inheritDoc */ @@ -164,13 +158,13 @@ public function getExpressionData(): array { $predicateCount = count($this->predicates); - if ($predicateCount === 0) { + if (0 === $predicateCount) { return ['spec' => '', 'values' => []]; } - if ($predicateCount === 1) { + if (1 === $predicateCount) { [$operator, $predicate] = $this->predicates[0]; - $expressionData = $predicate->getExpressionData(); + $expressionData = $predicate->getExpressionData(); if ($predicate instanceof self) { return [ @@ -197,7 +191,7 @@ public function getExpressionData(): array $first = false; $values = $expressionData['values']; - if ($values !== []) { + if ([] !== $values) { foreach ($values as $value) { $allValues[] = $value; } @@ -211,12 +205,20 @@ public function getExpressionData(): array } /** - * Get count of attached predicates + * Return the predicates */ - #[Override] - #[ReturnTypeWillChange] - public function count(): int + public function getPredicates(): array { - return count($this->predicates); + return $this->predicates; + } + + /** + * Add predicate using OR operator + */ + public function orPredicate(PredicateInterface $predicate): static + { + $this->predicates[] = [self::OP_OR, $predicate]; + + return $this; } } diff --git a/src/Sql/PreparableSqlInterface.php b/src/Sql/PreparableSqlInterface.php index 854317a73..3627acada 100644 --- a/src/Sql/PreparableSqlInterface.php +++ b/src/Sql/PreparableSqlInterface.php @@ -11,6 +11,6 @@ interface PreparableSqlInterface { public function prepareStatement( AdapterInterface $adapter, - StatementContainerInterface $statementContainer + StatementContainerInterface $statementContainer, ): StatementContainerInterface; } diff --git a/src/Sql/Select.php b/src/Sql/Select.php index ba99374cd..9b494f792 100644 --- a/src/Sql/Select.php +++ b/src/Sql/Select.php @@ -5,6 +5,7 @@ namespace PhpDb\Sql; use Closure; +use Override; use PhpDb\Adapter\Driver\DriverInterface; use PhpDb\Adapter\ParameterContainer; use PhpDb\Adapter\Platform\PlatformInterface; @@ -153,7 +154,7 @@ class Select extends AbstractPreparableSql protected array $order = []; - protected array|null $group = null; + protected ?array $group = null; protected ?Having $having = null; @@ -174,54 +175,6 @@ public function __construct(array|string|TableIdentifier|null $table = null) } } - private function getWhere(): Where - { - return $this->where ??= new Where(); - } - - private function getJoins(): Join - { - return $this->joins ??= new Join(); - } - - private function getHaving(): Having - { - return $this->having ??= new Having(); - } - - /** - * Create from clause - * - * @throws Exception\InvalidArgumentException - */ - public function from(array|string|TableIdentifier $table): static - { - if ($this->tableReadOnly) { - throw new Exception\InvalidArgumentException( - 'Since this object was created with a table and/or schema in the constructor, it is read only.' - ); - } - - if (is_array($table) && (! is_string(key($table)) || count($table) !== 1)) { - throw new Exception\InvalidArgumentException( - 'from() expects $table as an array is a single element associative array' - ); - } - - $this->table = $table; - return $this; - } - - /** - * @param string|Expression $quantifier DISTINCT|ALL - * @throws Exception\InvalidArgumentException - */ - public function quantifier(ExpressionInterface|string $quantifier): static - { - $this->quantifier = $quantifier; - return $this; - } - /** * Specify columns from which to select * Possible valid states: @@ -240,41 +193,65 @@ public function columns(array $columns, bool $prefixColumnsWithTable = true): st } /** - * Create join clause - * - * @param string $type one of the JOIN_* constants * @throws Exception\InvalidArgumentException */ - public function join( - array|string|TableIdentifier $name, - PredicateInterface|string $on, - array|string $columns = self::SQL_STAR, - string $type = self::JOIN_INNER - ): static { - $this->getJoins()->join($name, $on, $columns, $type); + public function combine(Select $select, string $type = self::COMBINE_UNION, string $modifier = ''): static + { + if ([] !== $this->combine) { + throw new Exception\InvalidArgumentException( + 'This Select object is already combined and cannot be combined with multiple Selects objects', + ); + } + $this->combine = [ + 'select' => $select, + 'type' => $type, + 'modifier' => $modifier, + ]; return $this; } /** - * Create where clause + * Create from clause * - * @param string $combination One of the OP_* constants from Predicate\PredicateSet * @throws Exception\InvalidArgumentException */ - public function where( - PredicateInterface|array|string|Closure $predicate, - string $combination = Predicate\PredicateSet::OP_AND - ): self { - if ($predicate instanceof Where) { - $this->where = $predicate; - } else { - $this->getWhere()->addPredicates($predicate, $combination); + public function from(array|string|TableIdentifier $table): static + { + if ($this->tableReadOnly) { + throw new Exception\InvalidArgumentException( + 'Since this object was created with a table and/or schema in the constructor, it is read only.', + ); + } + + if (is_array($table) && (! is_string(key($table)) || count($table) !== 1)) { + throw new Exception\InvalidArgumentException( + 'from() expects $table as an array is a single element associative array', + ); } + $this->table = $table; return $this; } + public function getRawState(?string $key = null): mixed + { + $rawState = [ + self::TABLE => $this->table, + self::QUANTIFIER => $this->quantifier, + self::COLUMNS => $this->columns, + self::JOINS => $this->getJoins(), + self::WHERE => $this->getWhere(), + self::ORDER => $this->order, + self::GROUP => $this->group, + self::HAVING => $this->getHaving(), + self::LIMIT => $this->limit, + self::OFFSET => $this->offset, + self::COMBINE => $this->combine, + ]; + return null !== $key && array_key_exists($key, $rawState) ? $rawState[$key] : $rawState; + } + public function group(mixed $group): static { if (is_array($group)) { @@ -295,7 +272,7 @@ public function group(mixed $group): static */ public function having( Having|PredicateInterface|array|Closure|string $predicate, - string $combination = Predicate\PredicateSet::OP_AND + string $combination = Predicate\PredicateSet::OP_AND, ): static { if ($predicate instanceof Having) { $this->having = $predicate; @@ -306,21 +283,27 @@ public function having( return $this; } - public function order(ExpressionInterface|array|string $order): static + /** + * Returns whether the table is read only or not. + */ + public function isTableReadOnly(): bool { - if (is_string($order)) { - $order = str_contains($order, ',') ? preg_split('#,\s+#', $order) : (array) $order; - } elseif (! is_array($order)) { - $order = [$order]; - } + return $this->tableReadOnly; + } - foreach ($order as $k => $v) { - if (is_string($k)) { - $this->order[$k] = $v; - } else { - $this->order[] = $v; - } - } + /** + * Create join clause + * + * @param string $type one of the JOIN_* constants + * @throws Exception\InvalidArgumentException + */ + public function join( + array|string|TableIdentifier $name, + PredicateInterface|string $on, + array|string $columns = self::SQL_STAR, + string $type = self::JOIN_INNER, + ): static { + $this->getJoins()->join($name, $on, $columns, $type); return $this; } @@ -334,7 +317,7 @@ public function limit(int|string $limit): static throw new Exception\InvalidArgumentException(sprintf( '%s expects parameter to be numeric, "%s" given', __METHOD__, - gettype($limit) + gettype($limit), )); } @@ -351,7 +334,7 @@ public function offset(int|string $offset): static throw new Exception\InvalidArgumentException(sprintf( '%s expects parameter to be numeric, "%s" given', __METHOD__, - gettype($offset) + gettype($offset), )); } @@ -359,22 +342,32 @@ public function offset(int|string $offset): static return $this; } + public function order(ExpressionInterface|array|string $order): static + { + if (is_string($order)) { + $order = str_contains($order, ',') ? preg_split('#,\s+#', $order) : (array) $order; + } elseif (! is_array($order)) { + $order = [$order]; + } + + foreach ($order as $k => $v) { + if (is_string($k)) { + $this->order[$k] = $v; + } else { + $this->order[] = $v; + } + } + + return $this; + } + /** + * @param string|Expression $quantifier DISTINCT|ALL * @throws Exception\InvalidArgumentException */ - public function combine(Select $select, string $type = self::COMBINE_UNION, string $modifier = ''): static + public function quantifier(ExpressionInterface|string $quantifier): static { - if ($this->combine !== []) { - throw new Exception\InvalidArgumentException( - 'This Select object is already combined and cannot be combined with multiple Selects objects' - ); - } - - $this->combine = [ - 'select' => $select, - 'type' => $type, - 'modifier' => $modifier, - ]; + $this->quantifier = $quantifier; return $this; } @@ -387,7 +380,7 @@ public function reset(string $part): static case self::TABLE: if ($this->tableReadOnly) { throw new Exception\InvalidArgumentException( - 'Since this object was created with a table and/or schema in the constructor, it is read only.' + 'Since this object was created with a table and/or schema in the constructor, it is read only.', ); } @@ -433,7 +426,7 @@ public function reset(string $part): static */ public function setSpecification(string $index, array|string $specification): static { - if (! method_exists($this, 'process' . $index)) { + if (! method_exists($this, "process{$index}")) { throw new Exception\InvalidArgumentException('Not a valid specification name.'); } @@ -441,203 +434,133 @@ public function setSpecification(string $index, array|string $specification): st return $this; } - public function getRawState(?string $key = null): mixed - { - $rawState = [ - self::TABLE => $this->table, - self::QUANTIFIER => $this->quantifier, - self::COLUMNS => $this->columns, - self::JOINS => $this->getJoins(), - self::WHERE => $this->getWhere(), - self::ORDER => $this->order, - self::GROUP => $this->group, - self::HAVING => $this->getHaving(), - self::LIMIT => $this->limit, - self::OFFSET => $this->offset, - self::COMBINE => $this->combine, - ]; - return $key !== null && array_key_exists($key, $rawState) ? $rawState[$key] : $rawState; - } - /** - * Returns whether the table is read only or not. + * Create where clause + * + * @param string $combination One of the OP_* constants from Predicate\PredicateSet + * @throws Exception\InvalidArgumentException */ - public function isTableReadOnly(): bool - { - return $this->tableReadOnly; - } - - /** @return string[]|null */ - protected function processStatementStart(): ?array - { - if ($this->combine !== []) { - return ['(']; + public function where( + PredicateInterface|array|string|Closure $predicate, + string $combination = Predicate\PredicateSet::OP_AND, + ): self { + if ($predicate instanceof Where) { + $this->where = $predicate; + } else { + $this->getWhere()->addPredicates($predicate, $combination); } - return null; + return $this; } - /** @return string[]|null */ - protected function processStatementEnd(): ?array - { - if ($this->combine !== []) { - return [')']; + protected function processCombine( + PlatformInterface $platform, + ?DriverInterface $driver = null, + ?ParameterContainer $parameterContainer = null, + ): ?array { + if ([] === $this->combine) { + return null; } - return null; + $type = $this->combine['modifier'] + ? "{$this->combine['type']} {$this->combine['modifier']}" + : $this->combine['type']; + + return [ + strtoupper($type), + $this->processSubSelect($this->combine['select'], $platform, $driver, $parameterContainer), + ]; } - /** - * Process the select part - */ - protected function processSelect( + protected function processGroup( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null - ): array { - $expr = 1; - - [$table, $fromTable] = $this->resolveTable($this->table, $platform, $driver, $parameterContainer); - $columns = []; - foreach ($this->columns as $columnIndexOrAs => $column) { - if ($column === self::SQL_STAR) { - $columns[] = ["{$fromTable}*"]; - continue; - } + ?ParameterContainer $parameterContainer = null, + ): ?array { + if (null === $this->group) { + return null; + } - $columnName = $this->resolveColumnValue( + $groups = []; + foreach ($this->group as $column) { + $groups[] = $this->resolveColumnValue( [ 'column' => $column, - 'fromTable' => $fromTable, 'isIdentifier' => true, ], $platform, $driver, $parameterContainer, - is_string($columnIndexOrAs) ? $columnIndexOrAs : 'column' + 'group', ); - $columnAs = null; - if (is_string($columnIndexOrAs)) { - $columnAs = $platform->quoteIdentifier($columnIndexOrAs); - } elseif (stripos($columnName, ' as ') === false) { - $columnAs = is_string($column) ? $platform->quoteIdentifier($column) : 'Expression' . $expr++; - } - - $columns[] = $columnAs !== null ? [$columnName, $columnAs] : [$columnName]; } - foreach ($this->getJoins()->getJoins() as $join) { - $joinName = is_array($join['name']) ? key($join['name']) : $join['name']; - $joinName = parent::resolveTable($joinName, $platform, $driver, $parameterContainer); - - foreach ($join['columns'] as $jKey => $jColumn) { - $jColumns = []; - $jFromTable = is_scalar($jColumn) - ? $joinName . $platform->getIdentifierSeparator() - : ''; - $jColumns[] = $this->resolveColumnValue( - [ - 'column' => $jColumn, - 'fromTable' => $jFromTable, - 'isIdentifier' => true, - ], - $platform, - $driver, - $parameterContainer, - is_string($jKey) ? $jKey : 'column' - ); - if (is_string($jKey)) { - $jColumns[] = $platform->quoteIdentifier($jKey); - } elseif ($jColumn !== self::SQL_STAR) { - $jColumns[] = $platform->quoteIdentifier($jColumn); - } - - $columns[] = $jColumns; - } - } + return [$groups]; + } - if ($this->quantifier) { - $quantifier = $this->quantifier instanceof ExpressionInterface - ? $this->processExpression($this->quantifier, $platform, $driver, $parameterContainer, 'quantifier') - : $this->quantifier; + protected function processHaving( + PlatformInterface $platform, + ?DriverInterface $driver = null, + ?ParameterContainer $parameterContainer = null, + ): ?array { + if (null === $this->having || $this->having->count() === 0) { + return null; } - if (! isset($table)) { - return [$columns]; - } elseif (isset($quantifier)) { - return [$quantifier, $columns, $table]; - } else { - return [$columns, $table]; - } + return [ + $this->processExpression($this->having, $platform, $driver, $parameterContainer, 'having'), + ]; } /** @return string[][][]|null */ protected function processJoins( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): ?array { return $this->processJoin($this->joins, $platform, $driver, $parameterContainer); } - protected function processWhere( - PlatformInterface $platform, - ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null - ): ?array { - if ($this->where === null || $this->where->count() === 0) { - return null; - } - - return [ - $this->processExpression($this->where, $platform, $driver, $parameterContainer, 'where'), - ]; - } - - protected function processGroup( + protected function processLimit( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): ?array { - if ($this->group === null) { + if (null === $this->limit) { return null; } - $groups = []; - foreach ($this->group as $column) { - $groups[] = $this->resolveColumnValue( - [ - 'column' => $column, - 'isIdentifier' => true, - ], - $platform, - $driver, - $parameterContainer, - 'group' - ); + if ($parameterContainer instanceof ParameterContainer) { + $paramPrefix = $this->processInfo['paramPrefix']; + $parameterContainer->offsetSet("{$paramPrefix}limit", $this->limit, ParameterContainer::TYPE_INTEGER); + return [$driver->formatParameterName("{$paramPrefix}limit")]; } - return [$groups]; + return [$platform->quoteValue($this->limit)]; } - protected function processHaving( + protected function processOffset( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): ?array { - if ($this->having === null || $this->having->count() === 0) { + if (null === $this->offset) { return null; } - return [ - $this->processExpression($this->having, $platform, $driver, $parameterContainer, 'having'), - ]; + if ($parameterContainer instanceof ParameterContainer) { + $paramPrefix = $this->processInfo['paramPrefix']; + $parameterContainer->offsetSet("{$paramPrefix}offset", $this->offset, ParameterContainer::TYPE_INTEGER); + return [$driver->formatParameterName("{$paramPrefix}offset")]; + } + + return [$platform->quoteValue($this->offset)]; } protected function processOrder( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): ?array { if (empty($this->order)) { return null; @@ -671,105 +594,136 @@ protected function processOrder( return [$orders]; } - protected function processLimit( + /** + * Process the select part + */ + protected function processSelect( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null - ): ?array { - if ($this->limit === null) { - return null; + ?ParameterContainer $parameterContainer = null, + ): array { + $expr = 1; + + [$table, $fromTable] = $this->resolveTable($this->table, $platform, $driver, $parameterContainer); + $columns = []; + foreach ($this->columns as $columnIndexOrAs => $column) { + if (self::SQL_STAR === $column) { + $columns[] = ["{$fromTable}*"]; + continue; + } + + $columnName = $this->resolveColumnValue( + [ + 'column' => $column, + 'fromTable' => $fromTable, + 'isIdentifier' => true, + ], + $platform, + $driver, + $parameterContainer, + is_string($columnIndexOrAs) ? $columnIndexOrAs : 'column', + ); + $columnAs = null; + if (is_string($columnIndexOrAs)) { + $columnAs = $platform->quoteIdentifier($columnIndexOrAs); + } elseif (stripos($columnName, ' as ') === false) { + $columnAs = is_string($column) ? $platform->quoteIdentifier($column) : 'Expression' . $expr++; + } + + $columns[] = null !== $columnAs ? [$columnName, $columnAs] : [$columnName]; } - if ($parameterContainer instanceof ParameterContainer) { - $paramPrefix = $this->processInfo['paramPrefix']; - $parameterContainer->offsetSet($paramPrefix . 'limit', $this->limit, ParameterContainer::TYPE_INTEGER); - return [$driver->formatParameterName($paramPrefix . 'limit')]; + foreach ($this->getJoins()->getJoins() as $join) { + $joinName = is_array($join['name']) ? key($join['name']) : $join['name']; + $joinName = parent::resolveTable($joinName, $platform, $driver, $parameterContainer); + + foreach ($join['columns'] as $jKey => $jColumn) { + $jColumns = []; + $jFromTable = is_scalar($jColumn) + ? $joinName . $platform->getIdentifierSeparator() + : ''; + $jColumns[] = $this->resolveColumnValue( + [ + 'column' => $jColumn, + 'fromTable' => $jFromTable, + 'isIdentifier' => true, + ], + $platform, + $driver, + $parameterContainer, + is_string($jKey) ? $jKey : 'column', + ); + if (is_string($jKey)) { + $jColumns[] = $platform->quoteIdentifier($jKey); + } elseif (self::SQL_STAR !== $jColumn) { + $jColumns[] = $platform->quoteIdentifier($jColumn); + } + + $columns[] = $jColumns; + } } - return [$platform->quoteValue($this->limit)]; + if ($this->quantifier) { + $quantifier = $this->quantifier instanceof ExpressionInterface + ? $this->processExpression($this->quantifier, $platform, $driver, $parameterContainer, 'quantifier') + : $this->quantifier; + } + + if (! isset($table)) { + return [$columns]; + } + + if (isset($quantifier)) { + return [$quantifier, $columns, $table]; + } + + return [$columns, $table]; } - protected function processOffset( - PlatformInterface $platform, - ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null - ): ?array { - if ($this->offset === null) { - return null; + /** @return string[]|null */ + protected function processStatementEnd(): ?array + { + if ([] !== $this->combine) { + return [')']; } - if ($parameterContainer instanceof ParameterContainer) { - $paramPrefix = $this->processInfo['paramPrefix']; - $parameterContainer->offsetSet($paramPrefix . 'offset', $this->offset, ParameterContainer::TYPE_INTEGER); - return [$driver->formatParameterName($paramPrefix . 'offset')]; + return null; + } + + /** @return string[]|null */ + protected function processStatementStart(): ?array + { + if ([] !== $this->combine) { + return ['(']; } - return [$platform->quoteValue($this->offset)]; + return null; } - protected function processCombine( + protected function processWhere( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): ?array { - if ($this->combine === []) { + if (null === $this->where || $this->where->count() === 0) { return null; } - $type = $this->combine['modifier'] - ? "{$this->combine['type']} {$this->combine['modifier']}" - : $this->combine['type']; - return [ - strtoupper($type), - $this->processSubSelect($this->combine['select'], $platform, $driver, $parameterContainer), + $this->processExpression($this->where, $platform, $driver, $parameterContainer, 'where'), ]; } - /** - * Variable overloading - * - * @throws Exception\InvalidArgumentException - */ - public function __get(string $name): Where|Join|Having - { - return match (strtolower($name)) { - 'where' => $this->getWhere(), - 'having' => $this->getHaving(), - 'joins' => $this->getJoins(), - default => throw new Exception\InvalidArgumentException('Not a valid magic property for this object'), - }; - } - - /** - * __clone - * - * Resets the where object each time the Select is cloned. - * - * @return void - */ - public function __clone() - { - if ($this->where !== null) { - $this->where = clone $this->where; - } - if ($this->joins !== null) { - $this->joins = clone $this->joins; - } - if ($this->having !== null) { - $this->having = clone $this->having; - } - } - /** * @return array{0: string|null, 1: string} * @phpstan-return array{0: string|null, 1: string} */ + #[Override] protected function resolveTable( Select|string|array|TableIdentifier|null $table, PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): array { $alias = null; @@ -798,4 +752,54 @@ protected function resolveTable( $fromTable, ]; } + + private function getHaving(): Having + { + return $this->having ??= new Having(); + } + + private function getJoins(): Join + { + return $this->joins ??= new Join(); + } + + private function getWhere(): Where + { + return $this->where ??= new Where(); + } + + /** + * __clone + * + * Resets the where object each time the Select is cloned. + * + * @return void + */ + public function __clone() + { + if (null !== $this->where) { + $this->where = clone $this->where; + } + if (null !== $this->joins) { + $this->joins = clone $this->joins; + } + if (null !== $this->having) { + $this->having = clone $this->having; + } + } + + /** + * Variable overloading + * + * @throws Exception\InvalidArgumentException + */ + public function __get(string $name): Where|Join|Having + { + return match (strtolower($name)) { + 'where' => $this->getWhere(), + 'having' => $this->getHaving(), + 'joins' => $this->getJoins(), + default => throw new Exception\InvalidArgumentException('Not a valid magic property for this object'), + }; + } } diff --git a/src/Sql/Sql.php b/src/Sql/Sql.php index 525c4efb4..8d68fbfb9 100644 --- a/src/Sql/Sql.php +++ b/src/Sql/Sql.php @@ -19,99 +19,83 @@ class Sql public function __construct( AdapterInterface $adapter, - array|string|TableIdentifier|null $table = null + array|string|TableIdentifier|null $table = null, ) { $this->table = $table; $this->adapter = $adapter; $this->sqlPlatform = $adapter->getPlatform()->getSqlPlatformDecorator(); } - public function getAdapter(): ?AdapterInterface - { - return $this->adapter; - } - - public function hasTable(): bool - { - return $this->table !== null; - } - /** * @throws Exception\InvalidArgumentException */ - public function setTable(array|string|TableIdentifier $table): self + public function buildSqlString(SqlInterface $sqlObject, ?AdapterInterface $adapter = null): string { - $this->table = $table; - - return $this; - } + if (! $this->sqlPlatform instanceof SqlInterface) { + throw new Exception\RuntimeException( + 'The subject does not implement SqlInterface', + ); + } - public function getTable(): array|string|TableIdentifier|null - { - return $this->table; - } + $this->sqlPlatform->setSubject($sqlObject); - public function getSqlPlatform(): ?Platform\PlatformDecoratorInterface - { - return $this->sqlPlatform; + return $this->sqlPlatform->getSqlString( + $adapter instanceof AdapterInterface ? $adapter->getPlatform() : $this->adapter->getPlatform(), + ); } - public function select(string|TableIdentifier|null $table = null): Select + public function delete(string|TableIdentifier|null $table = null): Delete { - if ($this->table !== null && $table !== null) { + if (null !== $this->table && null !== $table) { throw new Exception\InvalidArgumentException(sprintf( 'This Sql object is intended to work with only the table "%s" provided at construction time.', - $this->table + $this->table, )); } - return new Select($table ?: $this->table); + return new Delete($table ?: $this->table); } - public function insert(string|null|TableIdentifier $table = null): Insert + public function getAdapter(): ?AdapterInterface { - if ($this->table !== null && $table !== null) { - throw new Exception\InvalidArgumentException(sprintf( - 'This Sql object is intended to work with only the table "%s" provided at construction time.', - $this->table - )); - } + return $this->adapter; + } - return new Insert($table ?: $this->table); + public function getSqlPlatform(): ?Platform\PlatformDecoratorInterface + { + return $this->sqlPlatform; } - public function update(null|string|TableIdentifier $table = null): Update + public function getTable(): array|string|TableIdentifier|null { - if ($this->table !== null && $table !== null) { - throw new Exception\InvalidArgumentException(sprintf( - 'This Sql object is intended to work with only the table "%s" provided at construction time.', - $this->table - )); - } + return $this->table; + } - return new Update($table ?: $this->table); + public function hasTable(): bool + { + return null !== $this->table; } - public function delete(null|string|TableIdentifier $table = null): Delete + public function insert(string|TableIdentifier|null $table = null): Insert { - if ($this->table !== null && $table !== null) { + if (null !== $this->table && null !== $table) { throw new Exception\InvalidArgumentException(sprintf( 'This Sql object is intended to work with only the table "%s" provided at construction time.', - $this->table + $this->table, )); } - return new Delete($table ?: $this->table); + return new Insert($table ?: $this->table); } public function prepareStatementForSqlObject( PreparableSqlInterface $sqlObject, ?StatementInterface $statement = null, - ?AdapterInterface $adapter = null + ?AdapterInterface $adapter = null, ): StatementInterface { if (! $this->sqlPlatform instanceof PreparableSqlInterface) { throw new Exception\RuntimeException( - 'The subject does not implement PreparableSqlInterface' + 'The subject does not implement PreparableSqlInterface', ); } @@ -124,21 +108,37 @@ public function prepareStatementForSqlObject( return $statement; } + public function select(string|TableIdentifier|null $table = null): Select + { + if (null !== $this->table && null !== $table) { + throw new Exception\InvalidArgumentException(sprintf( + 'This Sql object is intended to work with only the table "%s" provided at construction time.', + $this->table, + )); + } + + return new Select($table ?: $this->table); + } + /** * @throws Exception\InvalidArgumentException */ - public function buildSqlString(SqlInterface $sqlObject, ?AdapterInterface $adapter = null): string + public function setTable(array|string|TableIdentifier $table): self { - if (! $this->sqlPlatform instanceof SqlInterface) { - throw new Exception\RuntimeException( - 'The subject does not implement SqlInterface' - ); - } + $this->table = $table; - $this->sqlPlatform->setSubject($sqlObject); + return $this; + } - return $this->sqlPlatform->getSqlString( - $adapter instanceof AdapterInterface ? $adapter->getPlatform() : $this->adapter->getPlatform() - ); + public function update(string|TableIdentifier|null $table = null): Update + { + if (null !== $this->table && null !== $table) { + throw new Exception\InvalidArgumentException(sprintf( + 'This Sql object is intended to work with only the table "%s" provided at construction time.', + $this->table, + )); + } + + return new Update($table ?: $this->table); } } diff --git a/src/Sql/SqlInterface.php b/src/Sql/SqlInterface.php index f8d04ffbb..8f2a93ecb 100644 --- a/src/Sql/SqlInterface.php +++ b/src/Sql/SqlInterface.php @@ -15,13 +15,13 @@ interface SqlInterface * * @see ArgumentType */ - public const TYPE_IDENTIFIER = 'identifier'; + public const TYPE_IDENTIFIER = 'identifier'; - public const TYPE_VALUE = 'value'; + public const TYPE_VALUE = 'value'; - public const TYPE_LITERAL = 'literal'; + public const TYPE_LITERAL = 'literal'; - public const TYPE_SELECT = 'select'; + public const TYPE_SELECT = 'select'; /** * Get SQL string for statement diff --git a/src/Sql/TableIdentifier.php b/src/Sql/TableIdentifier.php index eb5daee8f..de63add70 100644 --- a/src/Sql/TableIdentifier.php +++ b/src/Sql/TableIdentifier.php @@ -14,16 +14,16 @@ public function __construct(string $table, ?string $schema = null) { if ('' === $table) { throw new Exception\InvalidArgumentException( - '$table must be a valid table name, empty string given' + '$table must be a valid table name, empty string given', ); } $this->table = $table; - if ($schema !== null) { + if (null !== $schema) { if ('' === $schema) { throw new Exception\InvalidArgumentException( - '$schema must be a valid schema name or null, empty string given' + '$schema must be a valid schema name or null, empty string given', ); } @@ -31,14 +31,14 @@ public function __construct(string $table, ?string $schema = null) } } - public function getTable(): string + public function getSchema(): ?string { - return $this->table; + return $this->schema; } - public function getSchema(): ?string + public function getTable(): string { - return $this->schema; + return $this->table; } /** @return array{0: string, 1: null|string} */ diff --git a/src/Sql/Update.php b/src/Sql/Update.php index 8361a7015..71826c8cb 100644 --- a/src/Sql/Update.php +++ b/src/Sql/Update.php @@ -10,10 +10,7 @@ use PhpDb\Adapter\Driver\PdoDriverInterface; use PhpDb\Adapter\ParameterContainer; use PhpDb\Adapter\Platform\PlatformInterface; -use PhpDb\Sql\Join; use PhpDb\Sql\Predicate\PredicateInterface; -use PhpDb\Sql\TableIdentifier; -use PhpDb\Sql\Where; use function array_key_exists; use function implode; @@ -43,7 +40,7 @@ class Update extends AbstractPreparableSql final public const VALUES_SET = 'set'; - /**@#-**/ + /** @#-* */ /** @var array|array */ protected array $specifications = [ @@ -77,31 +74,27 @@ public function __construct(string|TableIdentifier|null $table = null) } } - private function getSet(): PriorityList - { - if ($this->set === null) { - $this->set = new PriorityList(); - $this->set->isLIFO(false); - } - return $this->set; - } - - private function getWhere(): Where - { - return $this->where ??= new Where(); - } - - private function getJoins(): Join + public function getRawState(?string $key = null): mixed { - return $this->joins ??= new Join(); + $rawState = [ + 'emptyWhereProtection' => $this->emptyWhereProtection, + 'table' => $this->table, + 'set' => $this->getSet()->toArray(), + 'where' => $this->getWhere(), + 'joins' => $this->getJoins(), + ]; + return null !== $key && array_key_exists($key, $rawState) ? $rawState[$key] : $rawState; } /** - * Specify table for statement + * Create join clause + * + * @throws Exception\InvalidArgumentException */ - public function table(TableIdentifier|string|array $table): static + public function join(array|string|TableIdentifier $name, string $on, string $type = Join::JOIN_INNER): static { - $this->table = $table; + $this->getJoins()->join($name, $on, [], $type); + return $this; } @@ -115,7 +108,7 @@ public function table(TableIdentifier|string|array $table): static public function set(array $values, string|int $flag = self::VALUES_SET): static { $set = $this->getSet(); - if ($flag === self::VALUES_SET) { + if (self::VALUES_SET === $flag) { $set->clear(); } @@ -131,6 +124,15 @@ public function set(array $values, string|int $flag = self::VALUES_SET): static return $this; } + /** + * Specify table for statement + */ + public function table(TableIdentifier|string|array $table): static + { + $this->table = $table; + return $this; + } + /** * Create where clause * @@ -138,7 +140,7 @@ public function set(array $values, string|int $flag = self::VALUES_SET): static */ public function where( PredicateInterface|array|Closure|string|Where $predicate, - string $combination = Predicate\PredicateSet::OP_AND + string $combination = Predicate\PredicateSet::OP_AND, ): static { if ($predicate instanceof Where) { $this->where = $predicate; @@ -149,53 +151,26 @@ public function where( return $this; } - /** - * Create join clause - * - * @throws Exception\InvalidArgumentException - */ - public function join(array|string|TableIdentifier $name, string $on, string $type = Join::JOIN_INNER): static - { - $this->getJoins()->join($name, $on, [], $type); - - return $this; - } - - public function getRawState(?string $key = null): mixed - { - $rawState = [ - 'emptyWhereProtection' => $this->emptyWhereProtection, - 'table' => $this->table, - 'set' => $this->getSet()->toArray(), - 'where' => $this->getWhere(), - 'joins' => $this->getJoins(), - ]; - return $key !== null && array_key_exists($key, $rawState) ? $rawState[$key] : $rawState; - } - - protected function processUpdate( + /** @return string[][][]|null */ + protected function processJoins( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null - ): string { - return str_replace( - '%1$s', - $this->resolveTable($this->table, $platform, $driver, $parameterContainer), - $this->specifications[static::SPECIFICATION_UPDATE] - ); + ?ParameterContainer $parameterContainer = null, + ): ?array { + return $this->processJoin($this->joins, $platform, $driver, $parameterContainer); } protected function processSet( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): string { $setSql = []; $i = 0; $isPdoDriver = $driver instanceof PdoDriverInterface; foreach ($this->getSet() as $column => $value) { - $prefix = $this->resolveColumnValue( + $prefix = $this->resolveColumnValue( [ 'column' => $column, 'fromTable' => '', @@ -204,7 +179,7 @@ protected function processSet( $platform, $driver, $parameterContainer, - 'column' + 'column', ); $prefix .= ' = '; if (is_scalar($value) && $parameterContainer) { @@ -217,11 +192,12 @@ protected function processSet( $setSql[] = $prefix . $driver->formatParameterName($column); $parameterContainer->offsetSet($column, $value); } else { - $setSql[] = $prefix . $this->resolveColumnValue( + $setSql[] = $prefix + . $this->resolveColumnValue( $value, $platform, $driver, - $parameterContainer + $parameterContainer, ); } } @@ -229,46 +205,55 @@ protected function processSet( return str_replace( '%1$s', implode(', ', $setSql), - $this->specifications[static::SPECIFICATION_SET] + $this->specifications[static::SPECIFICATION_SET], + ); + } + + protected function processUpdate( + PlatformInterface $platform, + ?DriverInterface $driver = null, + ?ParameterContainer $parameterContainer = null, + ): string { + return str_replace( + '%1$s', + $this->resolveTable($this->table, $platform, $driver, $parameterContainer), + $this->specifications[static::SPECIFICATION_UPDATE], ); } protected function processWhere( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): ?string { - if ($this->where === null || $this->where->count() === 0) { + if (null === $this->where || $this->where->count() === 0) { return null; } return str_replace( '%1$s', $this->processExpression($this->where, $platform, $driver, $parameterContainer, 'where'), - $this->specifications[static::SPECIFICATION_WHERE] + $this->specifications[static::SPECIFICATION_WHERE], ); } - /** @return string[][][]|null */ - protected function processJoins( - PlatformInterface $platform, - ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null - ): ?array { - return $this->processJoin($this->joins, $platform, $driver, $parameterContainer); + private function getJoins(): Join + { + return $this->joins ??= new Join(); } - /** - * Variable overloading - * Proxies to "where" only - */ - public function __get(string $name): ?Where + private function getSet(): PriorityList { - if (strtolower($name) === 'where') { - return $this->getWhere(); + if (null === $this->set) { + $this->set = new PriorityList(); + $this->set->isLIFO(false); } + return $this->set; + } - return null; + private function getWhere(): Where + { + return $this->where ??= new Where(); } /** @@ -280,14 +265,27 @@ public function __get(string $name): ?Where */ public function __clone() { - if ($this->where !== null) { + if (null !== $this->where) { $this->where = clone $this->where; } - if ($this->joins !== null) { + if (null !== $this->joins) { $this->joins = clone $this->joins; } - if ($this->set !== null) { + if (null !== $this->set) { $this->set = clone $this->set; } } + + /** + * Variable overloading + * Proxies to "where" only + */ + public function __get(string $name): ?Where + { + if (strtolower($name) === 'where') { + return $this->getWhere(); + } + + return null; + } } diff --git a/src/Sql/Where.php b/src/Sql/Where.php index b8d9830be..e9bddaa2e 100644 --- a/src/Sql/Where.php +++ b/src/Sql/Where.php @@ -4,6 +4,4 @@ namespace PhpDb\Sql; -class Where extends Predicate\Predicate -{ -} +class Where extends Predicate\Predicate {} diff --git a/src/TableGateway/AbstractTableGateway.php b/src/TableGateway/AbstractTableGateway.php index daaaf2400..808a1a726 100644 --- a/src/TableGateway/AbstractTableGateway.php +++ b/src/TableGateway/AbstractTableGateway.php @@ -51,9 +51,75 @@ abstract class AbstractTableGateway implements TableGatewayInterface protected string|int|false|null $lastInsertValue = null; - public function isInitialized(): bool + #[Override] + public function delete(Where|Closure|array|string $where): int { - return $this->isInitialized; + if (! $this->isInitialized) { + $this->initialize(); + } + $delete = $this->sql->delete(); + if ($where instanceof Closure) { + $where($delete); + } else { + $delete->where($where); + } + + return $this->executeDelete($delete); + } + + public function deleteWith(Delete $delete): int + { + $this->initialize(); + + return $this->executeDelete($delete); + } + + public function getAdapter(): AdapterInterface + { + return $this->adapter; + } + + public function getColumns(): array + { + return $this->columns; + } + + public function getFeatureSet(): Feature\FeatureSet + { + if (! $this->isInitialized) { + $this->initialize(); + } + + return $this->featureSet; + } + + public function getLastInsertValue(): string|int|false|null + { + return $this->lastInsertValue; + } + + public function getResultSetPrototype(): ResultSetInterface + { + if (! $this->isInitialized) { + $this->initialize(); + } + + return $this->resultSetPrototype; + } + + public function getSql(): Sql + { + if (! $this->isInitialized) { + $this->initialize(); + } + + return $this->sql; + } + + #[Override] + public function getTable(): TableIdentifier|array|string + { + return $this->table; } /** @@ -67,26 +133,26 @@ public function initialize(): void return; } - if ($this->featureSet === null) { + if (null === $this->featureSet) { $this->featureSet = new Feature\FeatureSet(); } $this->featureSet->setTableGateway($this); $this->featureSet->apply(EventFeatureEventsInterface::EVENT_PRE_INITIALIZE, []); - if ($this->adapter === null) { + if (null === $this->adapter) { throw new Exception\RuntimeException('This table does not have an Adapter setup'); } - if ($this->table === null) { + if (null === $this->table) { throw new Exception\RuntimeException('This table object does not have a valid table set.'); } - if ($this->resultSetPrototype === null) { + if (null === $this->resultSetPrototype) { $this->resultSetPrototype = new ResultSet(); } - if ($this->sql === null) { + if (null === $this->sql) { $this->sql = new Sql($this->adapter, $this->table); } @@ -96,46 +162,29 @@ public function initialize(): void } #[Override] - public function getTable(): TableIdentifier|array|string - { - return $this->table; - } - - public function getAdapter(): AdapterInterface - { - return $this->adapter; - } - - public function getColumns(): array - { - return $this->columns; - } - - public function getFeatureSet(): Feature\FeatureSet + public function insert(array $set): int { if (! $this->isInitialized) { $this->initialize(); } + $insert = $this->sql->insert(); + $insert->values($set); - return $this->featureSet; + return $this->executeInsert($insert); } - public function getResultSetPrototype(): ResultSetInterface + public function insertWith(Insert $insert): int { if (! $this->isInitialized) { $this->initialize(); } - return $this->resultSetPrototype; + return $this->executeInsert($insert); } - public function getSql(): Sql + public function isInitialized(): bool { - if (! $this->isInitialized) { - $this->initialize(); - } - - return $this->sql; + return $this->isInitialized; } #[Override] @@ -149,7 +198,7 @@ public function select(Where|Closure|string|array|null $where = null): ResultSet if ($where instanceof Closure) { $where($select); - } elseif ($where !== null) { + } elseif (null !== $where) { $select->where($where); } @@ -165,67 +214,76 @@ public function selectWith(Select $select): ResultSetInterface return $this->executeSelect($select); } - /** - * @throws Exception\RuntimeException - */ - protected function executeSelect(Select $select): ResultSetInterface - { - $selectState = $select->getRawState(); - if ( - isset($selectState['table']) - && $selectState['table'] !== $this->table - && (is_array($selectState['table']) - && end($selectState['table']) !== $this->table) - ) { - throw new Exception\RuntimeException( - 'The table name of the provided Select object must match that of the table' - ); + #[Override] + public function update( + array $set, + Where|Closure|array|string|null $where = null, + ?array $joins = null, + ): int { + if (! $this->isInitialized) { + $this->initialize(); } - - if ( - isset($selectState['columns']) - && $selectState['columns'] === [Select::SQL_STAR] - && $this->columns !== [] - ) { - $select->columns($this->columns); + $sql = $this->sql; + $update = $sql->update(); + $update->set($set); + if (null !== $where) { + $update->where($where); } - // apply preSelect features - $this->featureSet->apply(EventFeatureEventsInterface::EVENT_PRE_SELECT, [$select]); - - // prepare and execute - $statement = $this->sql->prepareStatementForSqlObject($select); - $result = $statement->execute(); - - // build result set - $resultSet = clone $this->resultSetPrototype; - $resultSet->initialize($result); - - // apply postSelect features - $this->featureSet->apply(EventFeatureEventsInterface::EVENT_POST_SELECT, [$statement, $result, $resultSet]); + if ($joins) { + foreach ($joins as $join) { + $type = $join['type'] ?? Join::JOIN_INNER; + $update->join($join['name'], $join['on'], $type); + } + } - return $resultSet; + return $this->executeUpdate($update); } - #[Override] - public function insert(array $set): int + public function updateWith(Update $update): int { if (! $this->isInitialized) { $this->initialize(); } - $insert = $this->sql->insert(); - $insert->values($set); - return $this->executeInsert($insert); + return $this->executeUpdate($update); } - public function insertWith(Insert $insert): int + /** + * @throws Exception\RuntimeException + * @todo add $columns support + */ + protected function executeDelete(Delete $delete): int { - if (! $this->isInitialized) { - $this->initialize(); + $deleteState = $delete->getRawState(); + if ($deleteState['table'] !== $this->table) { + throw new Exception\RuntimeException( + 'The table name of the provided Delete object must match that of the table', + ); } - return $this->executeInsert($insert); + // pre delete update + $this->featureSet->apply(EventFeatureEventsInterface::EVENT_PRE_DELETE, [$delete]); + + $unaliasedTable = false; + if (is_array($deleteState['table'])) { + $tableData = array_values($deleteState['table']); + $unaliasedTable = array_shift($tableData); + $delete->from($unaliasedTable); + } + + $statement = $this->sql->prepareStatementForSqlObject($delete); + $result = $statement->execute(); + + // apply postDelete features + $this->featureSet->apply(EventFeatureEventsInterface::EVENT_POST_DELETE, [$statement, $result]); + + // Reset original table information in Delete instance, if necessary + if ($unaliasedTable) { + $delete->from($deleteState['table']); + } + + return $result->getAffectedRows(); } /** @@ -237,7 +295,7 @@ protected function executeInsert(Insert $insert): int $insertState = $insert->getRawState(); if ($insertState['table'] !== $this->table) { throw new Exception\RuntimeException( - 'The table name of the provided Insert object must match that of the table' + 'The table name of the provided Insert object must match that of the table', ); } @@ -268,39 +326,48 @@ protected function executeInsert(Insert $insert): int return $result->getAffectedRows(); } - #[Override] - public function update( - array $set, - Where|Closure|array|string|null $where = null, - ?array $joins = null - ): int { - if (! $this->isInitialized) { - $this->initialize(); - } - $sql = $this->sql; - $update = $sql->update(); - $update->set($set); - if ($where !== null) { - $update->where($where); + /** + * @throws Exception\RuntimeException + */ + protected function executeSelect(Select $select): ResultSetInterface + { + $selectState = $select->getRawState(); + if ( + isset($selectState['table']) + && $selectState['table'] !== $this->table + && ( + is_array($selectState['table']) + && end($selectState['table']) !== $this->table + ) + ) { + throw new Exception\RuntimeException( + 'The table name of the provided Select object must match that of the table', + ); } - if ($joins) { - foreach ($joins as $join) { - $type = $join['type'] ?? Join::JOIN_INNER; - $update->join($join['name'], $join['on'], $type); - } + if ( + isset($selectState['columns']) + && [Select::SQL_STAR] === $selectState['columns'] + && [] !== $this->columns + ) { + $select->columns($this->columns); } - return $this->executeUpdate($update); - } + // apply preSelect features + $this->featureSet->apply(EventFeatureEventsInterface::EVENT_PRE_SELECT, [$select]); - public function updateWith(Update $update): int - { - if (! $this->isInitialized) { - $this->initialize(); - } + // prepare and execute + $statement = $this->sql->prepareStatementForSqlObject($select); + $result = $statement->execute(); - return $this->executeUpdate($update); + // build result set + $resultSet = clone $this->resultSetPrototype; + $resultSet->initialize($result); + + // apply postSelect features + $this->featureSet->apply(EventFeatureEventsInterface::EVENT_POST_SELECT, [$statement, $result, $resultSet]); + + return $resultSet; } /** @@ -312,7 +379,7 @@ protected function executeUpdate(Update $update): int $updateState = $update->getRawState(); if ($updateState['table'] !== $this->table) { throw new Exception\RuntimeException( - 'The table name of the provided Update object must match that of the table' + 'The table name of the provided Update object must match that of the table', ); } @@ -340,69 +407,36 @@ protected function executeUpdate(Update $update): int return $result->getAffectedRows(); } - #[Override] - public function delete(Where|Closure|array|string $where): int - { - if (! $this->isInitialized) { - $this->initialize(); - } - $delete = $this->sql->delete(); - if ($where instanceof Closure) { - $where($delete); - } else { - $delete->where($where); - } - - return $this->executeDelete($delete); - } - - public function deleteWith(Delete $delete): int - { - $this->initialize(); - - return $this->executeDelete($delete); - } - /** - * @throws Exception\RuntimeException - * @todo add $columns support + * @throws Exception\InvalidArgumentException */ - protected function executeDelete(Delete $delete): int + public function __call(string $method, array $arguments): mixed { - $deleteState = $delete->getRawState(); - if ($deleteState['table'] !== $this->table) { - throw new Exception\RuntimeException( - 'The table name of the provided Delete object must match that of the table' - ); - } - - // pre delete update - $this->featureSet->apply(EventFeatureEventsInterface::EVENT_PRE_DELETE, [$delete]); - - $unaliasedTable = false; - if (is_array($deleteState['table'])) { - $tableData = array_values($deleteState['table']); - $unaliasedTable = array_shift($tableData); - $delete->from($unaliasedTable); - } - - $statement = $this->sql->prepareStatementForSqlObject($delete); - $result = $statement->execute(); - - // apply postDelete features - $this->featureSet->apply(EventFeatureEventsInterface::EVENT_POST_DELETE, [$statement, $result]); - - // Reset original table information in Delete instance, if necessary - if ($unaliasedTable) { - $delete->from($deleteState['table']); + if ($this->featureSet->canCallMagicCall($method)) { + return $this->featureSet->callMagicCall($method, $arguments); } - - return $result->getAffectedRows(); + throw new Exception\InvalidArgumentException(sprintf( + 'Invalid method (%s) called, caught by %s::__call()', + $method, + self::class, + )); } - public function getLastInsertValue(): string|int|false|null + public function __clone(): void { - return $this->lastInsertValue; + $this->resultSetPrototype = isset($this->resultSetPrototype) ? clone $this->resultSetPrototype : null; + $this->sql = clone $this->sql; + if (is_object($this->table)) { + $this->table = clone $this->table; + } elseif ( + is_array($this->table) + && count($this->table) === 1 + && is_object(reset($this->table)) + ) { + foreach ($this->table as &$tableObject) { + $tableObject = clone $tableObject; + } + } } /** @@ -411,12 +445,10 @@ public function getLastInsertValue(): string|int|false|null public function __get(string $property): mixed { return match (true) { - 'lastInsertValue' === $property, - 'adapter' === $property, - 'table' === $property => $this->$property, + 'lastInsertValue' === $property, 'adapter' === $property, 'table' === $property => $this->$property, $this->featureSet->canCallMagicGet($property) => $this->featureSet->callMagicGet($property), default => throw new Exception\InvalidArgumentException( - 'Invalid magic property access in ' . self::class . '::__get()' + 'Invalid magic property access in ' . self::class . '::__get()', ), }; } @@ -433,36 +465,4 @@ public function __set(string $property, mixed $value): void } throw new Exception\InvalidArgumentException('Invalid magic property access in ' . self::class . '::__set()'); } - - /** - * @throws Exception\InvalidArgumentException - */ - public function __call(string $method, array $arguments): mixed - { - if ($this->featureSet->canCallMagicCall($method)) { - return $this->featureSet->callMagicCall($method, $arguments); - } - throw new Exception\InvalidArgumentException(sprintf( - 'Invalid method (%s) called, caught by %s::__call()', - $method, - self::class - )); - } - - public function __clone(): void - { - $this->resultSetPrototype = isset($this->resultSetPrototype) ? clone $this->resultSetPrototype : null; - $this->sql = clone $this->sql; - if (is_object($this->table)) { - $this->table = clone $this->table; - } elseif ( - is_array($this->table) - && count($this->table) === 1 - && is_object(reset($this->table)) - ) { - foreach ($this->table as &$tableObject) { - $tableObject = clone $tableObject; - } - } - } } diff --git a/src/TableGateway/Exception/ExceptionInterface.php b/src/TableGateway/Exception/ExceptionInterface.php index 3807d1a7d..233c70558 100644 --- a/src/TableGateway/Exception/ExceptionInterface.php +++ b/src/TableGateway/Exception/ExceptionInterface.php @@ -6,6 +6,4 @@ use PhpDb\Exception; -interface ExceptionInterface extends Exception\ExceptionInterface -{ -} +interface ExceptionInterface extends Exception\ExceptionInterface {} diff --git a/src/TableGateway/Exception/InvalidArgumentException.php b/src/TableGateway/Exception/InvalidArgumentException.php index 0c18263f3..256fcc324 100644 --- a/src/TableGateway/Exception/InvalidArgumentException.php +++ b/src/TableGateway/Exception/InvalidArgumentException.php @@ -6,6 +6,4 @@ use PhpDb\Exception; -class InvalidArgumentException extends Exception\InvalidArgumentException implements ExceptionInterface -{ -} +class InvalidArgumentException extends Exception\InvalidArgumentException implements ExceptionInterface {} diff --git a/src/TableGateway/Exception/RuntimeException.php b/src/TableGateway/Exception/RuntimeException.php index 9e230aff4..7d2902e49 100644 --- a/src/TableGateway/Exception/RuntimeException.php +++ b/src/TableGateway/Exception/RuntimeException.php @@ -6,6 +6,4 @@ use PhpDb\Exception; -class RuntimeException extends Exception\InvalidArgumentException implements ExceptionInterface -{ -} +class RuntimeException extends Exception\InvalidArgumentException implements ExceptionInterface {} diff --git a/src/TableGateway/Feature/AbstractFeature.php b/src/TableGateway/Feature/AbstractFeature.php index 92e872d5d..310dd89e8 100644 --- a/src/TableGateway/Feature/AbstractFeature.php +++ b/src/TableGateway/Feature/AbstractFeature.php @@ -4,6 +4,7 @@ namespace PhpDb\TableGateway\Feature; +use Override; use PhpDb\TableGateway\AbstractTableGateway; abstract class AbstractFeature extends AbstractTableGateway implements FeatureInterface @@ -12,37 +13,41 @@ abstract class AbstractFeature extends AbstractTableGateway implements FeatureIn protected array $sharedData = []; - public function getName(): string + /** @return array */ + #[Override] + public function getMagicMethodSpecifications(): array { - return static::class; + return []; } - public function setTableGateway(AbstractTableGateway $tableGateway): void + #[Override] + public function getName(): string { - $this->tableGateway = $tableGateway; + return static::class; } + #[Override] public function initialize(): void { // No-op } - /** @return array */ - public function getMagicMethodSpecifications(): array + #[Override] + public function setTableGateway(AbstractTableGateway $tableGateway): void { - return []; + $this->tableGateway = $tableGateway; } /* - public function preInitialize(); - public function postInitialize(); - public function preSelect(Select $select); - public function postSelect(StatementInterface $statement, ResultInterface $result, ResultSetInterface $resultSet); - public function preInsert(Insert $insert); - public function postInsert(StatementInterface $statement, ResultInterface $result); - public function preUpdate(Update $update); - public function postUpdate(StatementInterface $statement, ResultInterface $result); - public function preDelete(Delete $delete); - public function postDelete(StatementInterface $statement, ResultInterface $result); - */ + * public function preInitialize(); + * public function postInitialize(); + * public function preSelect(Select $select); + * public function postSelect(StatementInterface $statement, ResultInterface $result, ResultSetInterface $resultSet); + * public function preInsert(Insert $insert); + * public function postInsert(StatementInterface $statement, ResultInterface $result); + * public function preUpdate(Update $update); + * public function postUpdate(StatementInterface $statement, ResultInterface $result); + * public function preDelete(Delete $delete); + * public function postDelete(StatementInterface $statement, ResultInterface $result); + */ } diff --git a/src/TableGateway/Feature/EventFeature.php b/src/TableGateway/Feature/EventFeature.php index d78fd6dfa..cac06cd6a 100644 --- a/src/TableGateway/Feature/EventFeature.php +++ b/src/TableGateway/Feature/EventFeature.php @@ -7,6 +7,7 @@ use Laminas\EventManager\EventManager; use Laminas\EventManager\EventManagerInterface; use Laminas\EventManager\EventsCapableInterface; +use Override; use PhpDb\Adapter\Driver\ResultInterface; use PhpDb\Adapter\Driver\StatementInterface; use PhpDb\ResultSet\ResultSetInterface; @@ -18,9 +19,7 @@ use function get_class; -class EventFeature extends AbstractFeature implements - EventFeatureEventsInterface, - EventsCapableInterface +class EventFeature extends AbstractFeature implements EventFeatureEventsInterface, EventsCapableInterface { protected EventManagerInterface $eventManager; @@ -28,11 +27,11 @@ class EventFeature extends AbstractFeature implements public function __construct( ?EventManagerInterface $eventManager = null, - ?EventFeature\TableGatewayEvent $tableGatewayEvent = null + ?EventFeature\TableGatewayEvent $tableGatewayEvent = null, ) { $this->eventManager = $eventManager instanceof EventManagerInterface - ? $eventManager - : new EventManager(); + ? $eventManager + : new EventManager(); $this->eventManager->addIdentifiers([ TableGateway::class, @@ -42,36 +41,36 @@ public function __construct( } /** - * Retrieve composed event manager instance + * Retrieve composed event instance */ - public function getEventManager(): EventManagerInterface + public function getEvent(): EventFeature\TableGatewayEvent { - return $this->eventManager; + return $this->event; } /** - * Retrieve composed event instance + * Retrieve composed event manager instance */ - public function getEvent(): EventFeature\TableGatewayEvent + #[Override] + public function getEventManager(): EventManagerInterface { - return $this->event; + return $this->eventManager; } /** - * Initialize feature and trigger "preInitialize" event + * Trigger the "postDelete" event * - * Ensures that the composed TableGateway has identifiers based on the - * class name, and that the event target is set to the TableGateway - * instance. It then triggers the "preInitialize" event. + * Triggers the "postDelete" event mapping the following parameters: + * - $statement as "statement" + * - $result as "result" */ - public function preInitialize(): void + public function postDelete(StatementInterface $statement, ResultInterface $result): void { - if (get_class($this->tableGateway) !== TableGateway::class) { - $this->eventManager->addIdentifiers([get_class($this->tableGateway)]); - } - - $this->event->setTarget($this->tableGateway); - $this->event->setName(static::EVENT_PRE_INITIALIZE); + $this->event->setName(static::EVENT_POST_DELETE); + $this->event->setParams([ + 'statement' => $statement, + 'result' => $result, + ]); $this->eventManager->triggerEvent($this->event); } @@ -85,15 +84,19 @@ public function postInitialize(): void } /** - * Trigger the "preSelect" event + * Trigger the "postInsert" event * - * Triggers the "preSelect" event mapping the following parameters: - * - $select as "select" + * Triggers the "postInsert" event mapping the following parameters: + * - $statement as "statement" + * - $result as "result" */ - public function preSelect(Select $select): void + public function postInsert(StatementInterface $statement, ResultInterface $result): void { - $this->event->setName(static::EVENT_PRE_SELECT); - $this->event->setParams(['select' => $select]); + $this->event->setName(static::EVENT_POST_INSERT); + $this->event->setParams([ + 'statement' => $statement, + 'result' => $result, + ]); $this->eventManager->triggerEvent($this->event); } @@ -108,7 +111,7 @@ public function preSelect(Select $select): void public function postSelect( StatementInterface $statement, ResultInterface $result, - ResultSetInterface $resultSet + ResultSetInterface $resultSet, ): void { $this->event->setName(static::EVENT_POST_SELECT); $this->event->setParams([ @@ -120,92 +123,89 @@ public function postSelect( } /** - * Trigger the "preInsert" event + * Trigger the "postUpdate" event * - * Triggers the "preInsert" event mapping the following parameters: - * - $insert as "insert" + * Triggers the "postUpdate" event mapping the following parameters: + * - $statement as "statement" + * - $result as "result" */ - public function preInsert(Insert $insert): void + public function postUpdate(StatementInterface $statement, ResultInterface $result): void { - $this->event->setName(static::EVENT_PRE_INSERT); - $this->event->setParams(['insert' => $insert]); + $this->event->setName(static::EVENT_POST_UPDATE); + $this->event->setParams([ + 'statement' => $statement, + 'result' => $result, + ]); $this->eventManager->triggerEvent($this->event); } /** - * Trigger the "postInsert" event + * Trigger the "preDelete" event * - * Triggers the "postInsert" event mapping the following parameters: - * - $statement as "statement" - * - $result as "result" + * Triggers the "preDelete" event mapping the following parameters: + * - $delete as "delete" */ - public function postInsert(StatementInterface $statement, ResultInterface $result): void + public function preDelete(Delete $delete): void { - $this->event->setName(static::EVENT_POST_INSERT); - $this->event->setParams([ - 'statement' => $statement, - 'result' => $result, - ]); + $this->event->setName(static::EVENT_PRE_DELETE); + $this->event->setParams(['delete' => $delete]); $this->eventManager->triggerEvent($this->event); } /** - * Trigger the "preUpdate" event + * Initialize feature and trigger "preInitialize" event * - * Triggers the "preUpdate" event mapping the following parameters: - * - $update as "update" + * Ensures that the composed TableGateway has identifiers based on the + * class name, and that the event target is set to the TableGateway + * instance. It then triggers the "preInitialize" event. */ - public function preUpdate(Update $update): void + public function preInitialize(): void { - $this->event->setName(static::EVENT_PRE_UPDATE); - $this->event->setParams(['update' => $update]); + if (get_class($this->tableGateway) !== TableGateway::class) { + $this->eventManager->addIdentifiers([get_class($this->tableGateway)]); + } + + $this->event->setTarget($this->tableGateway); + $this->event->setName(static::EVENT_PRE_INITIALIZE); $this->eventManager->triggerEvent($this->event); } /** - * Trigger the "postUpdate" event + * Trigger the "preInsert" event * - * Triggers the "postUpdate" event mapping the following parameters: - * - $statement as "statement" - * - $result as "result" + * Triggers the "preInsert" event mapping the following parameters: + * - $insert as "insert" */ - public function postUpdate(StatementInterface $statement, ResultInterface $result): void + public function preInsert(Insert $insert): void { - $this->event->setName(static::EVENT_POST_UPDATE); - $this->event->setParams([ - 'statement' => $statement, - 'result' => $result, - ]); + $this->event->setName(static::EVENT_PRE_INSERT); + $this->event->setParams(['insert' => $insert]); $this->eventManager->triggerEvent($this->event); } /** - * Trigger the "preDelete" event + * Trigger the "preSelect" event * - * Triggers the "preDelete" event mapping the following parameters: - * - $delete as "delete" + * Triggers the "preSelect" event mapping the following parameters: + * - $select as "select" */ - public function preDelete(Delete $delete): void + public function preSelect(Select $select): void { - $this->event->setName(static::EVENT_PRE_DELETE); - $this->event->setParams(['delete' => $delete]); + $this->event->setName(static::EVENT_PRE_SELECT); + $this->event->setParams(['select' => $select]); $this->eventManager->triggerEvent($this->event); } /** - * Trigger the "postDelete" event + * Trigger the "preUpdate" event * - * Triggers the "postDelete" event mapping the following parameters: - * - $statement as "statement" - * - $result as "result" + * Triggers the "preUpdate" event mapping the following parameters: + * - $update as "update" */ - public function postDelete(StatementInterface $statement, ResultInterface $result): void + public function preUpdate(Update $update): void { - $this->event->setName(static::EVENT_POST_DELETE); - $this->event->setParams([ - 'statement' => $statement, - 'result' => $result, - ]); + $this->event->setName(static::EVENT_PRE_UPDATE); + $this->event->setParams(['update' => $update]); $this->eventManager->triggerEvent($this->event); } } diff --git a/src/TableGateway/Feature/EventFeature/TableGatewayEvent.php b/src/TableGateway/Feature/EventFeature/TableGatewayEvent.php index 6449e66ed..f76a374c0 100644 --- a/src/TableGateway/Feature/EventFeature/TableGatewayEvent.php +++ b/src/TableGateway/Feature/EventFeature/TableGatewayEvent.php @@ -5,6 +5,7 @@ namespace PhpDb\TableGateway\Feature\EventFeature; use Laminas\EventManager\EventInterface; +use Override; use PhpDb\TableGateway\AbstractTableGateway; class TableGatewayEvent implements EventInterface @@ -15,36 +16,49 @@ class TableGatewayEvent implements EventInterface protected array|object $params = []; + #[Override] public function getName(): ?string { return $this->name; } /** - * Get target/context from which event was triggered + * Get a single parameter by name + * + * @param string|int $name + * @param mixed $default Default value to return if parameter does not exist */ - public function getTarget(): ?AbstractTableGateway + #[Override] + public function getParam($name, $default = null): mixed { - return $this->target; + return $this->params[$name] ?? $default; } /** * Get parameters passed to the event */ + #[Override] public function getParams(): array|object { return $this->params; } /** - * Get a single parameter by name - * - * @param string|int $name - * @param mixed $default Default value to return if parameter does not exist + * Get target/context from which event was triggered */ - public function getParam($name, $default = null): mixed + #[Override] + public function getTarget(): ?AbstractTableGateway { - return $this->params[$name] ?? $default; + return $this->target; + } + + /** + * Has this event indicated event propagation should stop? + */ + #[Override] + public function propagationIsStopped(): false + { + return false; } /** @@ -52,20 +66,22 @@ public function getParam($name, $default = null): mixed * * @param string $name */ + #[Override] public function setName($name): void { $this->name = $name; } /** - * Set the event target/context + * Set a single parameter by key * - * @param object|string|null $target - * @phpstan-ignore selfOut.type + * @param string|int $name + * @param mixed $value */ - public function setTarget($target): void + #[Override] + public function setParam($name, $value): void { - $this->target = $target; + $this->params[$name] = $value; } /** @@ -74,20 +90,22 @@ public function setTarget($target): void * @param array|object $params * @phpstan-ignore selfOut.type */ + #[Override] public function setParams($params): void { $this->params = $params; } /** - * Set a single parameter by key + * Set the event target/context * - * @param string|int $name - * @param mixed $value + * @param object|string|null $target + * @phpstan-ignore selfOut.type */ - public function setParam($name, $value): void + #[Override] + public function setTarget($target): void { - $this->params[$name] = $value; + $this->target = $target; } /** @@ -95,15 +113,6 @@ public function setParam($name, $value): void * * @param bool $flag */ - public function stopPropagation($flag = true): void - { - } - - /** - * Has this event indicated event propagation should stop? - */ - public function propagationIsStopped(): false - { - return false; - } + #[Override] + public function stopPropagation($flag = true): void {} } diff --git a/src/TableGateway/Feature/FeatureSet.php b/src/TableGateway/Feature/FeatureSet.php index 039e07ae3..efef7c2d4 100644 --- a/src/TableGateway/Feature/FeatureSet.php +++ b/src/TableGateway/Feature/FeatureSet.php @@ -22,32 +22,20 @@ class FeatureSet public function __construct(array $features = []) { - if ($features !== []) { + if ([] !== $features) { $this->addFeatures($features); } } - public function setTableGateway(AbstractTableGateway $tableGateway): static + public function addFeature(FeatureInterface $feature): static { - $this->tableGateway = $tableGateway; - foreach ($this->features as $feature) { + if ($this->tableGateway instanceof TableGatewayInterface) { $feature->setTableGateway($this->tableGateway); } + $this->features[] = $feature; return $this; } - public function getFeatureByClassName(string $featureClassName): ?FeatureInterface - { - $feature = null; - foreach ($this->features as $potentialFeature) { - if ($potentialFeature instanceof $featureClassName) { - $feature = $potentialFeature; - break; - } - } - return $feature; - } - public function addFeatures(array $features): static { foreach ($features as $feature) { @@ -56,30 +44,32 @@ public function addFeatures(array $features): static return $this; } - public function addFeature(FeatureInterface $feature): static + public function apply(string $method, array $args): void { - if ($this->tableGateway instanceof TableGatewayInterface) { - $feature->setTableGateway($this->tableGateway); + foreach ($this->features as $feature) { + if (! method_exists($feature, $method)) { + continue; + } + + $return = $feature->$method(...$args); + if (self::APPLY_HALT === $return) { + break; + } } - $this->features[] = $feature; - return $this; } - public function apply(string $method, array $args): void + /** + * Call method of on added feature as though it were a local method + */ + public function callMagicCall(string $method, array $arguments): mixed { foreach ($this->features as $feature) { if (method_exists($feature, $method)) { - $return = $feature->$method(...$args); - if ($return === self::APPLY_HALT) { - break; - } + return $feature->$method($arguments); } } - } - public function canCallMagicGet(string $property): bool - { - return false; + return null; } public function callMagicGet(string $property): mixed @@ -87,11 +77,6 @@ public function callMagicGet(string $property): mixed return null; } - public function canCallMagicSet(string $property): bool - { - return false; - } - public function callMagicSet(string $property, mixed $value): mixed { return null; @@ -102,7 +87,7 @@ public function callMagicSet(string $property, mixed $value): mixed */ public function canCallMagicCall(string $method): bool { - if ($this->features !== []) { + if ([] !== $this->features) { foreach ($this->features as $feature) { if (method_exists($feature, $method)) { return true; @@ -112,17 +97,36 @@ public function canCallMagicCall(string $method): bool return false; } - /** - * Call method of on added feature as though it were a local method - */ - public function callMagicCall(string $method, array $arguments): mixed + public function canCallMagicGet(string $property): bool { - foreach ($this->features as $feature) { - if (method_exists($feature, $method)) { - return $feature->$method($arguments); + return false; + } + + public function canCallMagicSet(string $property): bool + { + return false; + } + + public function getFeatureByClassName(string $featureClassName): ?FeatureInterface + { + $feature = null; + foreach ($this->features as $potentialFeature) { + if (! $potentialFeature instanceof $featureClassName) { + continue; } + + $feature = $potentialFeature; + break; } + return $feature; + } - return null; + public function setTableGateway(AbstractTableGateway $tableGateway): static + { + $this->tableGateway = $tableGateway; + foreach ($this->features as $feature) { + $feature->setTableGateway($this->tableGateway); + } + return $this; } } diff --git a/src/TableGateway/Feature/GlobalAdapterFeature.php b/src/TableGateway/Feature/GlobalAdapterFeature.php index cb1f5a4b2..de5fadebc 100644 --- a/src/TableGateway/Feature/GlobalAdapterFeature.php +++ b/src/TableGateway/Feature/GlobalAdapterFeature.php @@ -12,19 +12,6 @@ class GlobalAdapterFeature extends AbstractFeature /** @var AdapterInterface[] */ protected static array $staticAdapters = []; - /** - * Set static adapter - */ - public static function setStaticAdapter(AdapterInterface $adapter): void - { - $class = static::class; - - static::$staticAdapters[$class] = $adapter; - if ($class === self::class) { - static::$staticAdapters[self::class] = $adapter; - } - } - /** * Get static adapter * @@ -47,6 +34,19 @@ public static function getStaticAdapter(): AdapterInterface throw new Exception\RuntimeException('No database adapter was found in the static registry.'); } + /** + * Set static adapter + */ + public static function setStaticAdapter(AdapterInterface $adapter): void + { + $class = static::class; + + static::$staticAdapters[$class] = $adapter; + if (self::class === $class) { + static::$staticAdapters[self::class] = $adapter; + } + } + /** * after initialization, retrieve the original adapter as "master" */ diff --git a/src/TableGateway/Feature/MasterSlaveFeature.php b/src/TableGateway/Feature/MasterSlaveFeature.php index 48b4d2bd5..500e56e65 100644 --- a/src/TableGateway/Feature/MasterSlaveFeature.php +++ b/src/TableGateway/Feature/MasterSlaveFeature.php @@ -39,29 +39,29 @@ public function getSlaveSql(): ?Sql public function postInitialize(): void { $this->masterSql = $this->tableGateway->sql; - if ($this->slaveSql === null) { + if (null === $this->slaveSql) { $this->slaveSql = new Sql( $this->slaveAdapter, - $this->tableGateway->sql->getTable() + $this->tableGateway->sql->getTable(), ); } } /** - * preSelect() - * Replace adapter with slave temporarily + * postSelect() + * Ensure to return to the master adapter */ - public function preSelect(): void + public function postSelect(): void { - $this->tableGateway->sql = $this->slaveSql; + $this->tableGateway->sql = $this->masterSql; } /** - * postSelect() - * Ensure to return to the master adapter + * preSelect() + * Replace adapter with slave temporarily */ - public function postSelect(): void + public function preSelect(): void { - $this->tableGateway->sql = $this->masterSql; + $this->tableGateway->sql = $this->slaveSql; } } diff --git a/src/TableGateway/Feature/MetadataFeature.php b/src/TableGateway/Feature/MetadataFeature.php index 1178b3eaa..ac4c2d696 100644 --- a/src/TableGateway/Feature/MetadataFeature.php +++ b/src/TableGateway/Feature/MetadataFeature.php @@ -19,7 +19,7 @@ class MetadataFeature extends AbstractFeature * Constructor */ public function __construct( - protected MetadataInterface $metadata + protected MetadataInterface $metadata, ) { $this->sharedData['metadata'] = [ 'primaryKey' => null, @@ -58,13 +58,15 @@ public function postInitialize(): void $pkc = null; foreach ($m->getConstraints($table, $schema) as $constraint) { - if ($constraint->getType() === 'PRIMARY KEY') { - $pkc = $constraint; - break; + if ($constraint->getType() !== 'PRIMARY KEY') { + continue; } + + $pkc = $constraint; + break; } - if ($pkc === null) { + if (null === $pkc) { throw new Exception\RuntimeException('A primary key for this column could not be found in the metadata.'); } diff --git a/src/TableGateway/Feature/RowGatewayFeature.php b/src/TableGateway/Feature/RowGatewayFeature.php index 5442ca3bf..95cc81618 100644 --- a/src/TableGateway/Feature/RowGatewayFeature.php +++ b/src/TableGateway/Feature/RowGatewayFeature.php @@ -8,7 +8,6 @@ use PhpDb\RowGateway\RowGateway; use PhpDb\RowGateway\RowGatewayInterface; use PhpDb\TableGateway\Exception; -use PhpDb\TableGateway\Feature\MetadataFeature; use function is_string; @@ -30,7 +29,7 @@ public function postInitialize(): void if (! $this->tableGateway->resultSetPrototype instanceof ResultSet) { throw new Exception\RuntimeException( - 'This feature ' . self::class . ' expects the ResultSet to be an instance of ' . ResultSet::class + 'This feature ' . self::class . ' expects the ResultSet to be an instance of ' . ResultSet::class, ); } @@ -40,7 +39,7 @@ public function postInitialize(): void $rowGatewayPrototype = new RowGateway( $primaryKey, $this->tableGateway->table, - $this->tableGateway->adapter + $this->tableGateway->adapter, ); $resultSetPrototype->setArrayObjectPrototype($rowGatewayPrototype); } elseif ($args[0] instanceof RowGatewayInterface) { @@ -50,19 +49,19 @@ public function postInitialize(): void } else { // get from metadata feature $metadata = $this->tableGateway->featureSet->getFeatureByClassName( - MetadataFeature::class + MetadataFeature::class, ); - if ($metadata === null || ! isset($metadata->sharedData['metadata'])) { + if (null === $metadata || ! isset($metadata->sharedData['metadata'])) { throw new Exception\RuntimeException( 'No information was provided to the RowGatewayFeature and/or no MetadataFeature could be consulted ' - . 'to find the primary key necessary for RowGateway object creation.' + . 'to find the primary key necessary for RowGateway object creation.', ); } $primaryKey = $metadata->sharedData['metadata']['primaryKey']; $rowGatewayPrototype = new RowGateway( $primaryKey, $this->tableGateway->table, - $this->tableGateway->adapter + $this->tableGateway->adapter, ); $resultSetPrototype->setArrayObjectPrototype($rowGatewayPrototype); } diff --git a/src/TableGateway/Feature/SequenceFeature.php b/src/TableGateway/Feature/SequenceFeature.php index e6bdce876..4cbe00918 100644 --- a/src/TableGateway/Feature/SequenceFeature.php +++ b/src/TableGateway/Feature/SequenceFeature.php @@ -25,48 +25,23 @@ public function __construct(string $primaryKeyField, string $sequenceName) $this->sequenceName = $sequenceName; } - public function preInsert(Insert $insert): Insert - { - $columns = $insert->getRawState('columns'); - $values = $insert->getRawState('values'); - $key = array_search($this->primaryKeyField, $columns); - if ($key !== false) { - $this->sequenceValue = $values[$key] ?? null; - return $insert; - } - - $this->sequenceValue = $this->nextSequenceId(); - if ($this->sequenceValue === null) { - return $insert; - } - - $insert->values([$this->primaryKeyField => $this->sequenceValue], Insert::VALUES_MERGE); - return $insert; - } - - public function postInsert(StatementInterface $statement, ResultInterface $result): void - { - if ($this->sequenceValue !== null) { - $this->tableGateway->lastInsertValue = $this->sequenceValue; - } - } - /** - * Generate a new value from the specified sequence in the database, and return it. + * Return the most recent value from the specified sequence in the database. * * @throws RuntimeException */ - public function nextSequenceId(): ?int + public function lastSequenceId(): int { $platform = $this->tableGateway->adapter->getPlatform(); $platformName = $platform->getName(); + // todo: Remove string usage $sql = match ($platformName) { - 'Oracle' => 'SELECT ' - . $platform->quoteIdentifier($this->sequenceName) - . '.NEXTVAL as "nextval" FROM dual', - 'PostgreSQL' => 'SELECT NEXTVAL(\'"' . $this->sequenceName . '"\')', - default => throw new RuntimeException('Unsupported platform for retrieving next sequence id'), + 'Oracle' => 'SELECT ' + . $platform->quoteIdentifier($this->sequenceName) + . '.CURRVAL as "currval" FROM dual', + 'PostgreSQL' => 'SELECT LAST_INSERT_ROWID() as "currval"', + default => throw new RuntimeException('Unsupported platform for retrieving last sequence id'), }; $statement = $this->tableGateway->adapter->createStatement(); @@ -74,26 +49,25 @@ public function nextSequenceId(): ?int $result = $statement->execute(); $sequence = $result->current(); unset($statement, $result); - return $sequence['nextval']; + return $sequence['currval']; } /** - * Return the most recent value from the specified sequence in the database. + * Generate a new value from the specified sequence in the database, and return it. * * @throws RuntimeException */ - public function lastSequenceId(): int + public function nextSequenceId(): ?int { $platform = $this->tableGateway->adapter->getPlatform(); $platformName = $platform->getName(); - // todo: Remove string usage $sql = match ($platformName) { - 'Oracle' => 'SELECT ' - . $platform->quoteIdentifier($this->sequenceName) - . '.CURRVAL as "currval" FROM dual', - 'PostgreSQL' => 'SELECT LAST_INSERT_ROWID() as "currval"', - default => throw new RuntimeException('Unsupported platform for retrieving last sequence id'), + 'Oracle' => 'SELECT ' + . $platform->quoteIdentifier($this->sequenceName) + . '.NEXTVAL as "nextval" FROM dual', + 'PostgreSQL' => 'SELECT NEXTVAL(\'"' . $this->sequenceName . '"\')', + default => throw new RuntimeException('Unsupported platform for retrieving next sequence id'), }; $statement = $this->tableGateway->adapter->createStatement(); @@ -101,6 +75,32 @@ public function lastSequenceId(): int $result = $statement->execute(); $sequence = $result->current(); unset($statement, $result); - return $sequence['currval']; + return $sequence['nextval']; + } + + public function postInsert(StatementInterface $statement, ResultInterface $result): void + { + if (null !== $this->sequenceValue) { + $this->tableGateway->lastInsertValue = $this->sequenceValue; + } + } + + public function preInsert(Insert $insert): Insert + { + $columns = $insert->getRawState('columns'); + $values = $insert->getRawState('values'); + $key = array_search($this->primaryKeyField, $columns); + if (false !== $key) { + $this->sequenceValue = $values[$key] ?? null; + return $insert; + } + + $this->sequenceValue = $this->nextSequenceId(); + if (null === $this->sequenceValue) { + return $insert; + } + + $insert->values([$this->primaryKeyField => $this->sequenceValue], Insert::VALUES_MERGE); + return $insert; } } diff --git a/src/TableGateway/TableGateway.php b/src/TableGateway/TableGateway.php index 7969492a9..c26604be0 100644 --- a/src/TableGateway/TableGateway.php +++ b/src/TableGateway/TableGateway.php @@ -21,8 +21,8 @@ public function __construct( TableIdentifier|array|string $table, AdapterInterface $adapter, Feature\FeatureSet|Feature\FeatureInterface|array|null $features = new Feature\FeatureSet(), - ResultSetInterface|null $resultSetPrototype = new ResultSet(), - ?Sql $sql = null + ?ResultSetInterface $resultSetPrototype = new ResultSet(), + ?Sql $sql = null, ) { $this->table = $table; @@ -31,7 +31,7 @@ public function __construct( $this->featureSet = match (true) { $features instanceof Feature\FeatureInterface => new Feature\FeatureSet([$features]), is_array($features) => new Feature\FeatureSet($features), - default => $features, + default => $features, }; $this->resultSetPrototype = $resultSetPrototype; @@ -40,7 +40,7 @@ public function __construct( if ($this->sql->getTable() !== $this->table) { throw new Exception\InvalidArgumentException( - 'The table inside the provided Sql object must match the table of this TableGateway' + 'The table inside the provided Sql object must match the table of this TableGateway', ); } diff --git a/src/TableGateway/TableGatewayInterface.php b/src/TableGateway/TableGatewayInterface.php index 6c9268766..cacf807bc 100644 --- a/src/TableGateway/TableGatewayInterface.php +++ b/src/TableGateway/TableGatewayInterface.php @@ -11,19 +11,19 @@ interface TableGatewayInterface { - public function getTable(): TableIdentifier|string|array; + public function delete(Where|Closure|array|string $where): int; - public function select(Where|Closure|string|array|null $where = null): ResultSetInterface; + public function getTable(): TableIdentifier|string|array; /** * @param array $set */ public function insert(array $set): int; + public function select(Where|Closure|string|array|null $where = null): ResultSetInterface; + /** * @param array $set */ public function update(array $set, Where|Closure|array|string $where): int; - - public function delete(Where|Closure|array|string $where): int; } diff --git a/test/integration/Adapter/Driver/Pdo/AbstractAdapterTestCase.php b/test/integration/Adapter/Driver/Pdo/AbstractAdapterTestCase.php index 2b087df32..6b533d0f7 100644 --- a/test/integration/Adapter/Driver/Pdo/AbstractAdapterTestCase.php +++ b/test/integration/Adapter/Driver/Pdo/AbstractAdapterTestCase.php @@ -62,9 +62,9 @@ protected function isConnectedTcp(): bool $mypid = getmypid(); $dbPort = (string) $this->port; /** @psalm-suppress ForbiddenCode - running lsof */ - $lsof = shell_exec("lsof -i -P -n | grep $dbPort | grep $mypid"); + $lsof = shell_exec("lsof -i -P -n | grep {$dbPort} | grep {$mypid}"); - return $lsof !== null; + return null !== $lsof; } protected function isTcpConnection(): bool diff --git a/test/integration/Adapter/Driver/Pdo/AdapterTrait.php b/test/integration/Adapter/Driver/Pdo/AdapterTrait.php index 5dae1f945..589775f2a 100644 --- a/test/integration/Adapter/Driver/Pdo/AdapterTrait.php +++ b/test/integration/Adapter/Driver/Pdo/AdapterTrait.php @@ -6,12 +6,12 @@ trait AdapterTrait { - protected ?AdapterInterface $adapter = null; - protected ?string $hostname = 'localhost'; + protected ?AdapterInterface $adapter = null; + protected ?string $hostname = 'localhost'; public function getAdapter(): AdapterInterface { - if ($this->adapter === null) { + if (null === $this->adapter) { $this->fail('Adapter not initialized'); } diff --git a/test/unit/Adapter/AdapterTest.php b/test/unit/Adapter/AdapterTest.php index 35585d294..14efdba69 100644 --- a/test/unit/Adapter/AdapterTest.php +++ b/test/unit/Adapter/AdapterTest.php @@ -34,8 +34,6 @@ #[CoversMethod(Adapter::class, 'getQueryResultSetPrototype')] #[CoversMethod(Adapter::class, 'getCurrentSchema')] #[CoversMethod(Adapter::class, 'query')] -#[CoversMethod(Adapter::class, 'prepareQuery')] -#[CoversMethod(Adapter::class, 'executeQuery')] #[CoversMethod(Adapter::class, 'createStatement')] #[CoversMethod(Adapter::class, '__get')] #[CoversMethod(Adapter::class, '__construct')] @@ -53,62 +51,77 @@ final class AdapterTest extends TestCase protected Adapter $adapter; - /** - * @throws Exception - */ - #[Override] - protected function setUp(): void + public function testConstructorWithProfilerDelegatesToSetProfiler(): void { - $this->mockDriver = $this->createMock(DriverInterface::class); - $this->mockConnection = $this->createMock(ConnectionInterface::class); - $this->mockDriver->method('checkEnvironment')->willReturn(true); - $this->mockDriver->method('getConnection') - ->willReturn($this->mockConnection); - $this->mockPlatform = $this->createMock(PlatformInterface::class); - $this->mockStatement = $this->createMock(StatementInterface::class); - $this->mockDriver->method('createStatement') - ->willReturn($this->mockStatement); + $profilerMock = $this->createMock(Profiler\ProfilerInterface::class); + $driverMock = $this->createMock(DriverInterface::class); + $platformMock = $this->createMock(PlatformInterface::class); - $this->adapter = new Adapter($this->mockDriver, $this->mockPlatform); + $adapter = new Adapter( + driver: $driverMock, + platform: $platformMock, + profiler: $profilerMock, + ); + + self::assertSame($profilerMock, $adapter->getProfiler()); } - #[TestDox('unit test: Test setProfiler() will store profiler')] - public function testFluentSetProfiler(): void + #[TestDox('unit test: Test createStatement() produces a statement object')] + public function testCreateStatementDelegatesToDriver(): void { - $ret = $this->adapter->setProfiler(new Profiler\Profiler()); - self::assertSame($this->adapter, $ret); + self::assertSame($this->mockStatement, $this->adapter->createStatement()); } - #[TestDox('unit test: Test getProfiler() will store profiler')] - public function testGetProfilerReturnsProfiler(): void + #[TestDox('unit test: Test executeQuery() returns the raw result without wrapping query results')] + public function testExecuteQueryReturnsRawResultWithoutWrappingQueryResults(): void { - $this->adapter->setProfiler($profiler = new Profiler\Profiler()); - self::assertSame($profiler, $this->adapter->getProfiler()); + $sql = 'SELECT foo'; + $result = $this->createMock(ResultInterface::class); - $adapter = new Adapter( - driver: $this->mockDriver, - platform: $this->mockPlatform, - profiler: new Profiler\Profiler(), - ); - self::assertInstanceOf(Profiler\Profiler::class, $adapter->getProfiler()); + $this->mockConnection->method('execute')->willReturn($result); + $result->expects($this->any())->method('isQueryResult')->willReturn(true); + $result->expects($this->never())->method('getQueryResult'); + + self::assertSame($result, $this->adapter->executeQuery($sql)); } - #[TestDox('unit test: Test getDriver() will return driver object')] - public function testGetDriverReturnsDriver(): void + #[TestDox('unit test: Test executeQuery() throws when execution does not produce a result')] + public function testExecuteQueryThrowsWhenExecutionDoesNotProduceAResult(): void { - self::assertSame($this->mockDriver, $this->adapter->getDriver()); + $sql = 'SELECT foo'; + $this->mockConnection->method('execute')->willReturn(null); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Query execution did not produce a result'); + + $this->adapter->executeQuery($sql); } - #[TestDox('unit test: Test getPlatform() returns platform object')] - public function testGetPlatformReturnsPlatform(): void + #[TestDox('unit test: Test executeQuery() with raw SQL delegates to connection execute')] + public function testExecuteQueryWithRawSqlDelegatesToConnectionExecute(): void { - self::assertSame($this->mockPlatform, $this->adapter->getPlatform()); + $sql = 'SELECT foo'; + $result = $this->createMock(ResultInterface::class); + $this->mockConnection->expects($this->once())->method('execute')->with($sql)->willReturn($result); + + self::assertSame($result, $this->adapter->executeQuery($sql)); } - #[TestDox('unit test: Test getPlatform() returns platform object')] - public function testGetQueryResultSetPrototypeReturnsResultSet(): void + #[TestDox('unit test: Test executeQuery() with a prepared statement executes the statement')] + public function testExecuteQueryWithStatementExecutesStatement(): void { - self::assertInstanceOf(ResultSetInterface::class, $this->adapter->getQueryResultSetPrototype()); + $result = $this->createMock(ResultInterface::class); + $this->mockStatement->expects($this->once())->method('execute')->willReturn($result); + $this->mockConnection->expects($this->never())->method('execute'); + + self::assertSame($result, $this->adapter->executeQuery($this->mockStatement)); + } + + #[TestDox('unit test: Test setProfiler() will store profiler')] + public function testFluentSetProfiler(): void + { + $ret = $this->adapter->setProfiler(new Profiler\Profiler()); + self::assertSame($this->adapter, $ret); } #[TestDox('unit test: Test getCurrentSchema() returns current schema from connection object')] @@ -118,134 +131,115 @@ public function testGetCurrentSchemaDelegatesToConnection(): void self::assertEquals('FooSchema', $this->adapter->getCurrentSchema()); } - /** - * @throws \Exception - */ - #[TestDox('unit test: Test query() in prepare mode produces a statement object')] - public function testQueryWhenPreparedProducesStatement(): void + #[TestDox('unit test: Test getDriver() will return driver object')] + public function testGetDriverReturnsDriver(): void { - $s = $this->adapter->query('SELECT foo'); - self::assertSame($this->mockStatement, $s); + self::assertSame($this->mockDriver, $this->adapter->getDriver()); } - /** - * @throws Exception - * @throws \Exception - */ - #[Group('#210')] - public function testProducedResultSetPrototypeIsDifferentForEachQuery(): void + public function testGetHelpersQuoteIdentifierClosureCallsPlatform(): void { - $result = $this->createMock(ResultInterface::class); + $this->mockPlatform + ->method('quoteIdentifier') + ->with('test') + ->willReturn('"test"'); - $this->mockStatement->method('execute') - ->willReturn($result); - $result->method('isQueryResult') - ->willReturn(true); - $result->method('getQueryResult') - ->willReturnCallback(static fn (): ResultSetInterface => new ResultSet()); + $functions = $this->adapter->getHelpers(Adapter::FUNCTION_QUOTE_IDENTIFIER); + $result = $functions[0]('test'); - self::assertNotSame( - $this->adapter->query('SELECT foo', []), - $this->adapter->query('SELECT foo', []) - ); + self::assertSame('"test"', $result); } - /** - * @throws \Exception - */ - #[TestDox('unit test: Test query() in prepare mode, with array of parameters, produces a result object')] - public function testQueryWhenPreparedWithParameterArrayProducesResult(): void + public function testGetHelpersQuoteValueClosureCallsPlatform(): void { - $parray = ['bar' => 'foo']; - $sql = 'SELECT foo, :bar'; - $result = $this->getMockBuilder(ResultInterface::class)->getMock(); - $this->mockStatement->expects($this->any())->method('execute')->willReturn($result); - $result->expects($this->any())->method('isQueryResult')->willReturn(false); + $this->mockPlatform + ->method('quoteValue') + ->with('test') + ->willThrowException(VunerablePlatformQuoteException::forPlatformAndMethod('test', 'test')); - $r = $this->adapter->query($sql, $parray); - self::assertSame($result, $r); + $functions = $this->adapter->getHelpers(Adapter::FUNCTION_QUOTE_VALUE); + + $this->expectException(VunerablePlatformQuoteException::class); + $functions[0]('test'); } - /** - * @throws \Exception - */ - #[TestDox('unit test: Test query() in prepare mode, with ParameterContainer, produces a result object')] - public function testQueryWhenPreparedWithParameterContainerProducesResult(): void + public function testGetHelpersReturnsBothFunctions(): void { - $sql = 'SELECT foo'; - $parameterContainer = $this->getMockBuilder(ParameterContainer::class)->getMock(); - $result = $this->getMockBuilder(ResultInterface::class)->getMock(); - $this->mockDriver->expects($this->any())->method('createStatement') - ->with($sql)->willReturn($this->mockStatement); - $this->mockStatement->expects($this->any())->method('execute')->willReturn($result); - $result->expects($this->any())->method('isQueryResult')->willReturn(true); - $result->expects($this->any())->method('getQueryResult')->willReturn(new ResultSet()); + $functions = $this->adapter->getHelpers( + Adapter::FUNCTION_QUOTE_IDENTIFIER, + Adapter::FUNCTION_QUOTE_VALUE, + ); - $r = $this->adapter->query($sql, $parameterContainer); - self::assertInstanceOf(ResultSet::class, $r); + self::assertCount(2, $functions); + self::assertIsCallable($functions[0]); + self::assertIsCallable($functions[1]); } - /** - * @throws \Exception - */ - #[TestDox('unit test: Test query() in execute mode produces a driver result object')] - public function testQueryWhenExecutedProducesAResult(): void + public function testGetHelpersReturnsQuoteIdentifierFunction(): void { - $sql = 'SELECT foo'; - $result = $this->getMockBuilder(ResultInterface::class)->getMock(); - $this->mockConnection->expects($this->any())->method('execute')->with($sql)->willReturn($result); - $result->expects($this->any())->method('isQueryResult')->willReturn(false); + $functions = $this->adapter->getHelpers(Adapter::FUNCTION_QUOTE_IDENTIFIER); - $r = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); - self::assertSame($result, $r); + self::assertCount(1, $functions); + self::assertIsCallable($functions[0]); } - /** - * @throws \Exception - */ - #[TestDox('unit test: Test query() in execute mode produces a resultset object')] - public function testQueryWhenExecutedProducesAResultSetObjectWhenResultIsQuery(): void + public function testGetHelpersReturnsQuoteValueFunction(): void { - $sql = 'SELECT foo'; + $functions = $this->adapter->getHelpers(Adapter::FUNCTION_QUOTE_VALUE); - $result = $this->getMockBuilder(ResultInterface::class)->getMock(); - $this->mockConnection->expects($this->any())->method('execute')->with($sql)->willReturn($result); - $result->expects($this->any())->method('isQueryResult')->willReturn(true); - $result->expects($this->any()) - ->method('getQueryResult') - ->willReturnCallback( - static function (?ResultSetInterface $resultPrototype = null): ResultSetInterface { - $resultPrototype ??= new ResultSet(); + self::assertCount(1, $functions); + self::assertIsCallable($functions[0]); + } - return clone $resultPrototype; - } - ); + #[TestDox('unit test: Test getPlatform() returns platform object')] + public function testGetPlatformReturnsPlatform(): void + { + self::assertSame($this->mockPlatform, $this->adapter->getPlatform()); + } - $r = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); - self::assertInstanceOf(ResultSet::class, $r); + #[TestDox('unit test: Test getProfiler() will store profiler')] + public function testGetProfilerReturnsProfiler(): void + { + $this->adapter->setProfiler($profiler = new Profiler\Profiler()); + self::assertSame($profiler, $this->adapter->getProfiler()); - $r = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE, new TemporaryResultSet()); - self::assertInstanceOf(TemporaryResultSet::class, $r); + $adapter = new Adapter( + driver: $this->mockDriver, + platform: $this->mockPlatform, + profiler: new Profiler\Profiler(), + ); + self::assertInstanceOf(Profiler\Profiler::class, $adapter->getProfiler()); } - #[TestDox('unit test: Test prepareQuery() prepares a statement without executing it')] - public function testPrepareQueryPreparesStatementWithoutExecuting(): void + #[TestDox('unit test: Test getPlatform() returns platform object')] + public function testGetQueryResultSetPrototypeReturnsResultSet(): void { - $this->mockStatement->expects($this->once())->method('prepare'); - $this->mockStatement->expects($this->never())->method('execute'); + self::assertInstanceOf(ResultSetInterface::class, $this->adapter->getQueryResultSetPrototype()); + } - $statement = $this->adapter->prepareQuery('SELECT foo'); + public function testMagicGetReturnsDriverAndPlatformCaseInsensitively(): void + { + self::assertSame($this->mockDriver, $this->adapter->driver); + /** @phpstan-ignore property.notFound */ + self::assertSame($this->mockDriver, $this->adapter->DrivER); + /** @phpstan-ignore property.notFound */ + self::assertSame($this->mockPlatform, $this->adapter->PlatForm); + self::assertSame($this->mockPlatform, $this->adapter->platform); - self::assertSame($this->mockStatement, $statement); + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage('Invalid magic'); + /** @phpstan-ignore property.notFound, expr.resultUnused */ + $this->adapter->foo; } #[TestDox('unit test: Test prepareQuery() binds an array of parameters as a ParameterContainer')] public function testPrepareQueryBindsParameterArray(): void { - $this->mockStatement->expects($this->once()) + $this->mockStatement + ->expects($this->once()) ->method('setParameterContainer') ->with(self::callback( - static fn (ParameterContainer $container): bool => $container->getNamedArray() === ['bar' => 'foo'] + static fn(ParameterContainer $container): bool => $container->getNamedArray() === ['bar' => 'foo'], )); $this->adapter->prepareQuery('SELECT foo, :bar', ['bar' => 'foo']); @@ -256,165 +250,182 @@ public function testPrepareQueryBindsParameterContainerDirectly(): void { $parameterContainer = new ParameterContainer(['bar' => 'foo']); - $this->mockStatement->expects($this->once()) + $this->mockStatement + ->expects($this->once()) ->method('setParameterContainer') ->with($parameterContainer); $this->adapter->prepareQuery('SELECT foo, :bar', $parameterContainer); } - #[TestDox('unit test: Test executeQuery() with raw SQL delegates to connection execute')] - public function testExecuteQueryWithRawSqlDelegatesToConnectionExecute(): void + #[TestDox('unit test: Test prepareQuery() prepares a statement without executing it')] + public function testPrepareQueryPreparesStatementWithoutExecuting(): void { - $sql = 'SELECT foo'; - $result = $this->createMock(ResultInterface::class); - $this->mockConnection->expects($this->once())->method('execute')->with($sql)->willReturn($result); - - self::assertSame($result, $this->adapter->executeQuery($sql)); - } + $this->mockStatement->expects($this->once())->method('prepare'); + $this->mockStatement->expects($this->never())->method('execute'); - #[TestDox('unit test: Test executeQuery() with a prepared statement executes the statement')] - public function testExecuteQueryWithStatementExecutesStatement(): void - { - $result = $this->createMock(ResultInterface::class); - $this->mockStatement->expects($this->once())->method('execute')->willReturn($result); - $this->mockConnection->expects($this->never())->method('execute'); + $statement = $this->adapter->prepareQuery('SELECT foo'); - self::assertSame($result, $this->adapter->executeQuery($this->mockStatement)); + self::assertSame($this->mockStatement, $statement); } - #[TestDox('unit test: Test executeQuery() returns the raw result without wrapping query results')] - public function testExecuteQueryReturnsRawResultWithoutWrappingQueryResults(): void + /** + * @throws Exception + * @throws \Exception + */ + #[Group('#210')] + public function testProducedResultSetPrototypeIsDifferentForEachQuery(): void { - $sql = 'SELECT foo'; - $result = $this->createMock(ResultInterface::class); + $statement = $this->createMock(StatementInterface::class); + $result = $this->createMock(ResultInterface::class); - $this->mockConnection->method('execute')->willReturn($result); - $result->expects($this->any())->method('isQueryResult')->willReturn(true); - $result->expects($this->never())->method('getQueryResult'); + $this->mockDriver->method('createStatement')->willReturn($statement); + $this->mockStatement->method('execute')->willReturn($result); + $result->method('isQueryResult') + ->willReturn(true); + $result->method('getQueryResult') + ->willReturnCallback(static fn(): ResultSetInterface => new ResultSet()); - self::assertSame($result, $this->adapter->executeQuery($sql)); + self::assertNotSame( + $this->adapter->query('SELECT foo', []), + $this->adapter->query('SELECT foo', []), + ); } - #[TestDox('unit test: Test executeQuery() throws when execution does not produce a result')] - public function testExecuteQueryThrowsWhenExecutionDoesNotProduceAResult(): void + public function testQueryThrowsOnInvalidParameterType(): void { - $sql = 'SELECT foo'; - $this->mockConnection->method('execute')->willReturn(null); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Query execution did not produce a result'); - - $this->adapter->executeQuery($sql); - } + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Flag incorrectly set'); - #[TestDox('unit test: Test createStatement() produces a statement object')] - public function testCreateStatementDelegatesToDriver(): void - { - self::assertSame($this->mockStatement, $this->adapter->createStatement()); + $this->adapter->query('SELECT 1', 'invalid_mode'); } - public function testMagicGetReturnsDriverAndPlatformCaseInsensitively(): void + /** + * @throws \Exception + */ + #[TestDox('unit test: Test query() in execute mode produces a driver result object')] + public function testQueryWhenExecutedProducesAResult(): void { - self::assertSame($this->mockDriver, $this->adapter->driver); - /** @phpstan-ignore property.notFound */ - self::assertSame($this->mockDriver, $this->adapter->DrivER); - /** @phpstan-ignore property.notFound */ - self::assertSame($this->mockPlatform, $this->adapter->PlatForm); - self::assertSame($this->mockPlatform, $this->adapter->platform); + $sql = 'SELECT foo'; + $result = $this->getMockBuilder(ResultInterface::class)->getMock(); + $this->mockConnection->expects($this->any())->method('execute')->with($sql)->willReturn($result); - $this->expectException('InvalidArgumentException'); - $this->expectExceptionMessage('Invalid magic'); - /** @phpstan-ignore property.notFound, expr.resultUnused */ - $this->adapter->foo; + $r = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); + self::assertSame($result, $r); } - public function testGetHelpersReturnsQuoteIdentifierFunction(): void + /** + * @throws \Exception + */ + #[TestDox('unit test: Test query() in execute mode produces a resultset object')] + public function testQueryWhenExecutedProducesAResultSetObjectWhenResultIsQuery(): void { - $functions = $this->adapter->getHelpers(Adapter::FUNCTION_QUOTE_IDENTIFIER); + $sql = 'SELECT foo'; - self::assertCount(1, $functions); - self::assertIsCallable($functions[0]); - } + $result = $this->getMockBuilder(ResultInterface::class)->getMock(); + $this->mockConnection->expects($this->any())->method('execute')->with($sql)->willReturn($result); + $result->expects($this->any())->method('isQueryResult')->willReturn(true); + $result->expects($this->any()) + ->method('getQueryResult') + ->willReturnCallback( + static function (?ResultSetInterface $resultPrototype = null): ResultSetInterface { + $resultPrototype ??= new ResultSet(); - public function testGetHelpersReturnsQuoteValueFunction(): void - { - $functions = $this->adapter->getHelpers(Adapter::FUNCTION_QUOTE_VALUE); + return clone $resultPrototype; + }, + ); - self::assertCount(1, $functions); - self::assertIsCallable($functions[0]); + $r = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE); + self::assertInstanceOf(ResultSet::class, $r); + + $r = $this->adapter->query($sql, AdapterInterface::QUERY_MODE_EXECUTE, new TemporaryResultSet()); + self::assertInstanceOf(TemporaryResultSet::class, $r); } - public function testGetHelpersReturnsBothFunctions(): void + /** + * @throws \Exception + */ + #[TestDox('unit test: Test query() in prepare mode produces a statement object')] + public function testQueryWhenPreparedProducesStatement(): void { - $functions = $this->adapter->getHelpers( - Adapter::FUNCTION_QUOTE_IDENTIFIER, - Adapter::FUNCTION_QUOTE_VALUE - ); - - self::assertCount(2, $functions); - self::assertIsCallable($functions[0]); - self::assertIsCallable($functions[1]); + $s = $this->adapter->query('SELECT foo'); + self::assertSame($this->mockStatement, $s); } - public function testConstructorWithProfilerDelegatesToSetProfiler(): void + /** + * @throws \Exception + */ + #[TestDox('unit test: Test query() in prepare mode, with array of parameters, produces a result object')] + public function testQueryWhenPreparedWithParameterArrayProducesResult(): void { - $profilerMock = $this->createMock(Profiler\ProfilerInterface::class); - $driverMock = $this->createMock(DriverInterface::class); - $platformMock = $this->createMock(PlatformInterface::class); - - $adapter = new Adapter( - driver: $driverMock, - platform: $platformMock, - profiler: $profilerMock, - ); + $parray = ['bar' => 'foo']; + $sql = 'SELECT foo, :bar'; + $statement = $this->getMockBuilder(StatementInterface::class)->getMock(); + $result = $this->getMockBuilder(ResultInterface::class)->getMock(); + $this->mockDriver + ->expects($this->any()) + ->method('createStatement') + ->with($sql) + ->willReturn($statement); + $this->mockStatement->expects($this->any())->method('execute')->willReturn($result); - self::assertSame($profilerMock, $adapter->getProfiler()); + $r = $this->adapter->query($sql, $parray); + self::assertSame($result, $r); } - public function testQueryThrowsOnInvalidParameterType(): void + /** + * @throws \Exception + */ + #[TestDox('unit test: Test query() in prepare mode, with ParameterContainer, produces a result object')] + public function testQueryWhenPreparedWithParameterContainerProducesResult(): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Flag incorrectly set'); + $sql = 'SELECT foo'; + $parameterContainer = $this->getMockBuilder(ParameterContainer::class)->getMock(); + $result = $this->getMockBuilder(ResultInterface::class)->getMock(); + $this->mockDriver + ->expects($this->any()) + ->method('createStatement') + ->with($sql) + ->willReturn($this->mockStatement); + $this->mockStatement->expects($this->any())->method('execute')->willReturn($result); + $result->expects($this->any())->method('isQueryResult')->willReturn(true); + $result->expects($this->any())->method('getQueryResult')->willReturn(new ResultSet()); - $this->adapter->query('SELECT 1', 'invalid_mode'); + $r = $this->adapter->query($sql, $parameterContainer); + self::assertInstanceOf(ResultSet::class, $r); } public function testSetProfilerDelegatesToDriverWhenProfilerAware(): void { $profiler = $this->createMock(Profiler\ProfilerInterface::class); $driver = $this->createMockForIntersectionOfInterfaces( - [DriverInterface::class, Profiler\ProfilerAwareInterface::class] + [DriverInterface::class, Profiler\ProfilerAwareInterface::class], ); $driver->expects($this->once())->method('setProfiler')->with($profiler); $platform = $this->createMock(PlatformInterface::class); - $adapter = new Adapter(driver: $driver, platform: $platform); + $adapter = new Adapter( + driver: $driver, + platform: $platform, + ); $adapter->setProfiler($profiler); } - public function testGetHelpersQuoteIdentifierClosureCallsPlatform(): void - { - $this->mockPlatform->method('quoteIdentifier') - ->with('test') - ->willReturn('"test"'); - - $functions = $this->adapter->getHelpers(Adapter::FUNCTION_QUOTE_IDENTIFIER); - $result = $functions[0]('test'); - - self::assertSame('"test"', $result); - } - - public function testGetHelpersQuoteValueClosureCallsPlatform(): void + /** + * @throws Exception + */ + #[Override] + protected function setUp(): void { - $this->mockPlatform->method('quoteValue') - ->with('test') - ->willThrowException(VunerablePlatformQuoteException::forPlatformAndMethod('test', 'test')); - - $functions = $this->adapter->getHelpers(Adapter::FUNCTION_QUOTE_VALUE); + $this->mockDriver = $this->createMock(DriverInterface::class); + $this->mockConnection = $this->createMock(ConnectionInterface::class); + $this->mockDriver->method('checkEnvironment')->willReturn(true); + $this->mockDriver->method('getConnection')->willReturn($this->mockConnection); + $this->mockPlatform = $this->createMock(PlatformInterface::class); + $this->mockStatement = $this->createMock(StatementInterface::class); + $this->mockDriver->method('createStatement')->willReturn($this->mockStatement); - $this->expectException(VunerablePlatformQuoteException::class); - $functions[0]('test'); + $this->adapter = new Adapter($this->mockDriver, $this->mockPlatform); } } diff --git a/test/unit/Adapter/Container/AbstractAdapterInterfaceFactoryTest.php b/test/unit/Adapter/Container/AbstractAdapterInterfaceFactoryTest.php index 577fe7bc5..bc55857f3 100644 --- a/test/unit/Adapter/Container/AbstractAdapterInterfaceFactoryTest.php +++ b/test/unit/Adapter/Container/AbstractAdapterInterfaceFactoryTest.php @@ -33,44 +33,11 @@ final class AbstractAdapterInterfaceFactoryTest extends TestCase { private ContainerInterface|ServiceManager $serviceManager; - #[Override] - protected function setUp(): void + public static function providerInvalidService(): array { - /** @var PdoDriverInterface&MockObject $pdoDriverInterfaceMock */ - $pdoDriverInterfaceMock = $this->getMockBuilder(PdoDriverInterface::class)->getMock(); - /** @var PlatformInterface&MockObject $platformMock */ - $platformMock = $this->getMockBuilder(PlatformInterface::class)->getMock(); - - $config = [ - 'abstract_factories' => [AbstractAdapterInterfaceFactory::class], - 'factories' => [ - PdoStubDriver::class => static function ( - ContainerInterface $container - ) use ($pdoDriverInterfaceMock): PdoDriverInterface { - return $pdoDriverInterfaceMock; - }, - PlatformInterface::class => static function ( - ContainerInterface $container - ) use ($platformMock): PlatformInterface { - return $platformMock; - }, - ], + return [ + ['PhpDb\Adapter\Unknown'], ]; - - $this->serviceManager = new ServiceManager($config); - - $this->serviceManager->setService('config', [ - AdapterInterface::class => [ - 'adapters' => [ - 'PhpDb\Adapter\Writer' => [ - 'driver' => PdoStubDriver::class, - ], - 'PhpDb\Adapter\Reader' => [ - 'driver' => PdoStubDriver::class, - ], - ], - ], - ]); } public static function providerValidService(): array @@ -81,35 +48,6 @@ public static function providerValidService(): array ]; } - public static function providerInvalidService(): array - { - return [ - ['PhpDb\Adapter\Unknown'], - ]; - } - - /** - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ - #[DataProvider('providerValidService')] - public function testValidService(string $service): void - { - $actual = $this->serviceManager->get($service); - self::assertInstanceOf(AdapterInterface::class, $actual); - } - - /** - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ - #[DataProvider('providerInvalidService')] - public function testInvalidService(string $service): void - { - $this->expectException(ServiceNotFoundException::class); - $this->serviceManager->get($service); - } - public function testCanCreateReturnsFalseForEmptyConfig(): void { $container = new ServiceManager(); @@ -120,25 +58,6 @@ public function testCanCreateReturnsFalseForEmptyConfig(): void self::assertFalse($factory->canCreate($container, 'PhpDb\Adapter\Writer')); } - public function testInvokeThrowsWhenDriverNotConfigured(): void - { - $container = new ServiceManager(); - $container->setService('config', [ - AdapterInterface::class => [ - 'adapters' => [ - 'PhpDb\Adapter\NoDriver' => [], - ], - ], - ]); - - $factory = new AbstractAdapterInterfaceFactory(); - $factory->canCreate($container, 'PhpDb\Adapter\NoDriver'); - - $this->expectException(ContainerException::class); - $this->expectExceptionMessage('no driver configured'); - $factory($container, 'PhpDb\Adapter\NoDriver'); - } - public function testGetConfigCachesResult(): void { $container = $this->createMock(ContainerInterface::class); @@ -167,6 +86,36 @@ public function testGetConfigReturnsEmptyWhenContainerHasNoConfig(): void self::assertFalse($factory->canCreate($container, 'anything')); } + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + #[DataProvider('providerInvalidService')] + public function testInvalidService(string $service): void + { + $this->expectException(ServiceNotFoundException::class); + $this->serviceManager->get($service); + } + + public function testInvokeThrowsWhenDriverNotConfigured(): void + { + $container = new ServiceManager(); + $container->setService('config', [ + AdapterInterface::class => [ + 'adapters' => [ + 'PhpDb\Adapter\NoDriver' => [], + ], + ], + ]); + + $factory = new AbstractAdapterInterfaceFactory(); + $factory->canCreate($container, 'PhpDb\Adapter\NoDriver'); + + $this->expectException(ContainerException::class); + $this->expectExceptionMessage('no driver configured'); + $factory($container, 'PhpDb\Adapter\NoDriver'); + } + public function testInvokeUsesResultSetFromContainer(): void { $resultSet = new ResultSet(); @@ -203,4 +152,55 @@ public function testInvokeUsesResultSetFromContainer(): void self::assertSame($resultSet, $adapter->getQueryResultSetPrototype()); self::assertSame($profiler, $adapter->getProfiler()); } + + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + #[DataProvider('providerValidService')] + public function testValidService(string $service): void + { + $actual = $this->serviceManager->get($service); + self::assertInstanceOf(AdapterInterface::class, $actual); + } + + #[Override] + protected function setUp(): void + { + /** @var PdoDriverInterface&MockObject $pdoDriverInterfaceMock */ + $pdoDriverInterfaceMock = $this->getMockBuilder(PdoDriverInterface::class)->getMock(); + /** @var PlatformInterface&MockObject $platformMock */ + $platformMock = $this->getMockBuilder(PlatformInterface::class)->getMock(); + + $config = [ + 'abstract_factories' => [AbstractAdapterInterfaceFactory::class], + 'factories' => [ + PdoStubDriver::class => static function ( + ContainerInterface $container, + ) use ($pdoDriverInterfaceMock): PdoDriverInterface { + return $pdoDriverInterfaceMock; + }, + PlatformInterface::class => static function ( + ContainerInterface $container, + ) use ($platformMock): PlatformInterface { + return $platformMock; + }, + ], + ]; + + $this->serviceManager = new ServiceManager($config); + + $this->serviceManager->setService('config', [ + AdapterInterface::class => [ + 'adapters' => [ + 'PhpDb\Adapter\Writer' => [ + 'driver' => PdoStubDriver::class, + ], + 'PhpDb\Adapter\Reader' => [ + 'driver' => PdoStubDriver::class, + ], + ], + ], + ]); + } } diff --git a/test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php b/test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php index 942fdfbb5..27d727d9b 100644 --- a/test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php +++ b/test/unit/Adapter/Container/AdapterInterfaceDelegatorTest.php @@ -30,121 +30,51 @@ #[CoversMethod(AdapterInterfaceDelegator::class, '__invoke')] final class AdapterInterfaceDelegatorTest extends TestCase { - /** - * @throws Exception - */ - public function testSetAdapterShouldBeCalledForExistingAdapter(): void + public function testDelegatorWithPluginManager(): void { - $container = $this->createMock(ContainerInterface::class); - $container - ->expects(self::once()) - ->method('has') - ->with(AdapterInterface::class) - ->willReturn(true); - $container - ->expects(self::once()) - ->method('get') - ->with(AdapterInterface::class) - ->willReturn($this->createMock(Adapter::class)); - - $callback = static fn(): ConcreteAdapterAwareObject => new ConcreteAdapterAwareObject(); - - /** @var ConcreteAdapterAwareObject $result */ - $result = (new AdapterInterfaceDelegator())( - $container, - ConcreteAdapterAwareObject::class, - $callback + $databaseAdapter = new Adapter( + $this->createMock(DriverInterface::class), + $this->createMock(PlatformInterface::class), + $this->createMock(ResultSetInterface::class), ); - $this->assertInstanceOf( - AdapterInterface::class, - $result->getAdapter() - ); - } + $container = new ServiceManager([ + 'factories' => [ + AdapterInterface::class => static fn() => $databaseAdapter, + ], + ]); - /** - * @throws Exception - */ - public function testSetAdapterShouldBeCalledForOnlyConcreteAdapter(): void - { - $container = $this - ->createMock(ContainerInterface::class); - $container - ->expects(self::once()) - ->method('has') - ->with(AdapterInterface::class) - ->willReturn(true); + $pluginManagerConfig = [ + 'invokables' => [ + ConcreteAdapterAwareObject::class => ConcreteAdapterAwareObject::class, + ], + 'delegators' => [ + ConcreteAdapterAwareObject::class => [ + AdapterInterfaceDelegator::class, + ], + ], + ]; - $container - ->expects(self::once()) - ->method('get') - ->with(AdapterInterface::class) - ->willReturn($this->createMock(AdapterInterface::class)); + $pluginManager = new class($container, $pluginManagerConfig) extends AbstractPluginManager { + public function validate(mixed $instance): void {} + }; - $callback = static fn(): ConcreteAdapterAwareObject => new ConcreteAdapterAwareObject(); + $options = [ + 'table' => 'foo', + 'field' => 'bar', + ]; /** @var ConcreteAdapterAwareObject $result */ - $result = (new AdapterInterfaceDelegator())( - $container, + $result = $pluginManager->build( ConcreteAdapterAwareObject::class, - $callback + $options, ); $this->assertInstanceOf( AdapterInterface::class, - $result->getAdapter() - ); - } - - /** - * @throws Exception - */ - public function testSetAdapterShouldNotBeCalledForMissingAdapter(): void - { - $container = $this->createMock(ContainerInterface::class); - $container - ->expects(self::once()) - ->method('has') - ->with(AdapterInterface::class) - ->willReturn(false); - $container - ->expects(self::never()) - ->method('get'); - - $callback = static fn(): ConcreteAdapterAwareObject => new ConcreteAdapterAwareObject(); - - $this->expectException(ServiceNotFoundException::class); - $this->expectExceptionMessage('Service "PhpDb\Adapter\AdapterInterface" not found in container'); - - (new AdapterInterfaceDelegator())( - $container, - ConcreteAdapterAwareObject::class, - $callback - ); - } - - /** - * @throws Exception - */ - public function testSetAdapterShouldNotBeCalledForWrongClassInstance(): void - { - $container = $this->createMock(ContainerInterface::class); - $container - ->expects(self::never()) - ->method('has'); - - $callback = static fn(): stdClass => new stdClass(); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage( - 'Delegated service "stdClass" must implement PhpDb\Adapter\AdapterAwareInterface' - ); - - (new AdapterInterfaceDelegator())( - $container, - stdClass::class, - $callback + $result->getAdapter(), ); + $this->assertSame($options, $result->getOptions()); } /** @@ -157,7 +87,7 @@ public function testDelegatorWithServiceManager(): void $databaseAdapter = new Adapter( $this->createMock(DriverInterface::class), $this->createMock(PlatformInterface::class), - $this->createMock(ResultSetInterface::class) + $this->createMock(ResultSetInterface::class), ); $container = new ServiceManager([ @@ -178,7 +108,7 @@ public function testDelegatorWithServiceManager(): void $this->assertInstanceOf( AdapterInterface::class, - $result->getAdapter() + $result->getAdapter(), ); } @@ -192,7 +122,7 @@ public function testDelegatorWithServiceManagerAndCustomAdapterName(): void $databaseAdapter = new Adapter( $this->createMock(DriverInterface::class), $this->createMock(PlatformInterface::class), - $this->createMock(ResultSetInterface::class) + $this->createMock(ResultSetInterface::class), ); $container = new ServiceManager([ @@ -213,96 +143,154 @@ public function testDelegatorWithServiceManagerAndCustomAdapterName(): void $this->assertInstanceOf( AdapterInterface::class, - $result->getAdapter() + $result->getAdapter(), ); } - public function testDelegatorWithPluginManager(): void + public function testInvokeReturnsInstanceWhenAdapterIsNotAdapterInterface(): void { - $databaseAdapter = new Adapter( - $this->createMock(DriverInterface::class), - $this->createMock(PlatformInterface::class), - $this->createMock(ResultSetInterface::class) - ); + $container = $this->createMock(ContainerInterface::class); + $container->expects(self::once()) + ->method('has') + ->with(AdapterInterface::class) + ->willReturn(true); + $container->expects(self::once()) + ->method('get') + ->with(AdapterInterface::class) + ->willReturn(new stdClass()); - $container = new ServiceManager([ - 'factories' => [ - AdapterInterface::class => static fn() => $databaseAdapter, - ], - ]); + $callback = static fn(): ConcreteAdapterAwareObject => new ConcreteAdapterAwareObject(); - $pluginManagerConfig = [ - 'invokables' => [ - ConcreteAdapterAwareObject::class => ConcreteAdapterAwareObject::class, - ], - 'delegators' => [ - ConcreteAdapterAwareObject::class => [ - AdapterInterfaceDelegator::class, - ], - ], - ]; + $result = (new AdapterInterfaceDelegator())( + $container, + ConcreteAdapterAwareObject::class, + $callback, + ); - $pluginManager = new class ($container, $pluginManagerConfig) extends AbstractPluginManager { - public function validate(mixed $instance): void - { - } - }; + self::assertInstanceOf(ConcreteAdapterAwareObject::class, $result); + self::assertNull($result->getAdapter()); + } - $options = [ - 'table' => 'foo', - 'field' => 'bar', - ]; + /** + * @throws Exception + */ + public function testSetAdapterShouldBeCalledForExistingAdapter(): void + { + $container = $this->createMock(ContainerInterface::class); + $container->expects(self::once()) + ->method('has') + ->with(AdapterInterface::class) + ->willReturn(true); + $container->expects(self::once()) + ->method('get') + ->with(AdapterInterface::class) + ->willReturn($this->createMock(Adapter::class)); + + $callback = static fn(): ConcreteAdapterAwareObject => new ConcreteAdapterAwareObject(); /** @var ConcreteAdapterAwareObject $result */ - $result = $pluginManager->build( + $result = (new AdapterInterfaceDelegator())( + $container, ConcreteAdapterAwareObject::class, - $options + $callback, ); $this->assertInstanceOf( AdapterInterface::class, - $result->getAdapter() + $result->getAdapter(), ); - $this->assertSame($options, $result->getOptions()); } - public function testSetStateWithDefaultAdapterName(): void + /** + * @throws Exception + */ + public function testSetAdapterShouldBeCalledForOnlyConcreteAdapter(): void { - $delegator = AdapterInterfaceDelegator::__set_state([]); + $container = $this->createMock(ContainerInterface::class); + $container->expects(self::once()) + ->method('has') + ->with(AdapterInterface::class) + ->willReturn(true); - self::assertInstanceOf(AdapterInterfaceDelegator::class, $delegator); - } + $container->expects(self::once()) + ->method('get') + ->with(AdapterInterface::class) + ->willReturn($this->createMock(AdapterInterface::class)); - public function testSetStateWithCustomAdapterName(): void - { - $delegator = AdapterInterfaceDelegator::__set_state(['adapterName' => 'custom']); + $callback = static fn(): ConcreteAdapterAwareObject => new ConcreteAdapterAwareObject(); - self::assertInstanceOf(AdapterInterfaceDelegator::class, $delegator); + /** @var ConcreteAdapterAwareObject $result */ + $result = (new AdapterInterfaceDelegator())( + $container, + ConcreteAdapterAwareObject::class, + $callback, + ); + + $this->assertInstanceOf( + AdapterInterface::class, + $result->getAdapter(), + ); } - public function testInvokeReturnsInstanceWhenAdapterIsNotAdapterInterface(): void + /** + * @throws Exception + */ + public function testSetAdapterShouldNotBeCalledForMissingAdapter(): void { $container = $this->createMock(ContainerInterface::class); - $container - ->expects(self::once()) + $container->expects(self::once()) ->method('has') ->with(AdapterInterface::class) - ->willReturn(true); - $container - ->expects(self::once()) - ->method('get') - ->with(AdapterInterface::class) - ->willReturn(new stdClass()); + ->willReturn(false); + $container->expects(self::never()) + ->method('get'); $callback = static fn(): ConcreteAdapterAwareObject => new ConcreteAdapterAwareObject(); - $result = (new AdapterInterfaceDelegator())( + $this->expectException(ServiceNotFoundException::class); + $this->expectExceptionMessage('Service "PhpDb\Adapter\AdapterInterface" not found in container'); + + (new AdapterInterfaceDelegator())( $container, ConcreteAdapterAwareObject::class, - $callback + $callback, ); + } - self::assertInstanceOf(ConcreteAdapterAwareObject::class, $result); - self::assertNull($result->getAdapter()); + /** + * @throws Exception + */ + public function testSetAdapterShouldNotBeCalledForWrongClassInstance(): void + { + $container = $this->createMock(ContainerInterface::class); + $container->expects(self::never()) + ->method('has'); + + $callback = static fn(): stdClass => new stdClass(); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'Delegated service "stdClass" must implement PhpDb\Adapter\AdapterAwareInterface', + ); + + (new AdapterInterfaceDelegator())( + $container, + stdClass::class, + $callback, + ); + } + + public function testSetStateWithCustomAdapterName(): void + { + $delegator = AdapterInterfaceDelegator::__set_state(['adapterName' => 'custom']); + + self::assertInstanceOf(AdapterInterfaceDelegator::class, $delegator); + } + + public function testSetStateWithDefaultAdapterName(): void + { + $delegator = AdapterInterfaceDelegator::__set_state([]); + + self::assertInstanceOf(AdapterInterfaceDelegator::class, $delegator); } } diff --git a/test/unit/Adapter/Container/TestAsset/TestPluginManager.php b/test/unit/Adapter/Container/TestAsset/TestPluginManager.php index 3a7464963..e40c3d476 100644 --- a/test/unit/Adapter/Container/TestAsset/TestPluginManager.php +++ b/test/unit/Adapter/Container/TestAsset/TestPluginManager.php @@ -8,7 +8,5 @@ class TestPluginManager extends AbstractPluginManager { - public function validate(mixed $instance): void - { - } + public function validate(mixed $instance): void {} } diff --git a/test/unit/Adapter/Driver/AbstractConnectionTest.php b/test/unit/Adapter/Driver/AbstractConnectionTest.php index af7fa3880..bd0429c1e 100644 --- a/test/unit/Adapter/Driver/AbstractConnectionTest.php +++ b/test/unit/Adapter/Driver/AbstractConnectionTest.php @@ -22,6 +22,15 @@ #[Group('unit')] final class AbstractConnectionTest extends TestCase { + public function testDisconnectIsNoOpWhenNotConnected(): void + { + $connection = new TestConnection(); + + $result = $connection->disconnect(); + + self::assertSame($connection, $result); + } + public function testDisconnectNullsResourceWhenConnected(): void { $connection = new TestConnection(); @@ -34,20 +43,18 @@ public function testDisconnectNullsResourceWhenConnected(): void self::assertFalse($connection->isConnected()); } - public function testDisconnectIsNoOpWhenNotConnected(): void + public function testGetConnectionParametersReturnsEmptyByDefault(): void { $connection = new TestConnection(); - $result = $connection->disconnect(); - - self::assertSame($connection, $result); + self::assertSame([], $connection->getConnectionParameters()); } - public function testGetConnectionParametersReturnsEmptyByDefault(): void + public function testGetDriverNameReturnsNullByDefault(): void { $connection = new TestConnection(); - self::assertSame([], $connection->getConnectionParameters()); + self::assertNull($connection->getDriverName()); } public function testGetDriverNameReturnsValueWhenSet(): void @@ -57,13 +64,6 @@ public function testGetDriverNameReturnsValueWhenSet(): void self::assertSame('sqlite', $connection->getDriverName()); } - public function testGetDriverNameReturnsNullByDefault(): void - { - $connection = new TestConnection(); - - self::assertNull($connection->getDriverName()); - } - public function testGetProfilerReturnsNullByDefault(): void { $connection = new TestConnection(); @@ -71,17 +71,6 @@ public function testGetProfilerReturnsNullByDefault(): void self::assertNull($connection->getProfiler()); } - public function testSetProfilerStoresAndReturnsProfiler(): void - { - $connection = new TestConnection(); - $profiler = $this->createMock(ProfilerInterface::class); - - $result = $connection->setProfiler($profiler); - - self::assertSame($connection, $result); - self::assertSame($profiler, $connection->getProfiler()); - } - public function testGetResourceAutoConnectsWhenNotConnected(): void { $connection = new TestConnection(); @@ -94,6 +83,13 @@ public function testGetResourceAutoConnectsWhenNotConnected(): void self::assertSame('fake-resource', $resource); } + public function testInTransactionReturnsFalseByDefault(): void + { + $connection = new TestConnection(); + + self::assertFalse($connection->inTransaction()); + } + public function testSetConnectionParametersStoresAndReturnsConnection(): void { $connection = new TestConnection(); @@ -105,10 +101,14 @@ public function testSetConnectionParametersStoresAndReturnsConnection(): void self::assertSame($params, $connection->getConnectionParameters()); } - public function testInTransactionReturnsFalseByDefault(): void + public function testSetProfilerStoresAndReturnsProfiler(): void { $connection = new TestConnection(); + $profiler = $this->createMock(ProfilerInterface::class); - self::assertFalse($connection->inTransaction()); + $result = $connection->setProfiler($profiler); + + self::assertSame($connection, $result); + self::assertSame($profiler, $connection->getProfiler()); } } diff --git a/test/unit/Adapter/Driver/Feature/DriverFeatureProviderTraitTest.php b/test/unit/Adapter/Driver/Feature/DriverFeatureProviderTraitTest.php index bd94d1404..09d9862e9 100644 --- a/test/unit/Adapter/Driver/Feature/DriverFeatureProviderTraitTest.php +++ b/test/unit/Adapter/Driver/Feature/DriverFeatureProviderTraitTest.php @@ -19,17 +19,6 @@ #[CoversMethod(DriverFeatureProviderTrait::class, 'getFeature')] final class DriverFeatureProviderTraitTest extends TestCase { - public function testAddFeatureSetsDriverAndStoresFeature(): void - { - $driver = new TestFeatureDriver(); - $feature = $this->createMock(DriverFeatureInterface::class); - $feature->expects(self::once())->method('setDriver')->with($driver); - - $driver->addFeature($feature); - - self::assertSame($feature, $driver->getFeature($feature::class)); - } - public function testAddFeaturesAddsMultipleFeatures(): void { $driver = new TestFeatureDriver(); @@ -41,23 +30,17 @@ public function testAddFeaturesAddsMultipleFeatures(): void self::assertNotFalse($driver->getFeature($feature1::class)); } - public function testGetFeatureReturnsFeatureByClassName(): void + public function testAddFeatureSetsDriverAndStoresFeature(): void { $driver = new TestFeatureDriver(); $feature = $this->createMock(DriverFeatureInterface::class); + $feature->expects(self::once())->method('setDriver')->with($driver); $driver->addFeature($feature); self::assertSame($feature, $driver->getFeature($feature::class)); } - public function testGetFeatureReturnsFalseWhenNotFound(): void - { - $driver = new TestFeatureDriver(); - - self::assertFalse($driver->getFeature('NonExistent')); - } - public function testAddFeatureThrowsWhenUsedOutsideDriverInterface(): void { $nonDriver = new class implements DriverFeatureProviderInterface { @@ -71,4 +54,21 @@ public function testAddFeatureThrowsWhenUsedOutsideDriverInterface(): void $nonDriver->addFeature($feature); } + + public function testGetFeatureReturnsFalseWhenNotFound(): void + { + $driver = new TestFeatureDriver(); + + self::assertFalse($driver->getFeature('NonExistent')); + } + + public function testGetFeatureReturnsFeatureByClassName(): void + { + $driver = new TestFeatureDriver(); + $feature = $this->createMock(DriverFeatureInterface::class); + + $driver->addFeature($feature); + + self::assertSame($feature, $driver->getFeature($feature::class)); + } } diff --git a/test/unit/Adapter/Driver/Feature/TestAsset/TestDriverFeature.php b/test/unit/Adapter/Driver/Feature/TestAsset/TestDriverFeature.php index 62a9e8c65..6d67940d0 100644 --- a/test/unit/Adapter/Driver/Feature/TestAsset/TestDriverFeature.php +++ b/test/unit/Adapter/Driver/Feature/TestAsset/TestDriverFeature.php @@ -6,6 +6,4 @@ use PhpDb\Adapter\Driver\Feature\AbstractFeature; -class TestDriverFeature extends AbstractFeature -{ -} +class TestDriverFeature extends AbstractFeature {} diff --git a/test/unit/Adapter/Driver/Pdo/ConnectionTest.php b/test/unit/Adapter/Driver/Pdo/ConnectionTest.php index fef089660..1de9bcb3b 100644 --- a/test/unit/Adapter/Driver/Pdo/ConnectionTest.php +++ b/test/unit/Adapter/Driver/Pdo/ConnectionTest.php @@ -36,51 +36,27 @@ final class ConnectionTest extends TestCase { protected TestConnection $connection; - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - #[Override] - protected function setUp(): void + public function testBeginTransactionAutoConnectsWhenNotConnected(): void { - $this->connection = new TestConnection( - [ - 'dsn' => 'sqlite::memory:', - 'username' => 'bar', - 'password' => 'baz', - ] - ); - } + $connection = new TestConnection(['dsn' => 'sqlite::memory:']); - /** - * Test getResource method tries to connect to the database, it should never return null - */ - public function testResource(): void - { - $resource = $this->connection->getResource(); - self::assertNotNull($resource); - } + self::assertFalse($connection->isConnected()); - /** - * Test getConnectedDsn returns a DSN string if it has been set - */ - public function testGetDsnReturnsDsnAfterConnect(): void - { - $dsn = "sqlite::memory:"; - $this->connection->setConnectionParameters(['dsn' => $dsn]); - try { - $this->connection->connect(); - } catch (Exception) { - } - $responseString = $this->connection->getDsn(); + $connection->beginTransaction(); - self::assertEquals($dsn, $responseString); + self::assertTrue($connection->isConnected()); } - public function testConstructorWithPdoResourceSetsConnected(): void + public function testCommitAutoConnectsWhenNotConnected(): void { - $pdo = new SqliteMemoryPdo(); - $connection = new TestConnection($pdo); + $connection = new TestConnection(new SqliteMemoryPdo()); + $connection->beginTransaction(); + $connection->beginTransaction(); + $connection->disconnect(); + + self::assertFalse($connection->isConnected()); + + $connection->commit(); self::assertTrue($connection->isConnected()); } @@ -93,52 +69,79 @@ public function testConstructorWithArraySetsConnectionParameters(): void self::assertSame($params, $connection->getConnectionParameters()); } - public function testFluentSetDriver(): void + public function testConstructorWithPdoResourceSetsConnected(): void { - $driver = $this->createMock(PdoDriverInterface::class); - - $result = $this->connection->setDriver($driver); + $pdo = new SqliteMemoryPdo(); + $connection = new TestConnection($pdo); - self::assertSame($this->connection, $result); + self::assertTrue($connection->isConnected()); } - public function testSetConnectionParametersStoresParams(): void + public function testExecuteAutoConnectsWhenNotConnected(): void { - $params = ['dsn' => 'sqlite::memory:', 'username' => 'test']; + $connection = new TestConnection(['dsn' => 'sqlite::memory:']); + $driver = new TestPdo($connection); - $this->connection->setConnectionParameters($params); + self::assertFalse($connection->isConnected()); - self::assertSame($params, $this->connection->getConnectionParameters()); + $connection->execute('SELECT 1'); + + self::assertTrue($connection->isConnected()); } - public function testExecuteCallsProfilerStartAndFinish(): void + public function testExecuteCallsProfilerFinishBeforeThrowingOnError(): void { $profiler = $this->createMock(ProfilerInterface::class); $profiler->expects($this->once())->method('profilerStart')->willReturnSelf(); $profiler->expects($this->once())->method('profilerFinish')->willReturnSelf(); - $pdo = new SqliteMemoryPdo(); + $pdo = new SqliteMemoryPdo(); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); $connection = new TestConnection($pdo); $driver = new TestPdo($connection); $connection->setProfiler($profiler); - $connection->execute('SELECT 1'); + $this->expectException(InvalidQueryException::class); + @$connection->execute('INVALID SQL STATEMENT HERE %%%'); } - public function testExecuteCallsProfilerFinishBeforeThrowingOnError(): void + public function testExecuteCallsProfilerStartAndFinish(): void { $profiler = $this->createMock(ProfilerInterface::class); $profiler->expects($this->once())->method('profilerStart')->willReturnSelf(); $profiler->expects($this->once())->method('profilerFinish')->willReturnSelf(); - $pdo = new SqliteMemoryPdo(); - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + $pdo = new SqliteMemoryPdo(); $connection = new TestConnection($pdo); $driver = new TestPdo($connection); $connection->setProfiler($profiler); - $this->expectException(InvalidQueryException::class); - @$connection->execute('INVALID SQL STATEMENT HERE %%%'); + $connection->execute('SELECT 1'); + } + + public function testFluentSetDriver(): void + { + $driver = $this->createMock(PdoDriverInterface::class); + + $result = $this->connection->setDriver($driver); + + self::assertSame($this->connection, $result); + } + + /** + * Test getConnectedDsn returns a DSN string if it has been set + */ + public function testGetDsnReturnsDsnAfterConnect(): void + { + $dsn = 'sqlite::memory:'; + $this->connection->setConnectionParameters(['dsn' => $dsn]); + try { + $this->connection->connect(); + } catch (Exception) { + } + $responseString = $this->connection->getDsn(); + + self::assertEquals($dsn, $responseString); } public function testGetDsnThrowsWhenDsnIsNull(): void @@ -155,53 +158,50 @@ public function testGetDsnThrowsWhenDsnIsNull(): void $connection->getDsn(); } - public function testBeginTransactionAutoConnectsWhenNotConnected(): void + public function testPrepareAutoConnectsAndReturnsStatement(): void { $connection = new TestConnection(['dsn' => 'sqlite::memory:']); + $driver = new TestPdo($connection); self::assertFalse($connection->isConnected()); - $connection->beginTransaction(); + $statement = $connection->prepare('SELECT 1'); self::assertTrue($connection->isConnected()); + self::assertInstanceOf(Statement::class, $statement); } - public function testCommitAutoConnectsWhenNotConnected(): void + /** + * Test getResource method tries to connect to the database, it should never return null + */ + public function testResource(): void { - $connection = new TestConnection(new SqliteMemoryPdo()); - $connection->beginTransaction(); - $connection->beginTransaction(); - $connection->disconnect(); - - self::assertFalse($connection->isConnected()); - - $connection->commit(); - - self::assertTrue($connection->isConnected()); + $resource = $this->connection->getResource(); + self::assertNotNull($resource); } - public function testExecuteAutoConnectsWhenNotConnected(): void + public function testSetConnectionParametersStoresParams(): void { - $connection = new TestConnection(['dsn' => 'sqlite::memory:']); - $driver = new TestPdo($connection); - - self::assertFalse($connection->isConnected()); + $params = ['dsn' => 'sqlite::memory:', 'username' => 'test']; - $connection->execute('SELECT 1'); + $this->connection->setConnectionParameters($params); - self::assertTrue($connection->isConnected()); + self::assertSame($params, $this->connection->getConnectionParameters()); } - public function testPrepareAutoConnectsAndReturnsStatement(): void + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + #[Override] + protected function setUp(): void { - $connection = new TestConnection(['dsn' => 'sqlite::memory:']); - $driver = new TestPdo($connection); - - self::assertFalse($connection->isConnected()); - - $statement = $connection->prepare('SELECT 1'); - - self::assertTrue($connection->isConnected()); - self::assertInstanceOf(Statement::class, $statement); + $this->connection = new TestConnection( + [ + 'dsn' => 'sqlite::memory:', + 'username' => 'bar', + 'password' => 'baz', + ], + ); } } diff --git a/test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php b/test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php index c3e223189..73ea607e0 100644 --- a/test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php +++ b/test/unit/Adapter/Driver/Pdo/ConnectionTransactionsTest.php @@ -27,16 +27,6 @@ final class ConnectionTransactionsTest extends TestCase { protected ConnectionWrapper $wrapper; - /** - * {@inheritDoc} - */ - #[Override] - protected function setUp(): void - { - $this->wrapper = new ConnectionWrapper(); - parent::setUp(); - } - public function testBeginTransactionReturnsInstanceOfConnection(): void { self::assertInstanceOf(ConnectionInterface::class, $this->wrapper->beginTransaction()); @@ -160,4 +150,14 @@ public function testStandaloneCommit(): void self::assertFalse($this->wrapper->inTransaction()); self::assertSame(0, $this->wrapper->getNestedTransactionsCount()); } + + /** + * {@inheritDoc} + */ + #[Override] + protected function setUp(): void + { + $this->wrapper = new ConnectionWrapper(); + parent::setUp(); + } } diff --git a/test/unit/Adapter/Driver/Pdo/PdoTest.php b/test/unit/Adapter/Driver/Pdo/PdoTest.php index e144ab872..05bd2a202 100644 --- a/test/unit/Adapter/Driver/Pdo/PdoTest.php +++ b/test/unit/Adapter/Driver/Pdo/PdoTest.php @@ -37,64 +37,48 @@ final class PdoTest extends TestCase { protected TestPdo $pdo; - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - #[Override] - protected function setUp(): void + /** @psalm-return array */ + public static function getInvalidParamName(): array { - $this->pdo = new TestPdo([]); + return [ + ['foo%'], + ['foo-'], + ['foo$'], + ['foo0!'], + ]; } /** @psalm-return array */ public static function getParamsAndType(): array { return [ - ['foo', null, ':foo'], - ['foo_bar', null, ':foo_bar'], - ['123foo', null, ':123foo'], - [1, null, '?'], - ['1', null, '?'], - ['foo', DriverInterface::PARAMETERIZATION_NAMED, ':foo'], + ['foo', null, ':foo'], + ['foo_bar', null, ':foo_bar'], + ['123foo', null, ':123foo'], + [1, null, '?'], + ['1', null, '?'], + ['foo', DriverInterface::PARAMETERIZATION_NAMED, ':foo'], ['foo_bar', DriverInterface::PARAMETERIZATION_NAMED, ':foo_bar'], - ['123foo', DriverInterface::PARAMETERIZATION_NAMED, ':123foo'], - [1, DriverInterface::PARAMETERIZATION_NAMED, ':1'], - ['1', DriverInterface::PARAMETERIZATION_NAMED, ':1'], - [':foo', null, ':foo'], + ['123foo', DriverInterface::PARAMETERIZATION_NAMED, ':123foo'], + [1, DriverInterface::PARAMETERIZATION_NAMED, ':1'], + ['1', DriverInterface::PARAMETERIZATION_NAMED, ':1'], + [':foo', null, ':foo'], ]; } - #[DataProvider('getParamsAndType')] - public function testFormatParameterNameFormatsCorrectly(int|string $name, ?string $type, string $expected): void - { - $result = $this->pdo->formatParameterName($name, $type); - $this->assertEquals($expected, $result); - } - - /** @psalm-return array */ - public static function getInvalidParamName(): array + public function testCheckEnvironmentReturnsTrue(): void { - return [ - ['foo%'], - ['foo-'], - ['foo$'], - ['foo0!'], - ]; + self::assertTrue($this->pdo->checkEnvironment()); } - #[DataProvider('getInvalidParamName')] - public function testFormatParameterNameWithInvalidCharacters(string $name): void + public function testConstructorAddsFeaturesWhenDriverSupportsFeatures(): void { - $this->expectException(RuntimeException::class); - $this->pdo->formatParameterName($name); - } + $feature = $this->createMock(DriverFeatureInterface::class); + $connection = new TestConnection(new SqliteMemoryPdo()); - public function testGetResultPrototypeReturnsResult(): void - { - $resultPrototype = $this->pdo->getResultPrototype(); + $pdo = new TestPdoWithFeatures($connection, features: [$feature]); - self::assertInstanceOf(Result::class, $resultPrototype); + self::assertSame($feature, $pdo->getFeature($feature::class)); } public function testConstructorSetsDriverOnConnection(): void @@ -105,16 +89,26 @@ public function testConstructorSetsDriverOnConnection(): void self::assertSame($connection, $pdo->getConnection()); } - public function testCheckEnvironmentReturnsTrue(): void + public function testCreateStatementWithNullConnectsAndInitializes(): void { - self::assertTrue($this->pdo->checkEnvironment()); + $connection = new TestConnection(['dsn' => 'sqlite::memory:']); + $pdo = new TestPdo($connection); + + $statement = $pdo->createStatement(); + + self::assertInstanceOf(Statement::class, $statement); } - public function testGetConnectionReturnsConnectionInstance(): void + public function testCreateStatementWithPdoStatementResource(): void { - $connection = $this->pdo->getConnection(); + $connection = new TestConnection(new SqliteMemoryPdo()); + $pdo = new TestPdo($connection); - self::assertInstanceOf(TestConnection::class, $connection); + $pdoStmt = $this->createMock(PDOStatement::class); + $statement = $pdo->createStatement($pdoStmt); + + self::assertInstanceOf(Statement::class, $statement); + self::assertSame($pdoStmt, $statement->getResource()); } public function testCreateStatementWithSqlString(): void @@ -128,19 +122,30 @@ public function testCreateStatementWithSqlString(): void self::assertSame('SELECT 1', $statement->getSql()); } - public function testCreateStatementWithNullConnectsAndInitializes(): void + #[DataProvider('getParamsAndType')] + public function testFormatParameterNameFormatsCorrectly(int|string $name, ?string $type, string $expected): void { - $connection = new TestConnection(['dsn' => 'sqlite::memory:']); - $pdo = new TestPdo($connection); + $result = $this->pdo->formatParameterName($name, $type); + $this->assertEquals($expected, $result); + } - $statement = $pdo->createStatement(); + public function testFormatParameterNameReturnsQuestionMarkForNumericWithoutType(): void + { + self::assertSame('?', $this->pdo->formatParameterName(42)); + } - self::assertInstanceOf(Statement::class, $statement); + #[DataProvider('getInvalidParamName')] + public function testFormatParameterNameWithInvalidCharacters(string $name): void + { + $this->expectException(RuntimeException::class); + $this->pdo->formatParameterName($name); } - public function testGetPrepareTypeReturnsNamed(): void + public function testGetConnectionReturnsConnectionInstance(): void { - self::assertSame(DriverInterface::PARAMETERIZATION_NAMED, $this->pdo->getPrepareType()); + $connection = $this->pdo->getConnection(); + + self::assertInstanceOf(TestConnection::class, $connection); } public function testGetLastGeneratedValueDelegatesToConnection(): void @@ -153,18 +158,18 @@ public function testGetLastGeneratedValueDelegatesToConnection(): void self::assertSame('0', $value); } - public function testSetProfilerPropagatesProfilerToConnectionAndStatement(): void + public function testGetPrepareTypeReturnsNamed(): void { - $profiler = $this->createMock(ProfilerInterface::class); - $connection = new TestConnection(new SqliteMemoryPdo()); - $statement = new Statement(); - $pdo = new TestPdo($connection, $statement); + self::assertSame(DriverInterface::PARAMETERIZATION_NAMED, $this->pdo->getPrepareType()); + } - $pdo->setProfiler($profiler); + public function testGetProfilerReturnsSetProfiler(): void + { + $profiler = $this->createMock(ProfilerInterface::class); - self::assertSame($profiler, $pdo->getProfiler()); - self::assertSame($profiler, $connection->getProfiler()); - self::assertSame($profiler, $statement->getProfiler()); + $this->pdo->setProfiler($profiler); + + self::assertSame($profiler, $this->pdo->getProfiler()); } public function testGetProfilerThrowsWhenNotInitialized(): void @@ -176,39 +181,34 @@ public function testGetProfilerThrowsWhenNotInitialized(): void $unused = $pdo->getProfiler(); } - public function testGetProfilerReturnsSetProfiler(): void - { - $profiler = $this->createMock(ProfilerInterface::class); - - $this->pdo->setProfiler($profiler); - - self::assertSame($profiler, $this->pdo->getProfiler()); - } - - public function testConstructorAddsFeaturesWhenDriverSupportsFeatures(): void + public function testGetResultPrototypeReturnsResult(): void { - $feature = $this->createMock(DriverFeatureInterface::class); - $connection = new TestConnection(new SqliteMemoryPdo()); - - $pdo = new TestPdoWithFeatures($connection, features: [$feature]); + $resultPrototype = $this->pdo->getResultPrototype(); - self::assertSame($feature, $pdo->getFeature($feature::class)); + self::assertInstanceOf(Result::class, $resultPrototype); } - public function testCreateStatementWithPdoStatementResource(): void + public function testSetProfilerPropagatesProfilerToConnectionAndStatement(): void { + $profiler = $this->createMock(ProfilerInterface::class); $connection = new TestConnection(new SqliteMemoryPdo()); - $pdo = new TestPdo($connection); + $statement = new Statement(); + $pdo = new TestPdo($connection, $statement); - $pdoStmt = $this->createMock(PDOStatement::class); - $statement = $pdo->createStatement($pdoStmt); + $pdo->setProfiler($profiler); - self::assertInstanceOf(Statement::class, $statement); - self::assertSame($pdoStmt, $statement->getResource()); + self::assertSame($profiler, $pdo->getProfiler()); + self::assertSame($profiler, $connection->getProfiler()); + self::assertSame($profiler, $statement->getProfiler()); } - public function testFormatParameterNameReturnsQuestionMarkForNumericWithoutType(): void + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + #[Override] + protected function setUp(): void { - self::assertSame('?', $this->pdo->formatParameterName(42)); + $this->pdo = new TestPdo([]); } } diff --git a/test/unit/Adapter/Driver/Pdo/ResultTest.php b/test/unit/Adapter/Driver/Pdo/ResultTest.php index 151ff3423..1f9a66561 100644 --- a/test/unit/Adapter/Driver/Pdo/ResultTest.php +++ b/test/unit/Adapter/Driver/Pdo/ResultTest.php @@ -42,106 +42,45 @@ #[Group('unit')] final class ResultTest extends TestCase { - /** - * Tests current method returns same data on consecutive calls. - */ - public function testCurrentReturnsSameDataOnConsecutiveCalls(): void - { - $stub = $this->getMockBuilder('PDOStatement')->getMock(); - $stub->expects($this->any()) - ->method('fetch') - ->willReturnCallback(fn() => uniqid()); - - $result = new Result(); - $result->initialize($stub, null); - - self::assertEquals($result->current(), $result->current()); - } - - public function testSetFetchModeThrowsOnInvalidMode(): void - { - $result = new Result(); - - $this->expectException(InvalidArgumentException::class); - $result->setFetchMode(13); - } - - /** - * Tests whether the fetch mode was set properly and - */ - public function testFetchModeObjReturnsStdClass(): void + public function testBufferIsCallableWithNoEffect(): void { - $stub = $this->getMockBuilder('PDOStatement')->getMock(); - $stub->expects($this->any()) - ->method('fetch') - ->willReturnCallback(fn() => new stdClass()); - $result = new Result(); - $result->initialize($stub, null); - $result->setFetchMode(PDO::FETCH_OBJ); + $result->buffer(); - self::assertEquals(5, $result->getFetchMode()); - self::assertInstanceOf('stdClass', $result->current()); + self::assertFalse($result->isBuffered()); } - /** - * Tests whether the fetch mode has a broader range - */ - public function testFetchModeAcceptsNamedMode(): void + public function testCountCachesResultFromClosure(): void { - $stub = $this->getMockBuilder('PDOStatement')->getMock(); - $stub->expects($this->any()) - ->method('fetch') - ->willReturnCallback(fn() => new stdClass()); - $result = new Result(); - $result->initialize($stub, null); - $result->setFetchMode(PDO::FETCH_NAMED); - self::assertEquals(11, $result->getFetchMode()); - self::assertInstanceOf('stdClass', $result->current()); - } + $callCount = 0; + $rowCount = static function () use (&$callCount): int { + $callCount++; + return 7; + }; - public function testRewindResetsIterationToStart(): void - { - $data = [ - ['test' => 1], - ['test' => 2], - ]; - $position = 0; + $stub = $this->getMockBuilder(PDOStatement::class)->getMock(); - $stub = $this->getMockBuilder('PDOStatement')->getMock(); - assert($stub instanceof PDOStatement); // to suppress IDE type warnings - $stub->expects($this->any()) - ->method('fetch') - ->willReturnCallback(function () use ($data, &$position) { - return $data[$position++]; - }); $result = new Result(); - $result->initialize($stub, null); - - $result->rewind(); - $result->rewind(); + $result->initialize($stub, null, $rowCount); - $this->assertEquals(0, $result->key()); - $this->assertEquals(1, $position); - $this->assertEquals($data[0], $result->current()); + $result->count(); + $result->count(); - $result->next(); - $this->assertEquals(1, $result->key()); - $this->assertEquals(2, $position); - $this->assertEquals($data[1], $result->current()); + self::assertSame(1, $callCount); } - public function testCountWithNoRowCountFallsBackToStatementRowCount(): void + public function testCountCachesResultFromStatementRowCount(): void { $stub = $this->getMockBuilder(PDOStatement::class)->getMock(); - $stub->expects($this->once()) - ->method('rowCount') - ->willReturn(5); + $stub->expects($this->once())->method('rowCount')->willReturn(3); $result = new Result(); $result->initialize($stub, null); - self::assertSame(5, $result->count()); + $result->count(); + $result->count(); + + self::assertSame(3, $result->count()); } public function testCountWithClosureInvokesClosureAndReturnsValue(): void @@ -168,111 +107,130 @@ public function testCountWithIntReturnsProvidedValue(): void self::assertSame(10, $result->count()); } - public function testCountCachesResultFromClosure(): void + public function testCountWithNoRowCountFallsBackToStatementRowCount(): void { - $callCount = 0; - $rowCount = static function () use (&$callCount): int { - $callCount++; - return 7; - }; - $stub = $this->getMockBuilder(PDOStatement::class)->getMock(); + $stub->expects($this->once()) + ->method('rowCount') + ->willReturn(5); $result = new Result(); - $result->initialize($stub, null, $rowCount); - - $result->count(); - $result->count(); + $result->initialize($stub, null); - self::assertSame(1, $callCount); + self::assertSame(5, $result->count()); } - public function testCountCachesResultFromStatementRowCount(): void + /** + * Tests current method returns same data on consecutive calls. + */ + public function testCurrentReturnsSameDataOnConsecutiveCalls(): void { - $stub = $this->getMockBuilder(PDOStatement::class)->getMock(); - $stub->expects($this->once())->method('rowCount')->willReturn(3); + $stub = $this->getMockBuilder('PDOStatement')->getMock(); + $stub->expects($this->any()) + ->method('fetch') + ->willReturnCallback(static fn() => uniqid()); $result = new Result(); $result->initialize($stub, null); - $result->count(); - $result->count(); - - self::assertSame(3, $result->count()); + self::assertEquals($result->current(), $result->current()); } - public function testInitializeStoresResourceAndValues(): void + /** + * Tests whether the fetch mode has a broader range + */ + public function testFetchModeAcceptsNamedMode(): void { - $stub = $this->createMock(PDOStatement::class); + $stub = $this->getMockBuilder('PDOStatement')->getMock(); + $stub->expects($this->any()) + ->method('fetch') + ->willReturnCallback(static fn() => new stdClass()); $result = new Result(); - - $result->initialize($stub, 42, 5); - - self::assertSame(42, $result->getGeneratedValue()); - self::assertSame(5, $result->count()); + $result->initialize($stub, null); + $result->setFetchMode(PDO::FETCH_NAMED); + self::assertEquals(11, $result->getFetchMode()); + self::assertInstanceOf('stdClass', $result->current()); } - public function testIsBufferedReturnsFalse(): void + /** + * Tests whether the fetch mode was set properly and + */ + public function testFetchModeObjReturnsStdClass(): void { + $stub = $this->getMockBuilder('PDOStatement')->getMock(); + $stub->expects($this->any()) + ->method('fetch') + ->willReturnCallback(static fn() => new stdClass()); + $result = new Result(); + $result->initialize($stub, null); + $result->setFetchMode(PDO::FETCH_OBJ); - self::assertFalse($result->isBuffered()); + self::assertEquals(5, $result->getFetchMode()); + self::assertInstanceOf('stdClass', $result->current()); } - public function testGetFetchModeDefaultIsAssoc(): void + public function testGetAffectedRowsDelegatesToRowCount(): void { + $stub = $this->createMock(PDOStatement::class); + $stub->method('rowCount')->willReturn(5); + $result = new Result(); + $result->initialize($stub, null); - self::assertSame(PDO::FETCH_ASSOC, $result->getFetchMode()); + self::assertSame(5, $result->getAffectedRows()); } - public function testSetStatementModeToScrollable(): void + public function testGetFetchModeDefaultIsAssoc(): void { $result = new Result(); - $result->setStatementMode(Result::STATEMENT_MODE_SCROLLABLE); - - self::assertSame(Result::STATEMENT_MODE_SCROLLABLE, $result->getStatementMode()); + self::assertSame(PDO::FETCH_ASSOC, $result->getFetchMode()); } - public function testSetStatementModeToForward(): void + public function testGetFieldCountDelegatesToColumnCount(): void { - $result = new Result(); + $stub = $this->createMock(PDOStatement::class); + $stub->method('columnCount')->willReturn(3); - $result->setStatementMode(Result::STATEMENT_MODE_FORWARD); + $result = new Result(); + $result->initialize($stub, null); - self::assertSame(Result::STATEMENT_MODE_FORWARD, $result->getStatementMode()); + self::assertSame(3, $result->getFieldCount()); } - public function testSetStatementModeThrowsOnInvalidMode(): void + public function testGetGeneratedValueReturnsInitializedValue(): void { + $stub = $this->createMock(PDOStatement::class); $result = new Result(); + $result->initialize($stub, 42); - $this->expectException(InvalidArgumentException::class); - $result->setStatementMode('invalid'); + self::assertSame(42, $result->getGeneratedValue()); } - public function testGetResourceReturnsPdoStatement(): void + public function testGetGeneratedValueReturnsNullByDefault(): void { - $stub = $this->createMock(PDOStatement::class); $result = new Result(); - $result->initialize($stub, null); - self::assertSame($stub, $result->getResource()); + self::assertNull($result->getGeneratedValue()); } - public function testGetFieldCountDelegatesToColumnCount(): void + public function testGetQueryResultClonesGivenPrototypeRatherThanMutatingIt(): void { $stub = $this->createMock(PDOStatement::class); $stub->method('columnCount')->willReturn(3); $result = new Result(); $result->initialize($stub, null); + $prototype = new TemporaryResultSet(); - self::assertSame(3, $result->getFieldCount()); + $returned = $result->getQueryResult($prototype); + + self::assertInstanceOf(TemporaryResultSet::class, $returned); + self::assertNotSame($prototype, $returned); } - public function testIsQueryResultReturnsTrueWhenColumnsExist(): void + public function testGetQueryResultInitializesReturnedResultSetWithThisResult(): void { $stub = $this->createMock(PDOStatement::class); $stub->method('columnCount')->willReturn(3); @@ -280,18 +238,18 @@ public function testIsQueryResultReturnsTrueWhenColumnsExist(): void $result = new Result(); $result->initialize($stub, null); - self::assertTrue($result->isQueryResult()); + self::assertSame($result->getFieldCount(), $result->getQueryResult()->getFieldCount()); } - public function testIsQueryResultReturnsFalseWhenNoColumns(): void + public function testGetQueryResultReturnsDefaultResultSetPrototypeWhenNoneGiven(): void { $stub = $this->createMock(PDOStatement::class); - $stub->method('columnCount')->willReturn(0); + $stub->method('columnCount')->willReturn(3); $result = new Result(); $result->initialize($stub, null); - self::assertFalse($result->isQueryResult()); + self::assertInstanceOf(ResultSet::class, $result->getQueryResult()); } public function testGetQueryResultThrowsWhenResultIsNotAQueryResult(): void @@ -306,68 +264,99 @@ public function testGetQueryResultThrowsWhenResultIsNotAQueryResult(): void $result->getQueryResult(); } - public function testGetQueryResultReturnsDefaultResultSetPrototypeWhenNoneGiven(): void + public function testGetResourceReturnsPdoStatement(): void { - $stub = $this->createMock(PDOStatement::class); - $stub->method('columnCount')->willReturn(3); - + $stub = $this->createMock(PDOStatement::class); $result = new Result(); $result->initialize($stub, null); - self::assertInstanceOf(ResultSet::class, $result->getQueryResult()); + self::assertSame($stub, $result->getResource()); } - public function testGetQueryResultClonesGivenPrototypeRatherThanMutatingIt(): void + public function testInitializeStoresResourceAndValues(): void { - $stub = $this->createMock(PDOStatement::class); - $stub->method('columnCount')->willReturn(3); - + $stub = $this->createMock(PDOStatement::class); $result = new Result(); - $result->initialize($stub, null); - $prototype = new TemporaryResultSet(); - $returned = $result->getQueryResult($prototype); + $result->initialize($stub, 42, 5); - self::assertInstanceOf(TemporaryResultSet::class, $returned); - self::assertNotSame($prototype, $returned); + self::assertSame(42, $result->getGeneratedValue()); + self::assertSame(5, $result->count()); } - public function testGetQueryResultInitializesReturnedResultSetWithThisResult(): void + public function testIsBufferedReturnsFalse(): void + { + $result = new Result(); + + self::assertFalse($result->isBuffered()); + } + + public function testIsQueryResultReturnsFalseWhenNoColumns(): void { $stub = $this->createMock(PDOStatement::class); - $stub->method('columnCount')->willReturn(3); + $stub->method('columnCount')->willReturn(0); $result = new Result(); $result->initialize($stub, null); - self::assertSame($result->getFieldCount(), $result->getQueryResult()->getFieldCount()); + self::assertFalse($result->isQueryResult()); } - public function testGetAffectedRowsDelegatesToRowCount(): void + public function testIsQueryResultReturnsTrueWhenColumnsExist(): void { $stub = $this->createMock(PDOStatement::class); - $stub->method('rowCount')->willReturn(5); + $stub->method('columnCount')->willReturn(3); $result = new Result(); $result->initialize($stub, null); - self::assertSame(5, $result->getAffectedRows()); + self::assertTrue($result->isQueryResult()); } - public function testGetGeneratedValueReturnsInitializedValue(): void + public function testNextAdvancesPositionAndFetchesData(): void { - $stub = $this->createMock(PDOStatement::class); + $stub = $this->createMock(PDOStatement::class); + $stub->method('fetch')->willReturn(['name' => 'test']); + $result = new Result(); - $result->initialize($stub, 42); + $result->initialize($stub, null); - self::assertSame(42, $result->getGeneratedValue()); + $result->rewind(); + self::assertSame(0, $result->key()); + + $result->next(); + self::assertSame(1, $result->key()); } - public function testGetGeneratedValueReturnsNullByDefault(): void + public function testRewindResetsIterationToStart(): void { + $data = [ + ['test' => 1], + ['test' => 2], + ]; + $position = 0; + + $stub = $this->getMockBuilder('PDOStatement')->getMock(); + assert($stub instanceof PDOStatement); // to suppress IDE type warnings + $stub->expects($this->any()) + ->method('fetch') + ->willReturnCallback(static function () use ($data, &$position) { + return $data[$position++]; + }); $result = new Result(); + $result->initialize($stub, null); - self::assertNull($result->getGeneratedValue()); + $result->rewind(); + $result->rewind(); + + $this->assertEquals(0, $result->key()); + $this->assertEquals(1, $position); + $this->assertEquals($data[0], $result->current()); + + $result->next(); + $this->assertEquals(1, $result->key()); + $this->assertEquals(2, $position); + $this->assertEquals($data[1], $result->current()); } public function testRewindThrowsExceptionOnForwardOnlyAfterAdvancing(): void @@ -386,45 +375,56 @@ public function testRewindThrowsExceptionOnForwardOnlyAfterAdvancing(): void $result->rewind(); } - public function testNextAdvancesPositionAndFetchesData(): void + public function testSetFetchModeStoresValidMode(): void { - $stub = $this->createMock(PDOStatement::class); - $stub->method('fetch')->willReturn(['name' => 'test']); + $result = new Result(); + $result->setFetchMode(PDO::FETCH_NUM); + + self::assertSame(PDO::FETCH_NUM, $result->getFetchMode()); + } + public function testSetFetchModeThrowsOnInvalidFetchMode(): void + { $result = new Result(); - $result->initialize($stub, null); - $result->rewind(); - self::assertSame(0, $result->key()); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The fetch mode must be one of the PDO::FETCH_* constants.'); - $result->next(); - self::assertSame(1, $result->key()); + $result->setFetchMode(9999); } - public function testBufferIsCallableWithNoEffect(): void + public function testSetFetchModeThrowsOnInvalidMode(): void { $result = new Result(); - $result->buffer(); - self::assertFalse($result->isBuffered()); + $this->expectException(InvalidArgumentException::class); + $result->setFetchMode(13); } - public function testSetFetchModeThrowsOnInvalidFetchMode(): void + public function testSetStatementModeThrowsOnInvalidMode(): void { $result = new Result(); $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The fetch mode must be one of the PDO::FETCH_* constants.'); + $result->setStatementMode('invalid'); + } - $result->setFetchMode(9999); + public function testSetStatementModeToForward(): void + { + $result = new Result(); + + $result->setStatementMode(Result::STATEMENT_MODE_FORWARD); + + self::assertSame(Result::STATEMENT_MODE_FORWARD, $result->getStatementMode()); } - public function testSetFetchModeStoresValidMode(): void + public function testSetStatementModeToScrollable(): void { $result = new Result(); - $result->setFetchMode(PDO::FETCH_NUM); - self::assertSame(PDO::FETCH_NUM, $result->getFetchMode()); + $result->setStatementMode(Result::STATEMENT_MODE_SCROLLABLE); + + self::assertSame(Result::STATEMENT_MODE_SCROLLABLE, $result->getStatementMode()); } public function testValidReturnsFalseWhenCurrentDataIsFalse(): void diff --git a/test/unit/Adapter/Driver/Pdo/StatementIntegrationTest.php b/test/unit/Adapter/Driver/Pdo/StatementIntegrationTest.php index fd2627b01..7b789e555 100644 --- a/test/unit/Adapter/Driver/Pdo/StatementIntegrationTest.php +++ b/test/unit/Adapter/Driver/Pdo/StatementIntegrationTest.php @@ -18,6 +18,58 @@ final class StatementIntegrationTest extends TestCase protected PDOStatement|MockObject $pdoStatementMock; + public function testStatementExecuteWillConvertPhpBoolToPdoBoolWhenBinding(): void + { + $this->pdoStatementMock + ->expects($this->any()) + ->method('bindParam') + ->with( + $this->equalTo(':foo'), + $this->equalTo(false), + $this->equalTo(PDO::PARAM_BOOL), + ); + $this->statement->execute(['foo' => false]); + } + + public function testStatementExecuteWillUsePdoIntForIntWhenBinding(): void + { + $this->pdoStatementMock + ->expects($this->any()) + ->method('bindParam') + ->with( + $this->equalTo(':foo'), + $this->equalTo(123), + $this->equalTo(PDO::PARAM_INT), + ); + $this->statement->execute(['foo' => 123]); + } + + public function testStatementExecuteWillUsePdoStrByDefaultWhenBinding(): void + { + $this->pdoStatementMock + ->expects($this->any()) + ->method('bindParam') + ->with( + $this->equalTo(':foo'), + $this->equalTo('bar'), + $this->equalTo(PDO::PARAM_STR), + ); + $this->statement->execute(['foo' => 'bar']); + } + + public function testStatementExecuteWillUsePdoStrForStringIntegerWhenBinding(): void + { + $this->pdoStatementMock + ->expects($this->any()) + ->method('bindParam') + ->with( + $this->equalTo(':foo'), + $this->equalTo('123'), + $this->equalTo(PDO::PARAM_STR), + ); + $this->statement->execute(['foo' => '123']); + } + /** * Sets up the fixture, for example, opens a network connection. * This method is called before a test is executed. @@ -35,7 +87,7 @@ protected function setUp(): void $this->statement->initialize(new TestAsset\CtorlessPdo( $this->pdoStatementMock = $this->getMockBuilder('PDOStatement') ->onlyMethods(['execute', 'bindParam']) - ->getMock() + ->getMock(), )); } @@ -43,47 +95,5 @@ protected function setUp(): void * Tears down the fixture, for example, closes a network connection. * This method is called after a test is executed. */ - protected function tearDown(): void - { - } - - public function testStatementExecuteWillConvertPhpBoolToPdoBoolWhenBinding(): void - { - $this->pdoStatementMock->expects($this->any())->method('bindParam')->with( - $this->equalTo(':foo'), - $this->equalTo(false), - $this->equalTo(PDO::PARAM_BOOL) - ); - $this->statement->execute(['foo' => false]); - } - - public function testStatementExecuteWillUsePdoStrByDefaultWhenBinding(): void - { - $this->pdoStatementMock->expects($this->any())->method('bindParam')->with( - $this->equalTo(':foo'), - $this->equalTo('bar'), - $this->equalTo(PDO::PARAM_STR) - ); - $this->statement->execute(['foo' => 'bar']); - } - - public function testStatementExecuteWillUsePdoStrForStringIntegerWhenBinding(): void - { - $this->pdoStatementMock->expects($this->any())->method('bindParam')->with( - $this->equalTo(':foo'), - $this->equalTo('123'), - $this->equalTo(PDO::PARAM_STR) - ); - $this->statement->execute(['foo' => '123']); - } - - public function testStatementExecuteWillUsePdoIntForIntWhenBinding(): void - { - $this->pdoStatementMock->expects($this->any())->method('bindParam')->with( - $this->equalTo(':foo'), - $this->equalTo(123), - $this->equalTo(PDO::PARAM_INT) - ); - $this->statement->execute(['foo' => 123]); - } + protected function tearDown(): void {} } diff --git a/test/unit/Adapter/Driver/Pdo/StatementTest.php b/test/unit/Adapter/Driver/Pdo/StatementTest.php index 9534b1a9b..53a5be698 100644 --- a/test/unit/Adapter/Driver/Pdo/StatementTest.php +++ b/test/unit/Adapter/Driver/Pdo/StatementTest.php @@ -44,86 +44,6 @@ final class StatementTest extends TestCase { protected Statement $statement; - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - #[Override] - protected function setUp(): void - { - $this->statement = new Statement(); - } - - /** - * Tears down the fixture, for example, closes a network connection. - * This method is called after a test is executed. - */ - protected function tearDown(): void - { - } - - public function testFluentSetDriver(): void - { - self::assertEquals($this->statement, $this->statement->setDriver(new TestPdo([]))); - } - - public function testFluentSetParameterContainer(): void - { - self::assertSame($this->statement, $this->statement->setParameterContainer(new ParameterContainer())); - } - - public function testGetParameterContainerReturnsContainer(): void - { - $container = new ParameterContainer(); - $this->statement->setParameterContainer($container); - self::assertSame($container, $this->statement->getParameterContainer()); - } - - public function testGetResourceReturnsPdoStatement(): void - { - $pdo = new SqliteMemoryPdo(); - $stmt = $pdo->prepare('SELECT 1'); - $this->statement->setResource($stmt); - - self::assertSame($stmt, $this->statement->getResource()); - } - - public function testSetSql(): void - { - $this->statement->setSql('SELECT 1'); - self::assertEquals('SELECT 1', $this->statement->getSql()); - } - - public function testGetSql(): void - { - $this->statement->setSql('SELECT 1'); - self::assertEquals('SELECT 1', $this->statement->getSql()); - } - - public function testFluentPrepare(): void - { - $this->statement->initialize(new SqliteMemoryPdo()); - $result = $this->statement->prepare('SELECT 1'); - self::assertInstanceOf(Statement::class, $result); - self::assertSame($this->statement, $result); - } - - public function testIsPreparedReturnsTrueAfterPrepare(): void - { - self::assertFalse($this->statement->isPrepared()); - $this->statement->initialize(new SqliteMemoryPdo()); - $this->statement->prepare('SELECT 1'); - self::assertTrue($this->statement->isPrepared()); - } - - public function testExecuteReturnsResult(): void - { - $this->statement->setDriver(new TestPdo(new TestConnection($pdo = new SqliteMemoryPdo()))); - $this->statement->initialize($pdo); - $this->statement->prepare('SELECT 1'); - self::assertInstanceOf(Result::class, $this->statement->execute()); - } - /** @return array */ public static function invalidParameterNameProvider(): array { @@ -137,139 +57,112 @@ public static function invalidParameterNameProvider(): array ]; } - #[DataProvider('invalidParameterNameProvider')] - public function testExecuteThrowsOnInvalidParameterName(string $name): void + public function testBindParametersDetectsBooleanValueType(): void { - $this->statement->setDriver(new TestPdo(new TestConnection($pdo = new SqliteMemoryPdo()))); + $pdo = new SqliteMemoryPdo(); + $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); $this->statement->initialize($pdo); - $this->statement->prepare('SELECT 1'); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('contains invalid characters'); - $this->statement->execute([$name => 'value']); - } - - public function testSetProfilerStoresProfiler(): void - { - $profiler = $this->createMock(ProfilerInterface::class); + $this->statement->setSql('SELECT :val'); - $this->statement->setProfiler($profiler); + $container = new ParameterContainer(); + $container->offsetSet('val', true); + $this->statement->setParameterContainer($container); - self::assertSame($profiler, $this->statement->getProfiler()); - } + $result = $this->statement->execute(); - public function testGetProfilerReturnsNullByDefault(): void - { - self::assertNull($this->statement->getProfiler()); + self::assertInstanceOf(Result::class, $result); } - public function testInitializeSetsPdoResource(): void + public function testBindParametersDetectsNullValueType(): void { $pdo = new SqliteMemoryPdo(); - + $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); $this->statement->initialize($pdo); - $this->statement->setSql('SELECT 1'); - $this->statement->prepare(); - - self::assertTrue($this->statement->isPrepared()); - } + $this->statement->setSql('SELECT :val'); - public function testSetResourceStoresPdoStatement(): void - { - $pdoStmt = $this->createMock(PDOStatement::class); + $container = new ParameterContainer(); + $container->offsetSet('val', null); + $this->statement->setParameterContainer($container); - $this->statement->setResource($pdoStmt); + $result = $this->statement->execute(); - self::assertSame($pdoStmt, $this->statement->getResource()); + self::assertInstanceOf(Result::class, $result); } - public function testPrepareThrowsRuntimeExceptionOnPdoFailure(): void + public function testBindParametersFromContainerSkipsWhenAlreadyBound(): void { - $pdo = $this->createMock(PDO::class); - $pdo->method('prepare')->willReturn(false); - $pdo->method('errorInfo')->willReturn(['HY000', 1, 'Prepare failed']); - + $pdo = new SqliteMemoryPdo(); + $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); $this->statement->initialize($pdo); - $this->statement->setSql('INVALID SQL'); + $this->statement->setSql('SELECT :val'); + $this->statement->setParameterContainer(new ParameterContainer(['val' => 'first'])); - $this->expectException(RuntimeException::class); - $this->statement->prepare(); + $result1 = $this->statement->execute(); + self::assertInstanceOf(Result::class, $result1); } - public function testExecuteThrowsInvalidQueryExceptionOnPdoException(): void + public function testBindParametersWithErrataTypeDefaultsToString(): void { - $pdoStmt = $this->createMock(PDOStatement::class); - $pdoStmt->method('execute')->willThrowException(new PDOException('execute failed')); - $pdoStmt->method('errorInfo')->willReturn(['HY000', 1, 'execute failed']); - $pdo = new SqliteMemoryPdo(); $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); $this->statement->initialize($pdo); - $this->statement->setSql('SELECT 1'); - $this->statement->prepare(); + $this->statement->setSql('SELECT :val'); - $reflection = new ReflectionProperty($this->statement, 'resource'); - $reflection->setValue($this->statement, $pdoStmt); + $container = new ParameterContainer(); + $container->offsetSet('val', 'data', ParameterContainer::TYPE_BINARY); + $this->statement->setParameterContainer($container); - $this->expectException(InvalidQueryException::class); - $this->statement->execute(); + $result = $this->statement->execute(); + + self::assertInstanceOf(Result::class, $result); } - public function testExecuteCallsProfilerOnSuccess(): void + public function testBindParametersWithErrataTypeInteger(): void { - $profiler = $this->createMock(ProfilerInterface::class); - $profiler->expects($this->once())->method('profilerStart')->willReturnSelf(); - $profiler->expects($this->once())->method('profilerFinish')->willReturnSelf(); - $pdo = new SqliteMemoryPdo(); $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); $this->statement->initialize($pdo); - $this->statement->setSql('SELECT 1'); - $this->statement->setProfiler($profiler); + $this->statement->setSql('SELECT :val'); - $this->statement->execute(); - } + $container = new ParameterContainer(); + $container->offsetSet('val', 42, ParameterContainer::TYPE_INTEGER); + $this->statement->setParameterContainer($container); - public function testExecuteCallsProfilerFinishOnFailure(): void - { - $profiler = $this->createMock(ProfilerInterface::class); - $profiler->expects($this->once())->method('profilerStart')->willReturnSelf(); - $profiler->expects($this->once())->method('profilerFinish')->willReturnSelf(); + $result = $this->statement->execute(); - $pdoStmt = $this->createMock(PDOStatement::class); - $pdoStmt->method('execute')->willThrowException(new PDOException('fail')); - $pdoStmt->method('errorInfo')->willReturn(['HY000', 1, 'fail']); + self::assertInstanceOf(Result::class, $result); + } + public function testBindParametersWithErrataTypeLob(): void + { $pdo = new SqliteMemoryPdo(); $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); $this->statement->initialize($pdo); - $this->statement->setSql('SELECT 1'); - $this->statement->prepare(); - $this->statement->setProfiler($profiler); + $this->statement->setSql('SELECT :val'); - $reflection = new ReflectionProperty($this->statement, 'resource'); - $reflection->setValue($this->statement, $pdoStmt); + $container = new ParameterContainer(); + $container->offsetSet('val', 'data', ParameterContainer::TYPE_LOB); + $this->statement->setParameterContainer($container); - $this->expectException(InvalidQueryException::class); - $this->statement->execute(); + $result = $this->statement->execute(); + + self::assertInstanceOf(Result::class, $result); } - public function testCloneResetsState(): void + public function testBindParametersWithErrataTypeNull(): void { $pdo = new SqliteMemoryPdo(); $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); $this->statement->initialize($pdo); - $this->statement->setSql('SELECT 1'); - $this->statement->prepare(); + $this->statement->setSql('SELECT :val'); - $clone = clone $this->statement; + $container = new ParameterContainer(); + $container->offsetSet('val', null, ParameterContainer::TYPE_NULL); + $this->statement->setParameterContainer($container); - self::assertFalse($clone->isPrepared()); - self::assertNull($clone->getResource()); - self::assertNotSame( - $this->statement->getParameterContainer(), - $clone->getParameterContainer() - ); + $result = $this->statement->execute(); + + self::assertInstanceOf(Result::class, $result); } public function testBindParametersWithPositionalIntegers(): void @@ -290,6 +183,35 @@ public function testBindParametersWithPositionalIntegers(): void self::assertInstanceOf(Result::class, $result); } + public function testCloneClonesParameterContainerWhenSet(): void + { + $container = new ParameterContainer(['key' => 'value']); + $statement = new Statement($container); + + $clone = clone $statement; + + self::assertNotSame($container, $clone->getParameterContainer()); + self::assertSame('value', $clone->getParameterContainer()->offsetGet('key')); + } + + public function testCloneResetsState(): void + { + $pdo = new SqliteMemoryPdo(); + $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); + $this->statement->initialize($pdo); + $this->statement->setSql('SELECT 1'); + $this->statement->prepare(); + + $clone = clone $this->statement; + + self::assertFalse($clone->isPrepared()); + self::assertNull($clone->getResource()); + self::assertNotSame( + $this->statement->getParameterContainer(), + $clone->getParameterContainer(), + ); + } + public function testConstructorAcceptsParameterContainerAndOptions(): void { $container = new ParameterContainer(['key' => 'value']); @@ -298,45 +220,58 @@ public function testConstructorAcceptsParameterContainerAndOptions(): void self::assertSame($container, $statement->getParameterContainer()); } - public function testPrepareThrowsWhenAlreadyPrepared(): void + public function testExecuteAutoPrepares(): void { - $this->statement->initialize(new SqliteMemoryPdo()); - $this->statement->prepare('SELECT 1'); + $pdo = new SqliteMemoryPdo(); + $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); + $this->statement->initialize($pdo); + $this->statement->setSql('SELECT 1'); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This statement has been prepared already'); + self::assertFalse($this->statement->isPrepared()); - $this->statement->prepare('SELECT 2'); + $result = $this->statement->execute(); + + self::assertInstanceOf(Result::class, $result); + self::assertTrue($this->statement->isPrepared()); } - public function testExecuteWithParameterContainerSetsContainer(): void - { - $pdo = new SqliteMemoryPdo(); - $statement = new Statement(); - $statement->setDriver(new TestPdo(new TestConnection($pdo))); - $statement->initialize($pdo); - $statement->setSql('SELECT ?'); + public function testExecuteCallsProfilerFinishOnFailure(): void + { + $profiler = $this->createMock(ProfilerInterface::class); + $profiler->expects($this->once())->method('profilerStart')->willReturnSelf(); + $profiler->expects($this->once())->method('profilerFinish')->willReturnSelf(); + + $pdoStmt = $this->createMock(PDOStatement::class); + $pdoStmt->method('execute')->willThrowException(new PDOException('fail')); + $pdoStmt->method('errorInfo')->willReturn(['HY000', 1, 'fail']); - $container = new ParameterContainer(); - $container->offsetSet(null, 'value'); + $pdo = new SqliteMemoryPdo(); + $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); + $this->statement->initialize($pdo); + $this->statement->setSql('SELECT 1'); + $this->statement->prepare(); + $this->statement->setProfiler($profiler); - $result = $statement->execute($container); + $reflection = new ReflectionProperty($this->statement, 'resource'); + $reflection->setValue($this->statement, $pdoStmt); - self::assertInstanceOf(Result::class, $result); - self::assertSame($container, $statement->getParameterContainer()); + $this->expectException(InvalidQueryException::class); + $this->statement->execute(); } - public function testExecuteWithArrayParametersMergesIntoContainer(): void + public function testExecuteCallsProfilerOnSuccess(): void { - $pdo = new SqliteMemoryPdo(); - $statement = new Statement(); - $statement->setDriver(new TestPdo(new TestConnection($pdo))); - $statement->initialize($pdo); - $statement->setSql('SELECT :name'); + $profiler = $this->createMock(ProfilerInterface::class); + $profiler->expects($this->once())->method('profilerStart')->willReturnSelf(); + $profiler->expects($this->once())->method('profilerFinish')->willReturnSelf(); - $result = $statement->execute(['name' => 'test']); + $pdo = new SqliteMemoryPdo(); + $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); + $this->statement->initialize($pdo); + $this->statement->setSql('SELECT 1'); + $this->statement->setProfiler($profiler); - self::assertInstanceOf(Result::class, $result); + $this->statement->execute(); } public function testExecuteCastsNonIntErrorCodeToZero(): void @@ -366,127 +301,161 @@ public function testExecuteCastsNonIntErrorCodeToZero(): void } } - public function testBindParametersFromContainerSkipsWhenAlreadyBound(): void + public function testExecuteReturnsResult(): void { - $pdo = new SqliteMemoryPdo(); - $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); + $this->statement->setDriver(new TestPdo(new TestConnection($pdo = new SqliteMemoryPdo()))); $this->statement->initialize($pdo); - $this->statement->setSql('SELECT :val'); - $this->statement->setParameterContainer(new ParameterContainer(['val' => 'first'])); - - $result1 = $this->statement->execute(); - self::assertInstanceOf(Result::class, $result1); + $this->statement->prepare('SELECT 1'); + self::assertInstanceOf(Result::class, $this->statement->execute()); } - public function testBindParametersWithErrataTypeInteger(): void + public function testExecuteThrowsInvalidQueryExceptionOnPdoException(): void { + $pdoStmt = $this->createMock(PDOStatement::class); + $pdoStmt->method('execute')->willThrowException(new PDOException('execute failed')); + $pdoStmt->method('errorInfo')->willReturn(['HY000', 1, 'execute failed']); + $pdo = new SqliteMemoryPdo(); $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); $this->statement->initialize($pdo); - $this->statement->setSql('SELECT :val'); - - $container = new ParameterContainer(); - $container->offsetSet('val', 42, ParameterContainer::TYPE_INTEGER); - $this->statement->setParameterContainer($container); + $this->statement->setSql('SELECT 1'); + $this->statement->prepare(); - $result = $this->statement->execute(); + $reflection = new ReflectionProperty($this->statement, 'resource'); + $reflection->setValue($this->statement, $pdoStmt); - self::assertInstanceOf(Result::class, $result); + $this->expectException(InvalidQueryException::class); + $this->statement->execute(); } - public function testBindParametersWithErrataTypeNull(): void + #[DataProvider('invalidParameterNameProvider')] + public function testExecuteThrowsOnInvalidParameterName(string $name): void { - $pdo = new SqliteMemoryPdo(); - $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); + $this->statement->setDriver(new TestPdo(new TestConnection($pdo = new SqliteMemoryPdo()))); $this->statement->initialize($pdo); - $this->statement->setSql('SELECT :val'); + $this->statement->prepare('SELECT 1'); - $container = new ParameterContainer(); - $container->offsetSet('val', null, ParameterContainer::TYPE_NULL); - $this->statement->setParameterContainer($container); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('contains invalid characters'); + $this->statement->execute([$name => 'value']); + } - $result = $this->statement->execute(); + public function testExecuteWithArrayParametersMergesIntoContainer(): void + { + $pdo = new SqliteMemoryPdo(); + $statement = new Statement(); + $statement->setDriver(new TestPdo(new TestConnection($pdo))); + $statement->initialize($pdo); + $statement->setSql('SELECT :name'); + + $result = $statement->execute(['name' => 'test']); self::assertInstanceOf(Result::class, $result); } - public function testBindParametersWithErrataTypeLob(): void + public function testExecuteWithParameterContainerSetsContainer(): void { - $pdo = new SqliteMemoryPdo(); - $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); - $this->statement->initialize($pdo); - $this->statement->setSql('SELECT :val'); + $pdo = new SqliteMemoryPdo(); + $statement = new Statement(); + $statement->setDriver(new TestPdo(new TestConnection($pdo))); + $statement->initialize($pdo); + $statement->setSql('SELECT ?'); $container = new ParameterContainer(); - $container->offsetSet('val', 'data', ParameterContainer::TYPE_LOB); - $this->statement->setParameterContainer($container); + $container->offsetSet(null, 'value'); - $result = $this->statement->execute(); + $result = $statement->execute($container); self::assertInstanceOf(Result::class, $result); + self::assertSame($container, $statement->getParameterContainer()); } - public function testBindParametersWithErrataTypeDefaultsToString(): void + public function testFluentPrepare(): void { - $pdo = new SqliteMemoryPdo(); - $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); - $this->statement->initialize($pdo); - $this->statement->setSql('SELECT :val'); - - $container = new ParameterContainer(); - $container->offsetSet('val', 'data', ParameterContainer::TYPE_BINARY); - $this->statement->setParameterContainer($container); - - $result = $this->statement->execute(); + $this->statement->initialize(new SqliteMemoryPdo()); + $result = $this->statement->prepare('SELECT 1'); + self::assertInstanceOf(Statement::class, $result); + self::assertSame($this->statement, $result); + } - self::assertInstanceOf(Result::class, $result); + public function testFluentSetDriver(): void + { + self::assertEquals($this->statement, $this->statement->setDriver(new TestPdo([]))); } - public function testBindParametersDetectsNullValueType(): void + public function testFluentSetParameterContainer(): void { - $pdo = new SqliteMemoryPdo(); - $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); - $this->statement->initialize($pdo); - $this->statement->setSql('SELECT :val'); + self::assertSame($this->statement, $this->statement->setParameterContainer(new ParameterContainer())); + } + public function testGetParameterContainerReturnsContainer(): void + { $container = new ParameterContainer(); - $container->offsetSet('val', null); $this->statement->setParameterContainer($container); - - $result = $this->statement->execute(); - - self::assertInstanceOf(Result::class, $result); + self::assertSame($container, $this->statement->getParameterContainer()); } - public function testBindParametersDetectsBooleanValueType(): void + public function testGetProfilerReturnsNullByDefault(): void { - $pdo = new SqliteMemoryPdo(); - $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); - $this->statement->initialize($pdo); - $this->statement->setSql('SELECT :val'); + self::assertNull($this->statement->getProfiler()); + } - $container = new ParameterContainer(); - $container->offsetSet('val', true); - $this->statement->setParameterContainer($container); + public function testGetResourceReturnsPdoStatement(): void + { + $pdo = new SqliteMemoryPdo(); + $stmt = $pdo->prepare('SELECT 1'); + $this->statement->setResource($stmt); - $result = $this->statement->execute(); + self::assertSame($stmt, $this->statement->getResource()); + } - self::assertInstanceOf(Result::class, $result); + public function testGetSql(): void + { + $this->statement->setSql('SELECT 1'); + self::assertEquals('SELECT 1', $this->statement->getSql()); } - public function testExecuteAutoPrepares(): void + public function testInitializeSetsPdoResource(): void { $pdo = new SqliteMemoryPdo(); - $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); + $this->statement->initialize($pdo); $this->statement->setSql('SELECT 1'); + $this->statement->prepare(); + + self::assertTrue($this->statement->isPrepared()); + } + public function testIsPreparedReturnsTrueAfterPrepare(): void + { self::assertFalse($this->statement->isPrepared()); + $this->statement->initialize(new SqliteMemoryPdo()); + $this->statement->prepare('SELECT 1'); + self::assertTrue($this->statement->isPrepared()); + } - $result = $this->statement->execute(); + public function testPrepareThrowsRuntimeExceptionOnPdoFailure(): void + { + $pdo = $this->createMock(PDO::class); + $pdo->method('prepare')->willReturn(false); + $pdo->method('errorInfo')->willReturn(['HY000', 1, 'Prepare failed']); - self::assertInstanceOf(Result::class, $result); - self::assertTrue($this->statement->isPrepared()); + $this->statement->initialize($pdo); + $this->statement->setSql('INVALID SQL'); + + $this->expectException(RuntimeException::class); + $this->statement->prepare(); + } + + public function testPrepareThrowsWhenAlreadyPrepared(): void + { + $this->statement->initialize(new SqliteMemoryPdo()); + $this->statement->prepare('SELECT 1'); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('This statement has been prepared already'); + + $this->statement->prepare('SELECT 2'); } public function testSecondExecuteSkipsBindingWhenAlreadyBound(): void @@ -504,14 +473,43 @@ public function testSecondExecuteSkipsBindingWhenAlreadyBound(): void self::assertInstanceOf(Result::class, $result2); } - public function testCloneClonesParameterContainerWhenSet(): void + public function testSetProfilerStoresProfiler(): void { - $container = new ParameterContainer(['key' => 'value']); - $statement = new Statement($container); + $profiler = $this->createMock(ProfilerInterface::class); - $clone = clone $statement; + $this->statement->setProfiler($profiler); - self::assertNotSame($container, $clone->getParameterContainer()); - self::assertSame('value', $clone->getParameterContainer()->offsetGet('key')); + self::assertSame($profiler, $this->statement->getProfiler()); + } + + public function testSetResourceStoresPdoStatement(): void + { + $pdoStmt = $this->createMock(PDOStatement::class); + + $this->statement->setResource($pdoStmt); + + self::assertSame($pdoStmt, $this->statement->getResource()); + } + + public function testSetSql(): void + { + $this->statement->setSql('SELECT 1'); + self::assertEquals('SELECT 1', $this->statement->getSql()); + } + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + #[Override] + protected function setUp(): void + { + $this->statement = new Statement(); } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown(): void {} } diff --git a/test/unit/Adapter/Driver/Pdo/TestAsset/CtorlessPdo.php b/test/unit/Adapter/Driver/Pdo/TestAsset/CtorlessPdo.php index c0efd74a7..51838a7af 100644 --- a/test/unit/Adapter/Driver/Pdo/TestAsset/CtorlessPdo.php +++ b/test/unit/Adapter/Driver/Pdo/TestAsset/CtorlessPdo.php @@ -11,9 +11,9 @@ class CtorlessPdo extends PDO { - public function __construct(protected PDOStatement&MockObject $mockStatement) - { - } + public function __construct( + protected PDOStatement&MockObject $mockStatement, + ) {} /** * @param array $options diff --git a/test/unit/Adapter/Driver/Pdo/TestAsset/SqliteMemoryPdo.php b/test/unit/Adapter/Driver/Pdo/TestAsset/SqliteMemoryPdo.php index dd169bf6f..d95f77c14 100644 --- a/test/unit/Adapter/Driver/Pdo/TestAsset/SqliteMemoryPdo.php +++ b/test/unit/Adapter/Driver/Pdo/TestAsset/SqliteMemoryPdo.php @@ -32,7 +32,7 @@ public function __construct($sql = null) throw new Exception(sprintf( 'Error: %s, %s', $this->errorCode(), - implode(',', $this->errorInfo()) + implode(',', $this->errorInfo()), )); } } diff --git a/test/unit/Adapter/Driver/Pdo/TestAsset/TestConnection.php b/test/unit/Adapter/Driver/Pdo/TestAsset/TestConnection.php index 836d9ecfd..170e47e90 100644 --- a/test/unit/Adapter/Driver/Pdo/TestAsset/TestConnection.php +++ b/test/unit/Adapter/Driver/Pdo/TestAsset/TestConnection.php @@ -41,25 +41,20 @@ public function connect(): ConnectionInterface $this->resource = new PDO( $this->getDsn(), $this->connectionParameters['username'] ?? null, - $this->connectionParameters['password'] ?? null + $this->connectionParameters['password'] ?? null, ); return $this; } - private function buildDsn(): string + #[Override] + public function getCurrentSchema(): string|false { - $pdoDriver = $this->connectionParameters['pdodriver'] ?? 'sqlite'; - $database = $this->connectionParameters['database'] ?? ':memory:'; + if (! $this->isConnected()) { + $this->connect(); + } - return match ($pdoDriver) { - 'sqlite' => "sqlite:$database", - 'mysql' => sprintf( - 'mysql:host=%s;dbname=%s', - $this->connectionParameters['hostname'] ?? 'localhost', - $database - ), - default => "$pdoDriver:$database", - }; + // For SQLite and other PDO drivers, return database name or false + return $this->connectionParameters['database'] ?? false; } /** @phpstan-ignore return.unusedType */ @@ -69,14 +64,19 @@ public function getLastGeneratedValue(?string $name = null): string|int|false return $this->resource?->lastInsertId($name) ?? null; } - #[Override] - public function getCurrentSchema(): string|false + private function buildDsn(): string { - if (! $this->isConnected()) { - $this->connect(); - } + $pdoDriver = $this->connectionParameters['pdodriver'] ?? 'sqlite'; + $database = $this->connectionParameters['database'] ?? ':memory:'; - // For SQLite and other PDO drivers, return database name or false - return $this->connectionParameters['database'] ?? false; + return match ($pdoDriver) { + 'sqlite' => "sqlite:{$database}", + 'mysql' => sprintf( + 'mysql:host=%s;dbname=%s', + $this->connectionParameters['hostname'] ?? 'localhost', + $database, + ), + default => "{$pdoDriver}:{$database}", + }; } } diff --git a/test/unit/Adapter/Driver/Pdo/TestAsset/TestPdo.php b/test/unit/Adapter/Driver/Pdo/TestAsset/TestPdo.php index 6f4a16e72..30767ef2d 100644 --- a/test/unit/Adapter/Driver/Pdo/TestAsset/TestPdo.php +++ b/test/unit/Adapter/Driver/Pdo/TestAsset/TestPdo.php @@ -24,7 +24,7 @@ public function __construct( array|AbstractPdoConnection|PDO $connection, ?Statement $statement = null, ?Result $result = null, - array $features = [] + array $features = [], ) { if (! $connection instanceof AbstractPdoConnection && ! $connection instanceof PDO) { $connection = new TestConnection($connection); @@ -41,7 +41,7 @@ public function __construct( $this->statementPrototype->setDriver($this); // $features is not constructor promoted because $this->features is defined in the trait - if ($features !== []) { + if ([] !== $features) { $this->addFeatures($features); } } diff --git a/test/unit/Adapter/Driver/Pdo/TestAsset/TestPdoWithFeatures.php b/test/unit/Adapter/Driver/Pdo/TestAsset/TestPdoWithFeatures.php index 4d13ad370..926d2f231 100644 --- a/test/unit/Adapter/Driver/Pdo/TestAsset/TestPdoWithFeatures.php +++ b/test/unit/Adapter/Driver/Pdo/TestAsset/TestPdoWithFeatures.php @@ -21,7 +21,7 @@ public function __construct( array|AbstractPdoConnection|PDO $connection, ?Statement $statement = null, ?Result $result = null, - array $features = [] + array $features = [], ) { if (! $connection instanceof AbstractPdoConnection && ! $connection instanceof PDO) { $connection = new TestConnection($connection); @@ -37,7 +37,7 @@ public function __construct( $this->statementPrototype->setDriver($this); - if ($features !== []) { + if ([] !== $features) { $this->addFeatures($features); } } diff --git a/test/unit/Adapter/Driver/TestAsset/PdoMock.php b/test/unit/Adapter/Driver/TestAsset/PdoMock.php index 14579faf8..cd6045d37 100644 --- a/test/unit/Adapter/Driver/TestAsset/PdoMock.php +++ b/test/unit/Adapter/Driver/TestAsset/PdoMock.php @@ -12,9 +12,7 @@ */ final class PdoMock extends PDO { - public function __construct() - { - } + public function __construct() {} public function beginTransaction(): bool { diff --git a/test/unit/Adapter/Driver/TestAsset/TestConnection.php b/test/unit/Adapter/Driver/TestAsset/TestConnection.php index 117a76738..bfd4fbdef 100644 --- a/test/unit/Adapter/Driver/TestAsset/TestConnection.php +++ b/test/unit/Adapter/Driver/TestAsset/TestConnection.php @@ -10,9 +10,9 @@ final class TestConnection extends AbstractConnection { - public function __construct(protected ?string $driverName = null) - { - } + public function __construct( + protected ?string $driverName = null, + ) {} public function beginTransaction(): ConnectionInterface { @@ -48,7 +48,7 @@ public function getLastGeneratedValue(?string $name = null): string|int|false|nu public function isConnected(): bool { - return $this->resource !== null; + return null !== $this->resource; } public function rollback(): ConnectionInterface diff --git a/test/unit/Adapter/Driver/TestAsset/TestFeatureDriver.php b/test/unit/Adapter/Driver/TestAsset/TestFeatureDriver.php index 94e16af8e..2260076aa 100644 --- a/test/unit/Adapter/Driver/TestAsset/TestFeatureDriver.php +++ b/test/unit/Adapter/Driver/TestAsset/TestFeatureDriver.php @@ -22,8 +22,9 @@ public function checkEnvironment(): bool return true; } + /** @param resource $resource */ #[Override] - public function getConnection(): ConnectionInterface + public function createResult($resource): ResultInterface { /** @phpstan-ignore return.type */ return null; @@ -37,29 +38,28 @@ public function createStatement($sqlOrResource = null): StatementInterface return null; } - /** @param resource $resource */ #[Override] - public function createResult($resource): ResultInterface + public function formatParameterName(string $name, ?string $type = null): string { - /** @phpstan-ignore return.type */ - return null; + return '?'; } #[Override] - public function getPrepareType(): string + public function getConnection(): ConnectionInterface { - return self::PARAMETERIZATION_POSITIONAL; + /** @phpstan-ignore return.type */ + return null; } #[Override] - public function formatParameterName(string $name, ?string $type = null): string + public function getLastGeneratedValue(): string|int|false|null { - return '?'; + return null; } #[Override] - public function getLastGeneratedValue(): string|int|false|null + public function getPrepareType(): string { - return null; + return self::PARAMETERIZATION_POSITIONAL; } } diff --git a/test/unit/Adapter/ParameterContainerTest.php b/test/unit/Adapter/ParameterContainerTest.php index d80c00d84..2e43c62e9 100644 --- a/test/unit/Adapter/ParameterContainerTest.php +++ b/test/unit/Adapter/ParameterContainerTest.php @@ -42,14 +42,70 @@ final class ParameterContainerTest extends TestCase { protected ParameterContainer $parameterContainer; - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - #[Override] - protected function setUp(): void + public function testConstructorWithDataPopulatesContainer(): void { - $this->parameterContainer = new ParameterContainer(['foo' => 'bar']); + $container = new ParameterContainer(['a' => 1, 'b' => 2]); + + self::assertSame(2, $container->count()); + self::assertSame(1, $container->offsetGet('a')); + self::assertSame(2, $container->offsetGet('b')); + } + + #[TestDox('unit test: Test count() returns the proper count')] + public function testCount(): void + { + self::assertEquals(1, $this->parameterContainer->count()); + } + + #[TestDox('unit test: Test current() returns the current element when used as an iterator')] + public function testCurrent(): void + { + $value = $this->parameterContainer->current(); + self::assertEquals('bar', $value); + } + + #[TestDox('unit test: Test getErrataIterator() will return an iterator for the errata data')] + public function testGetErrataIterator(): void + { + $this->parameterContainer->offsetSetErrata('foo', ParameterContainer::TYPE_INTEGER); + $data = $this->parameterContainer->getErrataIterator(); + self::assertInstanceOf('ArrayIterator', $data); + } + + #[TestDox('unit test: Test getMaxLengthIterator() will return an iterator for the errata data')] + public function testGetMaxLengthIterator(): void + { + $this->parameterContainer->offsetSetMaxLength('foo', 100); + $data = $this->parameterContainer->getMaxLengthIterator(); + self::assertInstanceOf('ArrayIterator', $data); + } + + #[TestDox('unit test: Test getNamedArray()')] + public function testGetNamedArray(): void + { + $data = $this->parameterContainer->getNamedArray(); + self::assertEquals(['foo' => 'bar'], $data); + } + + public function testGetPositionalArrayReturnsValues(): void + { + $container = new ParameterContainer(['a' => 1, 'b' => 2, 'c' => 3]); + + self::assertSame([1, 2, 3], $container->getPositionalArray()); + } + + #[TestDox("unit test: Test key() returns the name of the current item's name")] + public function testKey(): void + { + self::assertEquals('foo', $this->parameterContainer->key()); + } + + #[TestDox('unit test: Test next() increases the pointer when used as an iterator')] + public function testNext(): void + { + $this->parameterContainer['bar'] = 'baz'; + $this->parameterContainer->next(); + self::assertEquals('baz', $this->parameterContainer->current()); } #[TestDox('unit test: Test offsetExists() returns proper values via method call and isset()')] @@ -68,9 +124,83 @@ public function testOffsetGet(): void self::assertEquals('bar', $this->parameterContainer['foo']); self::assertNull($this->parameterContainer->offsetGet('bar')); + // @todo determine what should come back here } + #[TestDox('unit test: Test offsetGetErrata() return persisted errata data, if it exists')] + public function testOffsetGetErrata(): void + { + $this->parameterContainer->offsetSetErrata('foo', ParameterContainer::TYPE_INTEGER); + self::assertEquals(ParameterContainer::TYPE_INTEGER, $this->parameterContainer->offsetGetErrata('foo')); + } + + public function testOffsetGetErrataByPositionalIndex(): void + { + $container = new ParameterContainer(['foo' => 'bar']); + $container->offsetSetErrata('foo', ParameterContainer::TYPE_INTEGER); + + self::assertSame(ParameterContainer::TYPE_INTEGER, $container->offsetGetErrata(0)); + } + + public function testOffsetGetErrataThrowsWhenNameDoesNotExist(): void + { + $container = new ParameterContainer(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Data does not exist for this name/position'); + + $container->offsetGetErrata('nonexistent'); + } + + public function testOffsetGetMaxLengthByPositionalIndex(): void + { + $container = new ParameterContainer(['foo' => 'bar']); + $container->offsetSetMaxLength('foo', 50); + + self::assertSame(50, $container->offsetGetMaxLength(0)); + } + + public function testOffsetGetMaxLengthThrowsWhenNameDoesNotExist(): void + { + $container = new ParameterContainer(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Data does not exist for this name/position'); + + $container->offsetGetMaxLength('nonexistent'); + } + + #[TestDox('unit test: Test offsetHasErrata() will check if errata exists for a particular key')] + public function testOffsetHasErrata(): void + { + $this->parameterContainer->offsetSetErrata('foo', ParameterContainer::TYPE_INTEGER); + self::assertTrue($this->parameterContainer->offsetHasErrata('foo')); + } + + public function testOffsetHasErrataByPositionalIndex(): void + { + $container = new ParameterContainer(['foo' => 'bar']); + $container->offsetSetErrata('foo', ParameterContainer::TYPE_STRING); + + self::assertTrue($container->offsetHasErrata(0)); + } + + #[TestDox('unit test: Test offsetHasMaxLength() will check if errata exists for a particular key')] + public function testOffsetHasMaxLength(): void + { + $this->parameterContainer->offsetSetMaxLength('foo', 100); + self::assertTrue($this->parameterContainer->offsetHasMaxLength('foo')); + } + + public function testOffsetHasMaxLengthByPositionalIndex(): void + { + $container = new ParameterContainer(['foo' => 'bar']); + $container->offsetSetMaxLength('foo', 50); + + self::assertTrue($container->offsetHasMaxLength(0)); + } + #[TestDox('unit test: Test offsetSet() works via method call and array access')] public function testOffsetSet(): void { @@ -80,7 +210,7 @@ public function testOffsetSet(): void $this->parameterContainer->offsetSet('1', 'book', ParameterContainer::TYPE_STRING, 4); self::assertEquals( ['foo' => 'bar', 'boo' => 'baz', '1' => 'book'], - $this->parameterContainer->getNamedArray() + $this->parameterContainer->getNamedArray(), ); self::assertEquals('string', $this->parameterContainer->offsetGetErrata('1')); @@ -91,11 +221,11 @@ public function testOffsetSet(): void $this->parameterContainer[1] = 'One'; self::assertEquals( ['foo' => 'Zero', 'boo' => 'One', '1' => 'book'], - $this->parameterContainer->getNamedArray() + $this->parameterContainer->getNamedArray(), ); self::assertEquals( [0 => 'Zero', 1 => 'One', 2 => 'book'], - $this->parameterContainer->getPositionalArray() + $this->parameterContainer->getPositionalArray(), ); // test no-index applies @@ -111,44 +241,14 @@ public function testOffsetSet(): void '4' => 'Second To Last', '5' => 'Last', ], - $this->parameterContainer->getNamedArray() + $this->parameterContainer->getNamedArray(), ); self::assertEquals( [0 => 'Zero', 1 => 'One', 2 => 'book', 3 => 'A buffer Element', 4 => 'Second To Last', 5 => 'Last'], - $this->parameterContainer->getPositionalArray() + $this->parameterContainer->getPositionalArray(), ); } - #[TestDox('unit test: Test offsetUnset() works via method call and array access')] - public function testOffsetUnset(): void - { - $this->parameterContainer->offsetSet('boo', 'baz'); - self::assertTrue($this->parameterContainer->offsetExists('boo')); - - $this->parameterContainer->offsetUnset('boo'); - self::assertFalse($this->parameterContainer->offsetExists('boo')); - } - - #[TestDox('unit test: Test setFromArray() will populate the container')] - public function testSetFromArray(): void - { - $this->parameterContainer->setFromArray(['bar' => 'baz']); - self::assertEquals('baz', $this->parameterContainer['bar']); - } - - /** - * Handle statement parameters - https://github.com/laminas/laminas-db/issues/47 - * - * @see Insert::procesInsert as example - */ - public function testSetFromArrayNamed(): void - { - $this->parameterContainer->offsetSet('c_0', ':myparam'); - $this->parameterContainer->setFromArray([':myparam' => 'baz']); - self::assertEquals('baz', $this->parameterContainer['c_0']); - self::assertEquals('baz', $this->parameterContainer[':myparam']); - } - #[TestDox(' unit test: Test offsetSetMaxLength() will persist errata data unit test: Test offsetGetMaxLength() return persisted errata data, if it exists @@ -159,29 +259,6 @@ public function testOffsetSetAndGetMaxLength(): void self::assertEquals(100, $this->parameterContainer->offsetGetMaxLength('foo')); } - #[TestDox('unit test: Test offsetHasMaxLength() will check if errata exists for a particular key')] - public function testOffsetHasMaxLength(): void - { - $this->parameterContainer->offsetSetMaxLength('foo', 100); - self::assertTrue($this->parameterContainer->offsetHasMaxLength('foo')); - } - - #[TestDox('unit test: Test offsetUnsetMaxLength() will unset data for a particular key')] - public function testOffsetUnsetMaxLength(): void - { - $this->parameterContainer->offsetSetMaxLength('foo', 100); - $this->parameterContainer->offsetUnsetMaxLength('foo'); - self::assertNull($this->parameterContainer->offsetGetMaxLength('foo')); - } - - #[TestDox('unit test: Test getMaxLengthIterator() will return an iterator for the errata data')] - public function testGetMaxLengthIterator(): void - { - $this->parameterContainer->offsetSetMaxLength('foo', 100); - $data = $this->parameterContainer->getMaxLengthIterator(); - self::assertInstanceOf('ArrayIterator', $data); - } - #[TestDox('unit test: Test offsetSetErrata() will persist errata data')] public function testOffsetSetErrata(): void { @@ -189,103 +266,38 @@ public function testOffsetSetErrata(): void self::assertEquals(ParameterContainer::TYPE_INTEGER, $this->parameterContainer->offsetGetErrata('foo')); } - #[TestDox('unit test: Test offsetGetErrata() return persisted errata data, if it exists')] - public function testOffsetGetErrata(): void - { - $this->parameterContainer->offsetSetErrata('foo', ParameterContainer::TYPE_INTEGER); - self::assertEquals(ParameterContainer::TYPE_INTEGER, $this->parameterContainer->offsetGetErrata('foo')); - } - - #[TestDox('unit test: Test offsetHasErrata() will check if errata exists for a particular key')] - public function testOffsetHasErrata(): void - { - $this->parameterContainer->offsetSetErrata('foo', ParameterContainer::TYPE_INTEGER); - self::assertTrue($this->parameterContainer->offsetHasErrata('foo')); - } - - #[TestDox('unit test: Test offsetUnsetErrata() will unset data for a particular key')] - public function testOffsetUnsetErrata(): void - { - $this->parameterContainer->offsetSetErrata('foo', ParameterContainer::TYPE_INTEGER); - $this->parameterContainer->offsetUnsetErrata('foo'); - self::assertNull($this->parameterContainer->offsetGetErrata('foo')); - } - - #[TestDox('unit test: Test getErrataIterator() will return an iterator for the errata data')] - public function testGetErrataIterator(): void - { - $this->parameterContainer->offsetSetErrata('foo', ParameterContainer::TYPE_INTEGER); - $data = $this->parameterContainer->getErrataIterator(); - self::assertInstanceOf('ArrayIterator', $data); - } - - #[TestDox('unit test: Test getNamedArray()')] - public function testGetNamedArray(): void - { - $data = $this->parameterContainer->getNamedArray(); - self::assertEquals(['foo' => 'bar'], $data); - } - - #[TestDox('unit test: Test count() returns the proper count')] - public function testCount(): void + public function testOffsetSetErrataByPositionalIndex(): void { - self::assertEquals(1, $this->parameterContainer->count()); - } + $container = new ParameterContainer(['foo' => 'bar']); + $container->offsetSetErrata(0, ParameterContainer::TYPE_STRING); - #[TestDox('unit test: Test current() returns the current element when used as an iterator')] - public function testCurrent(): void - { - $value = $this->parameterContainer->current(); - self::assertEquals('bar', $value); + self::assertSame(ParameterContainer::TYPE_STRING, $container->offsetGetErrata('foo')); } - #[TestDox('unit test: Test next() increases the pointer when used as an iterator')] - public function testNext(): void + public function testOffsetSetMaxLengthByPositionalIndex(): void { - $this->parameterContainer['bar'] = 'baz'; - $this->parameterContainer->next(); - self::assertEquals('baz', $this->parameterContainer->current()); - } + $container = new ParameterContainer(['foo' => 'bar']); + $container->offsetSetMaxLength(0, 50); - #[TestDox("unit test: Test key() returns the name of the current item's name")] - public function testKey(): void - { - self::assertEquals('foo', $this->parameterContainer->key()); + self::assertSame(50, $container->offsetGetMaxLength('foo')); } - #[TestDox('unit test: Test valid() returns whether the iterators current position is valid')] - public function testValid(): void + public function testOffsetSetReferenceCreatesReference(): void { - self::assertTrue($this->parameterContainer->valid()); - $this->parameterContainer->next(); - self::assertFalse($this->parameterContainer->valid()); - } + $container = new ParameterContainer(['source' => 'original']); + $container->offsetSetReference('alias', 'source'); - #[TestDox('unit test: Test rewind() resets the iterators pointer')] - public function testRewind(): void - { - $this->parameterContainer->offsetSet('bar', 'baz'); - $this->parameterContainer->next(); - self::assertEquals('bar', $this->parameterContainer->key()); - $this->parameterContainer->rewind(); - self::assertEquals('foo', $this->parameterContainer->key()); + self::assertSame('original', $container->offsetGet('alias')); } - public function testConstructorWithDataPopulatesContainer(): void + public function testOffsetSetThrowsOnInvalidKeyType(): void { - $container = new ParameterContainer(['a' => 1, 'b' => 2]); - - self::assertSame(2, $container->count()); - self::assertSame(1, $container->offsetGet('a')); - self::assertSame(2, $container->offsetGet('b')); - } + $container = new ParameterContainer(); - public function testOffsetSetReferenceCreatesReference(): void - { - $container = new ParameterContainer(['source' => 'original']); - $container->offsetSetReference('alias', 'source'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Keys must be string, integer or null'); - self::assertSame('original', $container->offsetGet('alias')); + $container->offsetSet(1.5, 'value'); } public function testOffsetSetWithIntNotInPositionsCastsToString(): void @@ -305,14 +317,14 @@ public function testOffsetSetWithNameMappingMatchForNonColonName(): void self::assertSame('updated', $container->offsetGet('c_0')); } - public function testOffsetSetThrowsOnInvalidKeyType(): void + #[TestDox('unit test: Test offsetUnset() works via method call and array access')] + public function testOffsetUnset(): void { - $container = new ParameterContainer(); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Keys must be string, integer or null'); + $this->parameterContainer->offsetSet('boo', 'baz'); + self::assertTrue($this->parameterContainer->offsetExists('boo')); - $container->offsetSet(1.5, 'value'); + $this->parameterContainer->offsetUnset('boo'); + self::assertFalse($this->parameterContainer->offsetExists('boo')); } public function testOffsetUnsetByPositionalIndex(): void @@ -324,38 +336,39 @@ public function testOffsetUnsetByPositionalIndex(): void self::assertTrue($container->offsetExists('b')); } - public function testOffsetSetMaxLengthByPositionalIndex(): void + #[TestDox('unit test: Test offsetUnsetErrata() will unset data for a particular key')] + public function testOffsetUnsetErrata(): void { - $container = new ParameterContainer(['foo' => 'bar']); - $container->offsetSetMaxLength(0, 50); - - self::assertSame(50, $container->offsetGetMaxLength('foo')); + $this->parameterContainer->offsetSetErrata('foo', ParameterContainer::TYPE_INTEGER); + $this->parameterContainer->offsetUnsetErrata('foo'); + self::assertNull($this->parameterContainer->offsetGetErrata('foo')); } - public function testOffsetGetMaxLengthByPositionalIndex(): void + public function testOffsetUnsetErrataByPositionalIndex(): void { $container = new ParameterContainer(['foo' => 'bar']); - $container->offsetSetMaxLength('foo', 50); + $container->offsetSetErrata('foo', ParameterContainer::TYPE_STRING); + $container->offsetUnsetErrata(0); - self::assertSame(50, $container->offsetGetMaxLength(0)); + self::assertNull($container->offsetGetErrata('foo')); } - public function testOffsetGetMaxLengthThrowsWhenNameDoesNotExist(): void + public function testOffsetUnsetErrataThrowsWhenNameDoesNotExist(): void { - $container = new ParameterContainer(); + $container = new ParameterContainer(['foo' => 'bar']); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Data does not exist for this name/position'); - $container->offsetGetMaxLength('nonexistent'); + $container->offsetUnsetErrata('foo'); } - public function testOffsetHasMaxLengthByPositionalIndex(): void + #[TestDox('unit test: Test offsetUnsetMaxLength() will unset data for a particular key')] + public function testOffsetUnsetMaxLength(): void { - $container = new ParameterContainer(['foo' => 'bar']); - $container->offsetSetMaxLength('foo', 50); - - self::assertTrue($container->offsetHasMaxLength(0)); + $this->parameterContainer->offsetSetMaxLength('foo', 100); + $this->parameterContainer->offsetUnsetMaxLength('foo'); + self::assertNull($this->parameterContainer->offsetGetMaxLength('foo')); } public function testOffsetUnsetMaxLengthByPositionalIndex(): void @@ -377,63 +390,51 @@ public function testOffsetUnsetMaxLengthThrowsWhenNameDoesNotExist(): void $container->offsetUnsetMaxLength('foo'); } - public function testOffsetSetErrataByPositionalIndex(): void - { - $container = new ParameterContainer(['foo' => 'bar']); - $container->offsetSetErrata(0, ParameterContainer::TYPE_STRING); - - self::assertSame(ParameterContainer::TYPE_STRING, $container->offsetGetErrata('foo')); - } - - public function testOffsetGetErrataByPositionalIndex(): void - { - $container = new ParameterContainer(['foo' => 'bar']); - $container->offsetSetErrata('foo', ParameterContainer::TYPE_INTEGER); - - self::assertSame(ParameterContainer::TYPE_INTEGER, $container->offsetGetErrata(0)); - } - - public function testOffsetGetErrataThrowsWhenNameDoesNotExist(): void + #[TestDox('unit test: Test rewind() resets the iterators pointer')] + public function testRewind(): void { - $container = new ParameterContainer(); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Data does not exist for this name/position'); - - $container->offsetGetErrata('nonexistent'); + $this->parameterContainer->offsetSet('bar', 'baz'); + $this->parameterContainer->next(); + self::assertEquals('bar', $this->parameterContainer->key()); + $this->parameterContainer->rewind(); + self::assertEquals('foo', $this->parameterContainer->key()); } - public function testOffsetHasErrataByPositionalIndex(): void + #[TestDox('unit test: Test setFromArray() will populate the container')] + public function testSetFromArray(): void { - $container = new ParameterContainer(['foo' => 'bar']); - $container->offsetSetErrata('foo', ParameterContainer::TYPE_STRING); - - self::assertTrue($container->offsetHasErrata(0)); + $this->parameterContainer->setFromArray(['bar' => 'baz']); + self::assertEquals('baz', $this->parameterContainer['bar']); } - public function testOffsetUnsetErrataByPositionalIndex(): void + /** + * Handle statement parameters - https://github.com/laminas/laminas-db/issues/47 + * + * @see Insert::procesInsert as example + */ + public function testSetFromArrayNamed(): void { - $container = new ParameterContainer(['foo' => 'bar']); - $container->offsetSetErrata('foo', ParameterContainer::TYPE_STRING); - $container->offsetUnsetErrata(0); - - self::assertNull($container->offsetGetErrata('foo')); + $this->parameterContainer->offsetSet('c_0', ':myparam'); + $this->parameterContainer->setFromArray([':myparam' => 'baz']); + self::assertEquals('baz', $this->parameterContainer['c_0']); + self::assertEquals('baz', $this->parameterContainer[':myparam']); } - public function testOffsetUnsetErrataThrowsWhenNameDoesNotExist(): void + #[TestDox('unit test: Test valid() returns whether the iterators current position is valid')] + public function testValid(): void { - $container = new ParameterContainer(['foo' => 'bar']); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Data does not exist for this name/position'); - - $container->offsetUnsetErrata('foo'); + self::assertTrue($this->parameterContainer->valid()); + $this->parameterContainer->next(); + self::assertFalse($this->parameterContainer->valid()); } - public function testGetPositionalArrayReturnsValues(): void + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + #[Override] + protected function setUp(): void { - $container = new ParameterContainer(['a' => 1, 'b' => 2, 'c' => 3]); - - self::assertSame([1, 2, 3], $container->getPositionalArray()); + $this->parameterContainer = new ParameterContainer(['foo' => 'bar']); } } diff --git a/test/unit/Adapter/Platform/Sql92Test.php b/test/unit/Adapter/Platform/Sql92Test.php index aaa4a7e6b..b19279bda 100644 --- a/test/unit/Adapter/Platform/Sql92Test.php +++ b/test/unit/Adapter/Platform/Sql92Test.php @@ -39,89 +39,51 @@ final class Sql92Test extends TestCase { protected Sql92 $platform; - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - #[Override] - protected function setUp(): void - { - $this->platform = new Sql92(); - } - - public function testGetName(): void + public function testAbstractPlatformQuoteValueEscapesWithDriver(): void { - self::assertEquals('SQL92', $this->platform->getName()); - } + $platform = new TestPlatform($this->createStub(DriverInterface::class)); - public function testGetQuoteIdentifierSymbol(): void - { - self::assertEquals('"', $this->platform->getQuoteIdentifierSymbol()); + self::assertSame("'test\\'value'", $platform->quoteValue("test'value")); } - public function testQuoteIdentifier(): void + public function testAbstractPlatformQuoteValueThrowsWithoutDriver(): void { - self::assertEquals('"identifier"', $this->platform->quoteIdentifier('identifier')); - } + $platform = new TestPlatform(); - public function testQuoteIdentifierChain(): void - { - self::assertEquals('"identifier"', $this->platform->quoteIdentifierChain('identifier')); - self::assertEquals('"identifier"', $this->platform->quoteIdentifierChain(['identifier'])); - self::assertEquals('"schema"."identifier"', $this->platform->quoteIdentifierChain(['schema', 'identifier'])); + $this->expectException(VunerablePlatformQuoteException::class); + $platform->quoteValue('value'); } - public function testGetQuoteValueSymbol(): void + public function testGetIdentifierSeparator(): void { - self::assertEquals("'", $this->platform->getQuoteValueSymbol()); + self::assertEquals('.', $this->platform->getIdentifierSeparator()); } - public function testQuoteValueRaisesNoticeWithoutPlatformSupport(): void + public function testGetName(): void { - $this->expectException(VunerablePlatformQuoteException::class); - $this->platform->quoteValue('value'); + self::assertEquals('SQL92', $this->platform->getName()); } - public function testQuoteValueThrowsWithoutDriver(): void + public function testGetQuoteIdentifierSymbol(): void { - $this->expectException(VunerablePlatformQuoteException::class); - self::assertEquals("'value'", @$this->platform->quoteValue('value')); - self::assertEquals("'Foo O\\'Bar'", @$this->platform->quoteValue("Foo O'Bar")); - self::assertEquals( - '\'\\\'; DELETE FROM some_table; -- \'', - @$this->platform->quoteValue("'; DELETE FROM some_table; -- ") - ); - self::assertEquals( - "'\\\\\\'; DELETE FROM some_table; -- '", - @$this->platform->quoteValue('\\\'; DELETE FROM some_table; -- ') - ); + self::assertEquals('"', $this->platform->getQuoteIdentifierSymbol()); } - public function testQuoteTrustedValueEscapesSpecialCharacters(): void + public function testGetQuoteValueSymbol(): void { - self::assertEquals("'value'", $this->platform->quoteTrustedValue('value')); - self::assertEquals("'Foo O\\'Bar'", $this->platform->quoteTrustedValue("Foo O'Bar")); - self::assertEquals( - '\'\\\'; DELETE FROM some_table; -- \'', - $this->platform->quoteTrustedValue("'; DELETE FROM some_table; -- ") - ); - - // '\\\'; DELETE FROM some_table; -- ' <- actual below - self::assertEquals( - "'\\\\\\'; DELETE FROM some_table; -- '", - $this->platform->quoteTrustedValue('\\\'; DELETE FROM some_table; -- ') - ); + self::assertEquals("'", $this->platform->getQuoteValueSymbol()); } - public function testQuoteValueListThrowsWithoutDriver(): void + public function testQuoteIdentifier(): void { - $this->expectException(VunerablePlatformQuoteException::class); - self::assertEquals("'Foo O\\'Bar'", $this->platform->quoteValueList("Foo O'Bar")); + self::assertEquals('"identifier"', $this->platform->quoteIdentifier('identifier')); } - public function testGetIdentifierSeparator(): void + public function testQuoteIdentifierChain(): void { - self::assertEquals('.', $this->platform->getIdentifierSeparator()); + self::assertEquals('"identifier"', $this->platform->quoteIdentifierChain('identifier')); + self::assertEquals('"identifier"', $this->platform->quoteIdentifierChain(['identifier'])); + self::assertEquals('"schema"."identifier"', $this->platform->quoteIdentifierChain(['schema', 'identifier'])); } public function testQuoteIdentifierInFragment(): void @@ -132,7 +94,7 @@ public function testQuoteIdentifierInFragment(): void // single char words self::assertEquals( '("foo"."bar" = "boo"."baz")', - $this->platform->quoteIdentifierInFragment('(foo.bar = boo.baz)', ['(', ')', '=']) + $this->platform->quoteIdentifierInFragment('(foo.bar = boo.baz)', ['(', ')', '=']), ); // case insensitive safe words @@ -140,8 +102,8 @@ public function testQuoteIdentifierInFragment(): void '("foo"."bar" = "boo"."baz") AND ("foo"."baz" = "boo"."baz")', $this->platform->quoteIdentifierInFragment( '(foo.bar = boo.baz) AND (foo.baz = boo.baz)', - ['(', ')', '=', 'and'] - ) + ['(', ')', '=', 'and'], + ), ); // case insensitive safe words in field @@ -149,11 +111,18 @@ public function testQuoteIdentifierInFragment(): void '("foo"."bar" = "boo".baz) AND ("foo".baz = "boo".baz)', $this->platform->quoteIdentifierInFragment( '(foo.bar = boo.baz) AND (foo.baz = boo.baz)', - ['(', ')', '=', 'and', 'bAz'] - ) + ['(', ')', '=', 'and', 'bAz'], + ), ); } + public function testQuoteIdentifierInFragmentReturnsUnquotedWhenQuotingDisabled(): void + { + $platform = new TestSql92Platform(quoteIdentifiers: false); + + self::assertSame('foo.bar', $platform->quoteIdentifierInFragment('foo.bar')); + } + public function testQuoteIdentifierReturnsUnquotedWhenQuotingDisabled(): void { $platform = new TestSql92Platform(quoteIdentifiers: false); @@ -161,11 +130,20 @@ public function testQuoteIdentifierReturnsUnquotedWhenQuotingDisabled(): void self::assertSame('test', $platform->quoteIdentifier('test')); } - public function testQuoteIdentifierInFragmentReturnsUnquotedWhenQuotingDisabled(): void + public function testQuoteTrustedValueEscapesSpecialCharacters(): void { - $platform = new TestSql92Platform(quoteIdentifiers: false); + self::assertEquals("'value'", $this->platform->quoteTrustedValue('value')); + self::assertEquals("'Foo O\\'Bar'", $this->platform->quoteTrustedValue("Foo O'Bar")); + self::assertEquals( + '\'\\\'; DELETE FROM some_table; -- \'', + $this->platform->quoteTrustedValue("'; DELETE FROM some_table; -- "), + ); - self::assertSame('foo.bar', $platform->quoteIdentifierInFragment('foo.bar')); + // '\\\'; DELETE FROM some_table; -- ' <- actual below + self::assertEquals( + "'\\\\\\'; DELETE FROM some_table; -- '", + $this->platform->quoteTrustedValue('\\\'; DELETE FROM some_table; -- '), + ); } public function testQuoteValueEscapesSpecialCharacters(): void @@ -179,18 +157,40 @@ public function testQuoteValueEscapesSpecialCharacters(): void self::assertStringEndsWith("'", $quoted); } - public function testAbstractPlatformQuoteValueThrowsWithoutDriver(): void + public function testQuoteValueListThrowsWithoutDriver(): void { - $platform = new TestPlatform(); + $this->expectException(VunerablePlatformQuoteException::class); + self::assertEquals("'Foo O\\'Bar'", $this->platform->quoteValueList("Foo O'Bar")); + } + public function testQuoteValueRaisesNoticeWithoutPlatformSupport(): void + { $this->expectException(VunerablePlatformQuoteException::class); - $platform->quoteValue('value'); + $this->platform->quoteValue('value'); } - public function testAbstractPlatformQuoteValueEscapesWithDriver(): void + public function testQuoteValueThrowsWithoutDriver(): void { - $platform = new TestPlatform($this->createStub(DriverInterface::class)); + $this->expectException(VunerablePlatformQuoteException::class); + self::assertEquals("'value'", @$this->platform->quoteValue('value')); + self::assertEquals("'Foo O\\'Bar'", @$this->platform->quoteValue("Foo O'Bar")); + self::assertEquals( + '\'\\\'; DELETE FROM some_table; -- \'', + @$this->platform->quoteValue("'; DELETE FROM some_table; -- "), + ); + self::assertEquals( + "'\\\\\\'; DELETE FROM some_table; -- '", + @$this->platform->quoteValue('\\\'; DELETE FROM some_table; -- '), + ); + } - self::assertSame("'test\\'value'", $platform->quoteValue("test'value")); + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + #[Override] + protected function setUp(): void + { + $this->platform = new Sql92(); } } diff --git a/test/unit/Adapter/Platform/TestAsset/TestPlatform.php b/test/unit/Adapter/Platform/TestAsset/TestPlatform.php index 9b87d9d51..9b0a2308d 100644 --- a/test/unit/Adapter/Platform/TestAsset/TestPlatform.php +++ b/test/unit/Adapter/Platform/TestAsset/TestPlatform.php @@ -14,8 +14,7 @@ final class TestPlatform extends AbstractPlatform { public function __construct( protected ?DriverInterface $driver = null, - ) { - } + ) {} #[Override] public function getName(): string diff --git a/test/unit/Adapter/Profiler/ProfilerTest.php b/test/unit/Adapter/Profiler/ProfilerTest.php index 34bd2a0c6..387246868 100644 --- a/test/unit/Adapter/Profiler/ProfilerTest.php +++ b/test/unit/Adapter/Profiler/ProfilerTest.php @@ -22,40 +22,6 @@ final class ProfilerTest extends TestCase { protected Profiler $profiler; - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - #[Override] - protected function setUp(): void - { - $this->profiler = new Profiler(); - } - - public function testProfilerStartWithString(): void - { - $ret = $this->profiler->profilerStart('SELECT * FROM FOO'); - self::assertSame($this->profiler, $ret); - } - - public function testProfilerStartWithStatementContainer(): void - { - $ret = $this->profiler->profilerStart(new StatementContainer()); - self::assertSame($this->profiler, $ret); - } - - public function testProfilerFinishThrowsWithoutStart(): void - { - $this->profiler->profilerStart('SELECT * FROM FOO'); - $ret = $this->profiler->profilerFinish(); - self::assertSame($this->profiler, $ret); - - $profiler = new Profiler(); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('A profile must be started before profilerFinish can be called'); - $profiler->profilerFinish(); - } - public function testGetLastProfileReturnsSqlAndTimings(): void { $this->profiler->profilerStart('SELECT * FROM FOO'); @@ -78,6 +44,18 @@ public function testGetProfilesReturnsAllRecordedProfiles(): void self::assertCount(2, $this->profiler->getProfiles()); } + public function testProfilerFinishThrowsWithoutStart(): void + { + $this->profiler->profilerStart('SELECT * FROM FOO'); + $ret = $this->profiler->profilerFinish(); + self::assertSame($this->profiler, $ret); + + $profiler = new Profiler(); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('A profile must be started before profilerFinish can be called'); + $profiler->profilerFinish(); + } + public function testProfilerStartClonesParameterContainerFromStatementContainer(): void { $parameterContainer = new ParameterContainer(['key' => 'value']); @@ -92,4 +70,26 @@ public function testProfilerStartClonesParameterContainerFromStatementContainer( self::assertInstanceOf(ParameterContainer::class, $profile['parameters']); self::assertNotSame($parameterContainer, $profile['parameters']); } + + public function testProfilerStartWithStatementContainer(): void + { + $ret = $this->profiler->profilerStart(new StatementContainer()); + self::assertSame($this->profiler, $ret); + } + + public function testProfilerStartWithString(): void + { + $ret = $this->profiler->profilerStart('SELECT * FROM FOO'); + self::assertSame($this->profiler, $ret); + } + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + #[Override] + protected function setUp(): void + { + $this->profiler = new Profiler(); + } } diff --git a/test/unit/Adapter/StatementContainerTest.php b/test/unit/Adapter/StatementContainerTest.php index 42a4767e5..9436bc085 100644 --- a/test/unit/Adapter/StatementContainerTest.php +++ b/test/unit/Adapter/StatementContainerTest.php @@ -18,13 +18,6 @@ #[CoversMethod(StatementContainer::class, 'getParameterContainer')] final class StatementContainerTest extends TestCase { - public function testConstructorWithSqlSetsSql(): void - { - $container = new StatementContainer('SELECT 1'); - - self::assertSame('SELECT 1', $container->getSql()); - } - public function testConstructorWithoutSqlDoesNotSetSql(): void { $container = new StatementContainer(); @@ -32,14 +25,11 @@ public function testConstructorWithoutSqlDoesNotSetSql(): void self::assertSame('', $container->getSql()); } - public function testSetAndGetSql(): void + public function testConstructorWithSqlSetsSql(): void { - $container = new StatementContainer(); - - $result = $container->setSql('test'); + $container = new StatementContainer('SELECT 1'); - self::assertSame($container, $result); - self::assertSame('test', $container->getSql()); + self::assertSame('SELECT 1', $container->getSql()); } public function testSetAndGetParameterContainer(): void @@ -52,4 +42,14 @@ public function testSetAndGetParameterContainer(): void self::assertSame($container, $result); self::assertSame($parameterContainer, $container->getParameterContainer()); } + + public function testSetAndGetSql(): void + { + $container = new StatementContainer(); + + $result = $container->setSql('test'); + + self::assertSame($container, $result); + self::assertSame('test', $container->getSql()); + } } diff --git a/test/unit/Adapter/TestAsset/ConcreteAdapterAwareObject.php b/test/unit/Adapter/TestAsset/ConcreteAdapterAwareObject.php index c0d50ebf8..9470fa05f 100644 --- a/test/unit/Adapter/TestAsset/ConcreteAdapterAwareObject.php +++ b/test/unit/Adapter/TestAsset/ConcreteAdapterAwareObject.php @@ -12,9 +12,9 @@ class ConcreteAdapterAwareObject implements AdapterAwareInterface { use AdapterAwareTrait; - public function __construct(private readonly array $options = []) - { - } + public function __construct( + private readonly array $options = [], + ) {} public function getAdapter(): ?AdapterInterface { diff --git a/test/unit/AdapterTestTrait.php b/test/unit/AdapterTestTrait.php index 0a8ce5a28..2cda137dc 100644 --- a/test/unit/AdapterTestTrait.php +++ b/test/unit/AdapterTestTrait.php @@ -18,45 +18,45 @@ trait AdapterTestTrait { /** - * Creates a mock Adapter with all required dependencies + * Creates a real Adapter instance (not mocked) with all required dependencies * - * @param DriverInterface|null $driver Optional mock driver, will create one if not provided - * @param PlatformInterface|null $platform Optional mock platform, will create Sql92 if not provided - * @param ResultSetInterface|null $resultSet Optional mock result set, will create one if not provided + * @param DriverInterface|null $driver Optional driver, will create mock if not provided + * @param PlatformInterface|null $platform Optional platform, will create Sql92 if not provided + * @param ResultSetInterface|null $resultSet Optional result set, will create one if not provided * @throws Exception */ - protected function createMockAdapter( + protected function createAdapter( ?DriverInterface $driver = null, ?PlatformInterface $platform = null, - ?ResultSetInterface $resultSet = null + ?ResultSetInterface $resultSet = null, ): Adapter { - $driver = $driver ?? $this->createMock(DriverInterface::class); - $platform = $platform ?? new Sql92(); - $resultSet = $resultSet ?? new ResultSet(); + $driver ??= $this->createMock(DriverInterface::class); + $platform ??= new Sql92(); + $resultSet ??= new ResultSet(); - return $this->getMockBuilder(Adapter::class) - ->onlyMethods([]) - ->setConstructorArgs([$driver, $platform, $resultSet]) - ->getMock(); + return new Adapter($driver, $platform, $resultSet); } /** - * Creates a real Adapter instance (not mocked) with all required dependencies + * Creates a mock Adapter with all required dependencies * - * @param DriverInterface|null $driver Optional driver, will create mock if not provided - * @param PlatformInterface|null $platform Optional platform, will create Sql92 if not provided - * @param ResultSetInterface|null $resultSet Optional result set, will create one if not provided + * @param DriverInterface|null $driver Optional mock driver, will create one if not provided + * @param PlatformInterface|null $platform Optional mock platform, will create Sql92 if not provided + * @param ResultSetInterface|null $resultSet Optional mock result set, will create one if not provided * @throws Exception */ - protected function createAdapter( + protected function createMockAdapter( ?DriverInterface $driver = null, ?PlatformInterface $platform = null, - ?ResultSetInterface $resultSet = null + ?ResultSetInterface $resultSet = null, ): Adapter { - $driver = $driver ?? $this->createMock(DriverInterface::class); - $platform = $platform ?? new Sql92(); - $resultSet = $resultSet ?? new ResultSet(); + $driver ??= $this->createMock(DriverInterface::class); + $platform ??= new Sql92(); + $resultSet ??= new ResultSet(); - return new Adapter($driver, $platform, $resultSet); + return $this->getMockBuilder(Adapter::class) + ->onlyMethods([]) + ->setConstructorArgs([$driver, $platform, $resultSet]) + ->getMock(); } } diff --git a/test/unit/Container/AdapterInterfaceFactoryTest.php b/test/unit/Container/AdapterInterfaceFactoryTest.php index dcdab41fb..cbdf7b080 100644 --- a/test/unit/Container/AdapterInterfaceFactoryTest.php +++ b/test/unit/Container/AdapterInterfaceFactoryTest.php @@ -22,26 +22,34 @@ #[CoversMethod(AdapterInterfaceFactory::class, '__invoke')] final class AdapterInterfaceFactoryTest extends TestCase { - public function testInvokeThrowsWhenContainerHasNoConfig(): void + public function testInvokeCreatesAdapterWithAllDependencies(): void { $driverMock = $this->createMock(DriverInterface::class); $platformMock = $this->createMock(PlatformInterface::class); + $profilerMock = $this->createMock(ProfilerInterface::class); + $resultSet = new ResultSet(); $container = new ServiceManager([ 'factories' => [ DriverInterface::class => static fn() => $driverMock, PlatformInterface::class => static fn() => $platformMock, + ProfilerInterface::class => static fn() => $profilerMock, + ], + ]); + $container->setService('config', [ + AdapterInterface::class => [ + 'driver' => DriverInterface::class, ], ]); + $container->setService(ResultSetInterface::class, $resultSet); $factory = new AdapterInterfaceFactory(); + $adapter = $factory($container, AdapterInterface::class); - $this->expectException(ContainerException::class); - $this->expectExceptionMessage('Container is missing a config service'); - $factory($container, AdapterInterface::class); + self::assertInstanceOf(Adapter::class, $adapter); } - public function testInvokeThrowsWhenAdapterConfigIsEmpty(): void + public function testInvokeCreatesAdapterWithDefaultResultSet(): void { $driverMock = $this->createMock(DriverInterface::class); $platformMock = $this->createMock(PlatformInterface::class); @@ -52,27 +60,27 @@ public function testInvokeThrowsWhenAdapterConfigIsEmpty(): void PlatformInterface::class => static fn() => $platformMock, ], ]); - $container->setService('config', []); + $container->setService('config', [ + AdapterInterface::class => [ + 'driver' => DriverInterface::class, + ], + ]); $factory = new AdapterInterfaceFactory(); + $adapter = $factory($container, AdapterInterface::class); - $this->expectException(ContainerException::class); - $this->expectExceptionMessage('No configuration found for'); - $factory($container, AdapterInterface::class); + self::assertInstanceOf(ResultSet::class, $adapter->getQueryResultSetPrototype()); } - public function testInvokeCreatesAdapterWithAllDependencies(): void + public function testInvokeCreatesAdapterWithoutOptionalProfiler(): void { $driverMock = $this->createMock(DriverInterface::class); $platformMock = $this->createMock(PlatformInterface::class); - $profilerMock = $this->createMock(ProfilerInterface::class); - $resultSet = new ResultSet(); $container = new ServiceManager([ 'factories' => [ DriverInterface::class => static fn() => $driverMock, PlatformInterface::class => static fn() => $platformMock, - ProfilerInterface::class => static fn() => $profilerMock, ], ]); $container->setService('config', [ @@ -80,15 +88,15 @@ public function testInvokeCreatesAdapterWithAllDependencies(): void 'driver' => DriverInterface::class, ], ]); - $container->setService(ResultSetInterface::class, $resultSet); $factory = new AdapterInterfaceFactory(); $adapter = $factory($container, AdapterInterface::class); self::assertInstanceOf(Adapter::class, $adapter); + self::assertNull($adapter->getProfiler()); } - public function testInvokeCreatesAdapterWithoutOptionalProfiler(): void + public function testInvokeThrowsWhenAdapterConfigIsEmpty(): void { $driverMock = $this->createMock(DriverInterface::class); $platformMock = $this->createMock(PlatformInterface::class); @@ -99,20 +107,16 @@ public function testInvokeCreatesAdapterWithoutOptionalProfiler(): void PlatformInterface::class => static fn() => $platformMock, ], ]); - $container->setService('config', [ - AdapterInterface::class => [ - 'driver' => DriverInterface::class, - ], - ]); + $container->setService('config', []); $factory = new AdapterInterfaceFactory(); - $adapter = $factory($container, AdapterInterface::class); - self::assertInstanceOf(Adapter::class, $adapter); - self::assertNull($adapter->getProfiler()); + $this->expectException(ContainerException::class); + $this->expectExceptionMessage('No configuration found for'); + $factory($container, AdapterInterface::class); } - public function testInvokeCreatesAdapterWithDefaultResultSet(): void + public function testInvokeThrowsWhenContainerHasNoConfig(): void { $driverMock = $this->createMock(DriverInterface::class); $platformMock = $this->createMock(PlatformInterface::class); @@ -123,15 +127,11 @@ public function testInvokeCreatesAdapterWithDefaultResultSet(): void PlatformInterface::class => static fn() => $platformMock, ], ]); - $container->setService('config', [ - AdapterInterface::class => [ - 'driver' => DriverInterface::class, - ], - ]); $factory = new AdapterInterfaceFactory(); - $adapter = $factory($container, AdapterInterface::class); - self::assertInstanceOf(ResultSet::class, $adapter->getQueryResultSetPrototype()); + $this->expectException(ContainerException::class); + $this->expectExceptionMessage('Container is missing a config service'); + $factory($container, AdapterInterface::class); } } diff --git a/test/unit/DeprecatedAssertionsTrait.php b/test/unit/DeprecatedAssertionsTrait.php index fbec2b193..7e0b2c0c5 100644 --- a/test/unit/DeprecatedAssertionsTrait.php +++ b/test/unit/DeprecatedAssertionsTrait.php @@ -21,7 +21,7 @@ public static function assertAttributeEquals( mixed $expected, string $attribute, object $instance, - string $message = '' + string $message = '', ): void { $r = new ReflectionProperty($instance, $attribute); Assert::assertEquals($expected, $r->getValue($instance), $message); diff --git a/test/unit/Metadata/Object/AbstractTableObjectTest.php b/test/unit/Metadata/Object/AbstractTableObjectTest.php index 93644eb0d..eafeca84a 100644 --- a/test/unit/Metadata/Object/AbstractTableObjectTest.php +++ b/test/unit/Metadata/Object/AbstractTableObjectTest.php @@ -12,9 +12,38 @@ final class AbstractTableObjectTest extends TestCase { - private function createConcreteTableObject(?string $name): AbstractTableObject + public function testCompleteTableObjectWithAllProperties(): void { - return new ConcreteTableObject($name); + $table = $this->createConcreteTableObject('users'); + + $columns = [ + new ColumnObject('id', 'users', 'public'), + new ColumnObject('username', 'users', 'public'), + new ColumnObject('email', 'users', 'public'), + ]; + + $constraints = [ + new ConstraintObject('pk_users', 'users', 'public'), + new ConstraintObject('uq_users_email', 'users', 'public'), + ]; + + $table->setColumns($columns); + $table->setConstraints($constraints); + + // Verify all properties are set correctly + self::assertSame('users', $table->getName()); + self::assertSame($columns, $table->getColumns()); + self::assertCount(3, $table->getColumns()); + self::assertSame($constraints, $table->getConstraints()); + self::assertCount(2, $table->getConstraints()); + } + + public function testConstructorWithEmptyString(): void + { + $table = $this->createConcreteTableObject(''); + + // Verify empty string is converted to null + self::assertNull($table->getName()); } public function testConstructorWithName(): void @@ -33,21 +62,20 @@ public function testConstructorWithNullName(): void self::assertNull($table->getName()); } - public function testConstructorWithEmptyString(): void + public function testGetColumnsReturnsNullWhenNotSet(): void { - $table = $this->createConcreteTableObject(''); + $table = $this->createConcreteTableObject('table'); - // Verify empty string is converted to null - self::assertNull($table->getName()); + // Verify columns return null when not set + self::assertNull($table->getColumns()); } - public function testSetNameAndGetName(): void + public function testGetConstraintsReturnsNullWhenNotSet(): void { - $table = $this->createConcreteTableObject('initial_name'); + $table = $this->createConcreteTableObject('table'); - // Update name and verify change - $table->setName('new_name'); - self::assertSame('new_name', $table->getName()); + // Verify constraints return null when not set + self::assertNull($table->getConstraints()); } public function testSetColumnsAndGetColumns(): void @@ -94,45 +122,17 @@ public function testSetConstraintsWithEmptyArray(): void self::assertSame([], $table->getConstraints()); } - public function testCompleteTableObjectWithAllProperties(): void - { - $table = $this->createConcreteTableObject('users'); - - $columns = [ - new ColumnObject('id', 'users', 'public'), - new ColumnObject('username', 'users', 'public'), - new ColumnObject('email', 'users', 'public'), - ]; - - $constraints = [ - new ConstraintObject('pk_users', 'users', 'public'), - new ConstraintObject('uq_users_email', 'users', 'public'), - ]; - - $table->setColumns($columns); - $table->setConstraints($constraints); - - // Verify all properties are set correctly - self::assertSame('users', $table->getName()); - self::assertSame($columns, $table->getColumns()); - self::assertCount(3, $table->getColumns()); - self::assertSame($constraints, $table->getConstraints()); - self::assertCount(2, $table->getConstraints()); - } - - public function testGetColumnsReturnsNullWhenNotSet(): void + public function testSetNameAndGetName(): void { - $table = $this->createConcreteTableObject('table'); + $table = $this->createConcreteTableObject('initial_name'); - // Verify columns return null when not set - self::assertNull($table->getColumns()); + // Update name and verify change + $table->setName('new_name'); + self::assertSame('new_name', $table->getName()); } - public function testGetConstraintsReturnsNullWhenNotSet(): void + private function createConcreteTableObject(?string $name): AbstractTableObject { - $table = $this->createConcreteTableObject('table'); - - // Verify constraints return null when not set - self::assertNull($table->getConstraints()); + return new ConcreteTableObject($name); } } diff --git a/test/unit/Metadata/Object/ColumnObjectTest.php b/test/unit/Metadata/Object/ColumnObjectTest.php index c8b9cec5a..bad628695 100644 --- a/test/unit/Metadata/Object/ColumnObjectTest.php +++ b/test/unit/Metadata/Object/ColumnObjectTest.php @@ -9,6 +9,38 @@ final class ColumnObjectTest extends TestCase { + public function testCompleteColumnObjectWithAllProperties(): void + { + $column = new ColumnObject('id', 'users', 'public'); + + $column->setOrdinalPosition(1) + ->setColumnDefault('0') + ->setIsNullable(false) + ->setDataType('INT') + ->setCharacterMaximumLength(null) + ->setCharacterOctetLength(null) + ->setNumericPrecision(10) + ->setNumericScale(0) + ->setNumericUnsigned(true) + ->setErratas(['auto_increment' => true, 'comment' => 'Primary key']); + + // Verify all properties are set correctly + self::assertSame('id', $column->getName()); + self::assertSame('users', $column->getTableName()); + self::assertSame('public', $column->getSchemaName()); + self::assertSame(1, $column->getOrdinalPosition()); + self::assertSame('0', $column->getColumnDefault()); + self::assertFalse($column->getIsNullable()); + self::assertSame('INT', $column->getDataType()); + self::assertNull($column->getCharacterMaximumLength()); + self::assertNull($column->getCharacterOctetLength()); + self::assertSame(10, $column->getNumericPrecision()); + self::assertSame(0, $column->getNumericScale()); + self::assertTrue($column->isNumericUnsigned()); + self::assertTrue($column->getErrata('auto_increment')); + self::assertSame('Primary key', $column->getErrata('comment')); + } + public function testConstructorWithAllParameters(): void { $column = new ColumnObject('column_name', 'table_name', 'schema_name'); @@ -29,72 +61,20 @@ public function testConstructorWithNullSchema(): void self::assertNull($column->getSchemaName()); } - public function testSetNameAndGetName(): void - { - $column = new ColumnObject('initial', 'table', 'schema'); - - // Update name and verify change - $column->setName('new_name'); - self::assertSame('new_name', $column->getName()); - } - - public function testSetTableNameAndGetTableNameWithFluentInterface(): void - { - $column = new ColumnObject('column', 'initial_table', 'schema'); - - // Verify fluent interface and value update - $result = $column->setTableName('new_table'); - self::assertSame($column, $result); - self::assertSame('new_table', $column->getTableName()); - } - - public function testSetSchemaNameAndGetSchemaName(): void - { - $column = new ColumnObject('column', 'table', 'initial_schema'); - - // Update schema and verify change - $column->setSchemaName('new_schema'); - self::assertSame('new_schema', $column->getSchemaName()); - } - - public function testSetOrdinalPositionAndGetOrdinalPositionWithFluentInterface(): void - { - $column = new ColumnObject('column', 'table', 'schema'); - - // Verify fluent interface and value update - $result = $column->setOrdinalPosition(5); - self::assertSame($column, $result); - self::assertSame(5, $column->getOrdinalPosition()); - } - - public function testSetColumnDefaultAndGetColumnDefaultWithFluentInterface(): void - { - $column = new ColumnObject('column', 'table', 'schema'); - - // Verify fluent interface and value update - $result = $column->setColumnDefault('DEFAULT_VALUE'); - self::assertSame($column, $result); - self::assertSame('DEFAULT_VALUE', $column->getColumnDefault()); - } - - public function testSetColumnDefaultWithNull(): void + public function testGetErrataNonExistentKeyReturnsNull(): void { $column = new ColumnObject('column', 'table', 'schema'); - $column->setColumnDefault('initial'); - // Set default to null and verify - $column->setColumnDefault(null); - self::assertNull($column->getColumnDefault()); + // Verify non-existent key returns null + self::assertNull($column->getErrata('non_existent')); } - public function testSetIsNullableAndGetIsNullableWithFluentInterface(): void + public function testGetErratasReturnsEmptyArrayInitially(): void { $column = new ColumnObject('column', 'table', 'schema'); - // Verify fluent interface and value update - $result = $column->setIsNullable(true); - self::assertSame($column, $result); - self::assertTrue($column->getIsNullable()); + // Verify erratas default to empty array + self::assertSame([], $column->getErratas()); } public function testIsNullableAlias(): void @@ -106,14 +86,14 @@ public function testIsNullableAlias(): void self::assertFalse($column->getIsNullable()); } - public function testSetDataTypeAndGetDataTypeWithFluentInterface(): void + public function testIsNumericUnsignedAlias(): void { $column = new ColumnObject('column', 'table', 'schema'); - // Verify fluent interface and value update - $result = $column->setDataType('VARCHAR'); - self::assertSame($column, $result); - self::assertSame('VARCHAR', $column->getDataType()); + // Verify alias method returns same value + $column->setNumericUnsigned(false); + self::assertFalse($column->isNumericUnsigned()); + self::assertSame($column->getNumericUnsigned(), $column->isNumericUnsigned()); } public function testSetCharacterMaximumLengthAndGetCharacterMaximumLengthWithFluentInterface(): void @@ -156,44 +136,34 @@ public function testSetCharacterOctetLengthWithNull(): void self::assertNull($column->getCharacterOctetLength()); } - public function testSetNumericPrecisionAndGetNumericPrecisionWithFluentInterface(): void + public function testSetColumnDefaultAndGetColumnDefaultWithFluentInterface(): void { $column = new ColumnObject('column', 'table', 'schema'); // Verify fluent interface and value update - $result = $column->setNumericPrecision(10); + $result = $column->setColumnDefault('DEFAULT_VALUE'); self::assertSame($column, $result); - self::assertSame(10, $column->getNumericPrecision()); + self::assertSame('DEFAULT_VALUE', $column->getColumnDefault()); } - public function testSetNumericScaleAndGetNumericScaleWithFluentInterface(): void + public function testSetColumnDefaultWithNull(): void { $column = new ColumnObject('column', 'table', 'schema'); + $column->setColumnDefault('initial'); - // Verify fluent interface and value update - $result = $column->setNumericScale(2); - self::assertSame($column, $result); - self::assertSame(2, $column->getNumericScale()); + // Set default to null and verify + $column->setColumnDefault(null); + self::assertNull($column->getColumnDefault()); } - public function testSetNumericUnsignedAndGetNumericUnsignedWithFluentInterface(): void + public function testSetDataTypeAndGetDataTypeWithFluentInterface(): void { $column = new ColumnObject('column', 'table', 'schema'); // Verify fluent interface and value update - $result = $column->setNumericUnsigned(true); + $result = $column->setDataType('VARCHAR'); self::assertSame($column, $result); - self::assertTrue($column->getNumericUnsigned()); - } - - public function testIsNumericUnsignedAlias(): void - { - $column = new ColumnObject('column', 'table', 'schema'); - - // Verify alias method returns same value - $column->setNumericUnsigned(false); - self::assertFalse($column->isNumericUnsigned()); - self::assertSame($column->getNumericUnsigned(), $column->isNumericUnsigned()); + self::assertSame('VARCHAR', $column->getDataType()); } public function testSetErrataAndGetErrata(): void @@ -206,12 +176,18 @@ public function testSetErrataAndGetErrata(): void self::assertSame('value1', $column->getErrata('key1')); } - public function testGetErrataNonExistentKeyReturnsNull(): void + public function testSetErratasIteratesCorrectly(): void { - $column = new ColumnObject('column', 'table', 'schema'); + $column = new ColumnObject('column', 'table', 'schema'); + $erratas = [ + 'key1' => 'value1', + 'key2' => 'value2', + ]; - // Verify non-existent key returns null - self::assertNull($column->getErrata('non_existent')); + // Verify each errata is accessible individually + $column->setErratas($erratas); + self::assertSame('value1', $column->getErrata('key1')); + self::assertSame('value2', $column->getErrata('key2')); } public function testSetErratasWithArrayAndGetErratas(): void @@ -229,57 +205,81 @@ public function testSetErratasWithArrayAndGetErratas(): void self::assertSame($erratas, $column->getErratas()); } - public function testSetErratasIteratesCorrectly(): void + public function testSetIsNullableAndGetIsNullableWithFluentInterface(): void { - $column = new ColumnObject('column', 'table', 'schema'); - $erratas = [ - 'key1' => 'value1', - 'key2' => 'value2', - ]; + $column = new ColumnObject('column', 'table', 'schema'); - // Verify each errata is accessible individually - $column->setErratas($erratas); - self::assertSame('value1', $column->getErrata('key1')); - self::assertSame('value2', $column->getErrata('key2')); + // Verify fluent interface and value update + $result = $column->setIsNullable(true); + self::assertSame($column, $result); + self::assertTrue($column->getIsNullable()); } - public function testGetErratasReturnsEmptyArrayInitially(): void + public function testSetNameAndGetName(): void + { + $column = new ColumnObject('initial', 'table', 'schema'); + + // Update name and verify change + $column->setName('new_name'); + self::assertSame('new_name', $column->getName()); + } + + public function testSetNumericPrecisionAndGetNumericPrecisionWithFluentInterface(): void { $column = new ColumnObject('column', 'table', 'schema'); - // Verify erratas default to empty array - self::assertSame([], $column->getErratas()); + // Verify fluent interface and value update + $result = $column->setNumericPrecision(10); + self::assertSame($column, $result); + self::assertSame(10, $column->getNumericPrecision()); } - public function testCompleteColumnObjectWithAllProperties(): void + public function testSetNumericScaleAndGetNumericScaleWithFluentInterface(): void { - $column = new ColumnObject('id', 'users', 'public'); + $column = new ColumnObject('column', 'table', 'schema'); - $column->setOrdinalPosition(1) - ->setColumnDefault('0') - ->setIsNullable(false) - ->setDataType('INT') - ->setCharacterMaximumLength(null) - ->setCharacterOctetLength(null) - ->setNumericPrecision(10) - ->setNumericScale(0) - ->setNumericUnsigned(true) - ->setErratas(['auto_increment' => true, 'comment' => 'Primary key']); + // Verify fluent interface and value update + $result = $column->setNumericScale(2); + self::assertSame($column, $result); + self::assertSame(2, $column->getNumericScale()); + } - // Verify all properties are set correctly - self::assertSame('id', $column->getName()); - self::assertSame('users', $column->getTableName()); - self::assertSame('public', $column->getSchemaName()); - self::assertSame(1, $column->getOrdinalPosition()); - self::assertSame('0', $column->getColumnDefault()); - self::assertFalse($column->getIsNullable()); - self::assertSame('INT', $column->getDataType()); - self::assertNull($column->getCharacterMaximumLength()); - self::assertNull($column->getCharacterOctetLength()); - self::assertSame(10, $column->getNumericPrecision()); - self::assertSame(0, $column->getNumericScale()); - self::assertTrue($column->isNumericUnsigned()); - self::assertTrue($column->getErrata('auto_increment')); - self::assertSame('Primary key', $column->getErrata('comment')); + public function testSetNumericUnsignedAndGetNumericUnsignedWithFluentInterface(): void + { + $column = new ColumnObject('column', 'table', 'schema'); + + // Verify fluent interface and value update + $result = $column->setNumericUnsigned(true); + self::assertSame($column, $result); + self::assertTrue($column->getNumericUnsigned()); + } + + public function testSetOrdinalPositionAndGetOrdinalPositionWithFluentInterface(): void + { + $column = new ColumnObject('column', 'table', 'schema'); + + // Verify fluent interface and value update + $result = $column->setOrdinalPosition(5); + self::assertSame($column, $result); + self::assertSame(5, $column->getOrdinalPosition()); + } + + public function testSetSchemaNameAndGetSchemaName(): void + { + $column = new ColumnObject('column', 'table', 'initial_schema'); + + // Update schema and verify change + $column->setSchemaName('new_schema'); + self::assertSame('new_schema', $column->getSchemaName()); + } + + public function testSetTableNameAndGetTableNameWithFluentInterface(): void + { + $column = new ColumnObject('column', 'initial_table', 'schema'); + + // Verify fluent interface and value update + $result = $column->setTableName('new_table'); + self::assertSame($column, $result); + self::assertSame('new_table', $column->getTableName()); } } diff --git a/test/unit/Metadata/Object/ConstraintKeyObjectTest.php b/test/unit/Metadata/Object/ConstraintKeyObjectTest.php index 707317aeb..77e6074c1 100644 --- a/test/unit/Metadata/Object/ConstraintKeyObjectTest.php +++ b/test/unit/Metadata/Object/ConstraintKeyObjectTest.php @@ -9,6 +9,29 @@ final class ConstraintKeyObjectTest extends TestCase { + public function testCompleteConstraintKeyObject(): void + { + $constraintKey = new ConstraintKeyObject('user_id'); + + $constraintKey->setOrdinalPosition(1) + ->setPositionInUniqueConstraint(false) + ->setReferencedTableSchema('public') + ->setReferencedTableName('users') + ->setReferencedColumnName('id'); + $constraintKey->setForeignKeyUpdateRule(ConstraintKeyObject::FK_CASCADE); + $constraintKey->setForeignKeyDeleteRule(ConstraintKeyObject::FK_RESTRICT); + + // Verify all properties are set correctly + self::assertSame('user_id', $constraintKey->getColumnName()); + self::assertSame(1, $constraintKey->getOrdinalPosition()); + self::assertFalse($constraintKey->getPositionInUniqueConstraint()); + self::assertSame('public', $constraintKey->getReferencedTableSchema()); + self::assertSame('users', $constraintKey->getReferencedTableName()); + self::assertSame('id', $constraintKey->getReferencedColumnName()); + self::assertSame('CASCADE', $constraintKey->getForeignKeyUpdateRule()); + self::assertSame('RESTRICT', $constraintKey->getForeignKeyDeleteRule()); + } + public function testConstructorSetsColumnName(): void { $constraintKey = new ConstraintKeyObject('column_name'); @@ -37,54 +60,42 @@ public function testSetColumnNameAndGetColumnNameWithFluentInterface(): void self::assertSame('new_column', $constraintKey->getColumnName()); } - public function testSetOrdinalPositionAndGetOrdinalPositionWithFluentInterface(): void + public function testSetForeignKeyDeleteRuleAndGetForeignKeyDeleteRule(): void { $constraintKey = new ConstraintKeyObject('column'); - // Verify fluent interface and value update - $result = $constraintKey->setOrdinalPosition(3); - self::assertSame($constraintKey, $result); - self::assertSame(3, $constraintKey->getOrdinalPosition()); - } - - public function testSetPositionInUniqueConstraintAndGetPositionInUniqueConstraintWithFluentInterface(): void - { - $constraintKey = new ConstraintKeyObject('column'); + // Set delete rule and verify retrieval + $constraintKey->setForeignKeyDeleteRule(ConstraintKeyObject::FK_RESTRICT); + self::assertSame('RESTRICT', $constraintKey->getForeignKeyDeleteRule()); - // Verify fluent interface and value update - $result = $constraintKey->setPositionInUniqueConstraint(true); - self::assertSame($constraintKey, $result); - self::assertTrue($constraintKey->getPositionInUniqueConstraint()); + // Verify mutation by changing to different value + $constraintKey->setForeignKeyDeleteRule(ConstraintKeyObject::FK_CASCADE); + self::assertSame('CASCADE', $constraintKey->getForeignKeyDeleteRule()); } - public function testSetReferencedTableSchemaAndGetReferencedTableSchemaWithFluentInterface(): void + public function testSetForeignKeyDeleteRuleWithAllConstants(): void { $constraintKey = new ConstraintKeyObject('column'); - // Verify fluent interface and value update - $result = $constraintKey->setReferencedTableSchema('ref_schema'); - self::assertSame($constraintKey, $result); - self::assertSame('ref_schema', $constraintKey->getReferencedTableSchema()); - } + // Verify CASCADE constant + $constraintKey->setForeignKeyDeleteRule(ConstraintKeyObject::FK_CASCADE); + self::assertSame('CASCADE', $constraintKey->getForeignKeyDeleteRule()); - public function testSetReferencedTableNameAndGetReferencedTableNameWithFluentInterface(): void - { - $constraintKey = new ConstraintKeyObject('column'); + // Verify SET NULL constant + $constraintKey->setForeignKeyDeleteRule(ConstraintKeyObject::FK_SET_NULL); + self::assertSame('SET NULL', $constraintKey->getForeignKeyDeleteRule()); - // Verify fluent interface and value update - $result = $constraintKey->setReferencedTableName('ref_table'); - self::assertSame($constraintKey, $result); - self::assertSame('ref_table', $constraintKey->getReferencedTableName()); - } + // Verify NO ACTION constant + $constraintKey->setForeignKeyDeleteRule(ConstraintKeyObject::FK_NO_ACTION); + self::assertSame('NO ACTION', $constraintKey->getForeignKeyDeleteRule()); - public function testSetReferencedColumnNameAndGetReferencedColumnNameWithFluentInterface(): void - { - $constraintKey = new ConstraintKeyObject('column'); + // Verify RESTRICT constant + $constraintKey->setForeignKeyDeleteRule(ConstraintKeyObject::FK_RESTRICT); + self::assertSame('RESTRICT', $constraintKey->getForeignKeyDeleteRule()); - // Verify fluent interface and value update - $result = $constraintKey->setReferencedColumnName('ref_column'); - self::assertSame($constraintKey, $result); - self::assertSame('ref_column', $constraintKey->getReferencedColumnName()); + // Verify SET DEFAULT constant + $constraintKey->setForeignKeyDeleteRule(ConstraintKeyObject::FK_SET_DEFAULT); + self::assertSame('SET DEFAULT', $constraintKey->getForeignKeyDeleteRule()); } public function testSetForeignKeyUpdateRuleAndGetForeignKeyUpdateRule(): void @@ -125,64 +136,53 @@ public function testSetForeignKeyUpdateRuleWithAllConstants(): void self::assertSame('SET DEFAULT', $constraintKey->getForeignKeyUpdateRule()); } - public function testSetForeignKeyDeleteRuleAndGetForeignKeyDeleteRule(): void + public function testSetOrdinalPositionAndGetOrdinalPositionWithFluentInterface(): void { $constraintKey = new ConstraintKeyObject('column'); - // Set delete rule and verify retrieval - $constraintKey->setForeignKeyDeleteRule(ConstraintKeyObject::FK_RESTRICT); - self::assertSame('RESTRICT', $constraintKey->getForeignKeyDeleteRule()); - - // Verify mutation by changing to different value - $constraintKey->setForeignKeyDeleteRule(ConstraintKeyObject::FK_CASCADE); - self::assertSame('CASCADE', $constraintKey->getForeignKeyDeleteRule()); + // Verify fluent interface and value update + $result = $constraintKey->setOrdinalPosition(3); + self::assertSame($constraintKey, $result); + self::assertSame(3, $constraintKey->getOrdinalPosition()); } - public function testSetForeignKeyDeleteRuleWithAllConstants(): void + public function testSetPositionInUniqueConstraintAndGetPositionInUniqueConstraintWithFluentInterface(): void { $constraintKey = new ConstraintKeyObject('column'); - // Verify CASCADE constant - $constraintKey->setForeignKeyDeleteRule(ConstraintKeyObject::FK_CASCADE); - self::assertSame('CASCADE', $constraintKey->getForeignKeyDeleteRule()); + // Verify fluent interface and value update + $result = $constraintKey->setPositionInUniqueConstraint(true); + self::assertSame($constraintKey, $result); + self::assertTrue($constraintKey->getPositionInUniqueConstraint()); + } - // Verify SET NULL constant - $constraintKey->setForeignKeyDeleteRule(ConstraintKeyObject::FK_SET_NULL); - self::assertSame('SET NULL', $constraintKey->getForeignKeyDeleteRule()); + public function testSetReferencedColumnNameAndGetReferencedColumnNameWithFluentInterface(): void + { + $constraintKey = new ConstraintKeyObject('column'); - // Verify NO ACTION constant - $constraintKey->setForeignKeyDeleteRule(ConstraintKeyObject::FK_NO_ACTION); - self::assertSame('NO ACTION', $constraintKey->getForeignKeyDeleteRule()); + // Verify fluent interface and value update + $result = $constraintKey->setReferencedColumnName('ref_column'); + self::assertSame($constraintKey, $result); + self::assertSame('ref_column', $constraintKey->getReferencedColumnName()); + } - // Verify RESTRICT constant - $constraintKey->setForeignKeyDeleteRule(ConstraintKeyObject::FK_RESTRICT); - self::assertSame('RESTRICT', $constraintKey->getForeignKeyDeleteRule()); + public function testSetReferencedTableNameAndGetReferencedTableNameWithFluentInterface(): void + { + $constraintKey = new ConstraintKeyObject('column'); - // Verify SET DEFAULT constant - $constraintKey->setForeignKeyDeleteRule(ConstraintKeyObject::FK_SET_DEFAULT); - self::assertSame('SET DEFAULT', $constraintKey->getForeignKeyDeleteRule()); + // Verify fluent interface and value update + $result = $constraintKey->setReferencedTableName('ref_table'); + self::assertSame($constraintKey, $result); + self::assertSame('ref_table', $constraintKey->getReferencedTableName()); } - public function testCompleteConstraintKeyObject(): void + public function testSetReferencedTableSchemaAndGetReferencedTableSchemaWithFluentInterface(): void { - $constraintKey = new ConstraintKeyObject('user_id'); - - $constraintKey->setOrdinalPosition(1) - ->setPositionInUniqueConstraint(false) - ->setReferencedTableSchema('public') - ->setReferencedTableName('users') - ->setReferencedColumnName('id'); - $constraintKey->setForeignKeyUpdateRule(ConstraintKeyObject::FK_CASCADE); - $constraintKey->setForeignKeyDeleteRule(ConstraintKeyObject::FK_RESTRICT); + $constraintKey = new ConstraintKeyObject('column'); - // Verify all properties are set correctly - self::assertSame('user_id', $constraintKey->getColumnName()); - self::assertSame(1, $constraintKey->getOrdinalPosition()); - self::assertFalse($constraintKey->getPositionInUniqueConstraint()); - self::assertSame('public', $constraintKey->getReferencedTableSchema()); - self::assertSame('users', $constraintKey->getReferencedTableName()); - self::assertSame('id', $constraintKey->getReferencedColumnName()); - self::assertSame('CASCADE', $constraintKey->getForeignKeyUpdateRule()); - self::assertSame('RESTRICT', $constraintKey->getForeignKeyDeleteRule()); + // Verify fluent interface and value update + $result = $constraintKey->setReferencedTableSchema('ref_schema'); + self::assertSame($constraintKey, $result); + self::assertSame('ref_schema', $constraintKey->getReferencedTableSchema()); } } diff --git a/test/unit/Metadata/Object/ConstraintObjectTest.php b/test/unit/Metadata/Object/ConstraintObjectTest.php index 254b8e434..8fe4d22a0 100644 --- a/test/unit/Metadata/Object/ConstraintObjectTest.php +++ b/test/unit/Metadata/Object/ConstraintObjectTest.php @@ -9,65 +9,97 @@ final class ConstraintObjectTest extends TestCase { - public function testConstructorWithAllParameters(): void + public function testCompleteCheckConstraint(): void { - $constraint = new ConstraintObject('constraint_name', 'table_name', 'schema_name'); + $constraint = new ConstraintObject('chk_users_age', 'users', 'public'); + $constraint->setType('CHECK'); + $constraint->setCheckClause('age >= 18 AND age <= 120'); - // Verify all constructor parameters are set - self::assertSame('constraint_name', $constraint->getName()); - self::assertSame('table_name', $constraint->getTableName()); - self::assertSame('schema_name', $constraint->getSchemaName()); + // Verify check constraint is configured correctly + self::assertTrue($constraint->isCheck()); + self::assertFalse($constraint->isPrimaryKey()); + self::assertFalse($constraint->isUnique()); + self::assertFalse($constraint->isForeignKey()); + self::assertSame('age >= 18 AND age <= 120', $constraint->getCheckClause()); } - public function testConstructorWithNullSchema(): void + public function testCompleteForeignKeyConstraint(): void { - $constraint = new ConstraintObject('constraint_name', 'table_name'); + $constraint = new ConstraintObject('fk_orders_user', 'orders', 'public'); + $constraint->setType('FOREIGN KEY'); + $constraint->setColumns(['user_id']) + ->setReferencedTableSchema('public') + ->setReferencedTableName('users') + ->setReferencedColumns(['id']) + ->setMatchOption('SIMPLE') + ->setUpdateRule('CASCADE') + ->setDeleteRule('RESTRICT'); - // Verify schema defaults to null - self::assertSame('constraint_name', $constraint->getName()); - self::assertSame('table_name', $constraint->getTableName()); - self::assertNull($constraint->getSchemaName()); + // Verify foreign key constraint is configured correctly + self::assertSame('fk_orders_user', $constraint->getName()); + self::assertTrue($constraint->isForeignKey()); + self::assertFalse($constraint->isPrimaryKey()); + self::assertFalse($constraint->isUnique()); + self::assertFalse($constraint->isCheck()); + self::assertSame(['user_id'], $constraint->getColumns()); + self::assertSame('public', $constraint->getReferencedTableSchema()); + self::assertSame('users', $constraint->getReferencedTableName()); + self::assertSame(['id'], $constraint->getReferencedColumns()); + self::assertSame('SIMPLE', $constraint->getMatchOption()); + self::assertSame('CASCADE', $constraint->getUpdateRule()); + self::assertSame('RESTRICT', $constraint->getDeleteRule()); } - public function testSetNameAndGetName(): void + public function testCompletePrimaryKeyConstraint(): void { - $constraint = new ConstraintObject('initial', 'table', 'schema'); + $constraint = new ConstraintObject('pk_users', 'users', 'public'); + $constraint->setType('PRIMARY KEY'); + $constraint->setColumns(['id']); - // Update name and verify change - $constraint->setName('new_name'); - self::assertSame('new_name', $constraint->getName()); + // Verify primary key constraint is configured correctly + self::assertSame('pk_users', $constraint->getName()); + self::assertSame('users', $constraint->getTableName()); + self::assertSame('public', $constraint->getSchemaName()); + self::assertTrue($constraint->isPrimaryKey()); + self::assertFalse($constraint->isUnique()); + self::assertFalse($constraint->isForeignKey()); + self::assertFalse($constraint->isCheck()); + self::assertSame(['id'], $constraint->getColumns()); + self::assertTrue($constraint->hasColumns()); } - public function testSetSchemaNameAndGetSchemaName(): void + public function testCompleteUniqueConstraint(): void { - $constraint = new ConstraintObject('name', 'table', 'initial_schema'); + $constraint = new ConstraintObject('uq_users_email', 'users', 'public'); + $constraint->setType('UNIQUE'); + $constraint->setColumns(['email']); - // Update schema and verify change - $constraint->setSchemaName('new_schema'); - self::assertSame('new_schema', $constraint->getSchemaName()); + // Verify unique constraint is configured correctly + self::assertTrue($constraint->isUnique()); + self::assertFalse($constraint->isPrimaryKey()); + self::assertFalse($constraint->isForeignKey()); + self::assertFalse($constraint->isCheck()); + self::assertSame(['email'], $constraint->getColumns()); } - public function testSetTableNameAndGetTableNameWithFluentInterface(): void + public function testConstructorWithAllParameters(): void { - $constraint = new ConstraintObject('name', 'initial_table', 'schema'); + $constraint = new ConstraintObject('constraint_name', 'table_name', 'schema_name'); - // Verify fluent interface and value update - $result = $constraint->setTableName('new_table'); - self::assertSame($constraint, $result); - self::assertSame('new_table', $constraint->getTableName()); + // Verify all constructor parameters are set + self::assertSame('constraint_name', $constraint->getName()); + self::assertSame('table_name', $constraint->getTableName()); + self::assertSame('schema_name', $constraint->getSchemaName()); } - public function testSetTypeAndGetType(): void + public function testConstructorWithNullSchema(): void { - $constraint = new ConstraintObject('name', 'table', 'schema'); - - // Set type and verify retrieval - $constraint->setType('PRIMARY KEY'); - self::assertSame('PRIMARY KEY', $constraint->getType()); + $constraint = new ConstraintObject('constraint_name', 'table_name'); - // Verify mutation to different type - $constraint->setType('UNIQUE'); - self::assertSame('UNIQUE', $constraint->getType()); + // Verify schema defaults to null + self::assertSame('constraint_name', $constraint->getName()); + self::assertSame('table_name', $constraint->getTableName()); + self::assertNull($constraint->getSchemaName()); } public function testHasColumnsReturnsFalseWhenEmpty(): void @@ -87,97 +119,73 @@ public function testHasColumnsReturnsTrueWhenPopulated(): void self::assertTrue($constraint->hasColumns()); } - public function testSetColumnsAndGetColumnsWithFluentInterface(): void + public function testIsCheckReturnsFalseForOtherTypes(): void { $constraint = new ConstraintObject('name', 'table', 'schema'); - $columns = ['column1', 'column2', 'column3']; - // Verify fluent interface and value update - $result = $constraint->setColumns($columns); - self::assertSame($constraint, $result); - self::assertSame($columns, $constraint->getColumns()); - } + // Verify isCheck returns false for PRIMARY KEY + $constraint->setType('PRIMARY KEY'); + self::assertFalse($constraint->isCheck()); - public function testSetColumnsWithEmptyArray(): void - { - $constraint = new ConstraintObject('name', 'table', 'schema'); - $constraint->setColumns(['col1']); + // Verify isCheck returns false for UNIQUE + $constraint->setType('UNIQUE'); + self::assertFalse($constraint->isCheck()); - // Set empty array and verify hasColumns reflects change - $constraint->setColumns([]); - self::assertSame([], $constraint->getColumns()); - self::assertFalse($constraint->hasColumns()); + // Verify isCheck returns false for FOREIGN KEY + $constraint->setType('FOREIGN KEY'); + self::assertFalse($constraint->isCheck()); } - public function testSetReferencedTableSchemaAndGetReferencedTableSchemaWithFluentInterface(): void + public function testIsCheckReturnsTrueForCheck(): void { $constraint = new ConstraintObject('name', 'table', 'schema'); - // Verify fluent interface and value update - $result = $constraint->setReferencedTableSchema('ref_schema'); - self::assertSame($constraint, $result); - self::assertSame('ref_schema', $constraint->getReferencedTableSchema()); + // Verify isCheck returns true for CHECK type + $constraint->setType('CHECK'); + self::assertTrue($constraint->isCheck()); } - public function testSetReferencedTableNameAndGetReferencedTableNameWithFluentInterface(): void + public function testIsForeignKeyReturnsFalseForOtherTypes(): void { $constraint = new ConstraintObject('name', 'table', 'schema'); - // Verify fluent interface and value update - $result = $constraint->setReferencedTableName('ref_table'); - self::assertSame($constraint, $result); - self::assertSame('ref_table', $constraint->getReferencedTableName()); - } - - public function testSetReferencedColumnsAndGetReferencedColumnsWithFluentInterface(): void - { - $constraint = new ConstraintObject('name', 'table', 'schema'); - $columns = ['ref_col1', 'ref_col2']; - - // Verify fluent interface and value update - $result = $constraint->setReferencedColumns($columns); - self::assertSame($constraint, $result); - self::assertSame($columns, $constraint->getReferencedColumns()); - } + // Verify isForeignKey returns false for PRIMARY KEY + $constraint->setType('PRIMARY KEY'); + self::assertFalse($constraint->isForeignKey()); - public function testSetMatchOptionAndGetMatchOptionWithFluentInterface(): void - { - $constraint = new ConstraintObject('name', 'table', 'schema'); + // Verify isForeignKey returns false for UNIQUE + $constraint->setType('UNIQUE'); + self::assertFalse($constraint->isForeignKey()); - // Verify fluent interface and value update - $result = $constraint->setMatchOption('FULL'); - self::assertSame($constraint, $result); - self::assertSame('FULL', $constraint->getMatchOption()); + // Verify isForeignKey returns false for CHECK + $constraint->setType('CHECK'); + self::assertFalse($constraint->isForeignKey()); } - public function testSetUpdateRuleAndGetUpdateRuleWithFluentInterface(): void + public function testIsForeignKeyReturnsTrueForForeignKey(): void { $constraint = new ConstraintObject('name', 'table', 'schema'); - // Verify fluent interface and value update - $result = $constraint->setUpdateRule('CASCADE'); - self::assertSame($constraint, $result); - self::assertSame('CASCADE', $constraint->getUpdateRule()); + // Verify isForeignKey returns true for FOREIGN KEY type + $constraint->setType('FOREIGN KEY'); + self::assertTrue($constraint->isForeignKey()); } - public function testSetDeleteRuleAndGetDeleteRuleWithFluentInterface(): void + public function testIsPrimaryKeyReturnsFalseForOtherTypes(): void { $constraint = new ConstraintObject('name', 'table', 'schema'); - // Verify fluent interface and value update - $result = $constraint->setDeleteRule('RESTRICT'); - self::assertSame($constraint, $result); - self::assertSame('RESTRICT', $constraint->getDeleteRule()); - } + // Verify isPrimaryKey returns false for UNIQUE + $constraint->setType('UNIQUE'); + self::assertFalse($constraint->isPrimaryKey()); - public function testSetCheckClauseAndGetCheckClauseWithFluentInterface(): void - { - $constraint = new ConstraintObject('name', 'table', 'schema'); + // Verify isPrimaryKey returns false for FOREIGN KEY + $constraint->setType('FOREIGN KEY'); + self::assertFalse($constraint->isPrimaryKey()); - // Verify fluent interface and value update - $result = $constraint->setCheckClause('age >= 18'); - self::assertSame($constraint, $result); - self::assertSame('age >= 18', $constraint->getCheckClause()); + // Verify isPrimaryKey returns false for CHECK + $constraint->setType('CHECK'); + self::assertFalse($constraint->isPrimaryKey()); } public function testIsPrimaryKeyReturnsTrueForPrimaryKey(): void @@ -189,21 +197,21 @@ public function testIsPrimaryKeyReturnsTrueForPrimaryKey(): void self::assertTrue($constraint->isPrimaryKey()); } - public function testIsPrimaryKeyReturnsFalseForOtherTypes(): void + public function testIsUniqueReturnsFalseForOtherTypes(): void { $constraint = new ConstraintObject('name', 'table', 'schema'); - // Verify isPrimaryKey returns false for UNIQUE - $constraint->setType('UNIQUE'); - self::assertFalse($constraint->isPrimaryKey()); + // Verify isUnique returns false for PRIMARY KEY + $constraint->setType('PRIMARY KEY'); + self::assertFalse($constraint->isUnique()); - // Verify isPrimaryKey returns false for FOREIGN KEY + // Verify isUnique returns false for FOREIGN KEY $constraint->setType('FOREIGN KEY'); - self::assertFalse($constraint->isPrimaryKey()); + self::assertFalse($constraint->isUnique()); - // Verify isPrimaryKey returns false for CHECK + // Verify isUnique returns false for CHECK $constraint->setType('CHECK'); - self::assertFalse($constraint->isPrimaryKey()); + self::assertFalse($constraint->isUnique()); } public function testIsUniqueReturnsTrueForUnique(): void @@ -215,145 +223,137 @@ public function testIsUniqueReturnsTrueForUnique(): void self::assertTrue($constraint->isUnique()); } - public function testIsUniqueReturnsFalseForOtherTypes(): void + public function testSetCheckClauseAndGetCheckClauseWithFluentInterface(): void { $constraint = new ConstraintObject('name', 'table', 'schema'); - // Verify isUnique returns false for PRIMARY KEY - $constraint->setType('PRIMARY KEY'); - self::assertFalse($constraint->isUnique()); + // Verify fluent interface and value update + $result = $constraint->setCheckClause('age >= 18'); + self::assertSame($constraint, $result); + self::assertSame('age >= 18', $constraint->getCheckClause()); + } - // Verify isUnique returns false for FOREIGN KEY - $constraint->setType('FOREIGN KEY'); - self::assertFalse($constraint->isUnique()); + public function testSetColumnsAndGetColumnsWithFluentInterface(): void + { + $constraint = new ConstraintObject('name', 'table', 'schema'); + $columns = ['column1', 'column2', 'column3']; - // Verify isUnique returns false for CHECK - $constraint->setType('CHECK'); - self::assertFalse($constraint->isUnique()); + // Verify fluent interface and value update + $result = $constraint->setColumns($columns); + self::assertSame($constraint, $result); + self::assertSame($columns, $constraint->getColumns()); } - public function testIsForeignKeyReturnsTrueForForeignKey(): void + public function testSetColumnsWithEmptyArray(): void { $constraint = new ConstraintObject('name', 'table', 'schema'); + $constraint->setColumns(['col1']); - // Verify isForeignKey returns true for FOREIGN KEY type - $constraint->setType('FOREIGN KEY'); - self::assertTrue($constraint->isForeignKey()); + // Set empty array and verify hasColumns reflects change + $constraint->setColumns([]); + self::assertSame([], $constraint->getColumns()); + self::assertFalse($constraint->hasColumns()); } - public function testIsForeignKeyReturnsFalseForOtherTypes(): void + public function testSetDeleteRuleAndGetDeleteRuleWithFluentInterface(): void { $constraint = new ConstraintObject('name', 'table', 'schema'); - // Verify isForeignKey returns false for PRIMARY KEY - $constraint->setType('PRIMARY KEY'); - self::assertFalse($constraint->isForeignKey()); + // Verify fluent interface and value update + $result = $constraint->setDeleteRule('RESTRICT'); + self::assertSame($constraint, $result); + self::assertSame('RESTRICT', $constraint->getDeleteRule()); + } - // Verify isForeignKey returns false for UNIQUE - $constraint->setType('UNIQUE'); - self::assertFalse($constraint->isForeignKey()); + public function testSetMatchOptionAndGetMatchOptionWithFluentInterface(): void + { + $constraint = new ConstraintObject('name', 'table', 'schema'); - // Verify isForeignKey returns false for CHECK - $constraint->setType('CHECK'); - self::assertFalse($constraint->isForeignKey()); + // Verify fluent interface and value update + $result = $constraint->setMatchOption('FULL'); + self::assertSame($constraint, $result); + self::assertSame('FULL', $constraint->getMatchOption()); } - public function testIsCheckReturnsTrueForCheck(): void + public function testSetNameAndGetName(): void + { + $constraint = new ConstraintObject('initial', 'table', 'schema'); + + // Update name and verify change + $constraint->setName('new_name'); + self::assertSame('new_name', $constraint->getName()); + } + + public function testSetReferencedColumnsAndGetReferencedColumnsWithFluentInterface(): void { $constraint = new ConstraintObject('name', 'table', 'schema'); + $columns = ['ref_col1', 'ref_col2']; - // Verify isCheck returns true for CHECK type - $constraint->setType('CHECK'); - self::assertTrue($constraint->isCheck()); + // Verify fluent interface and value update + $result = $constraint->setReferencedColumns($columns); + self::assertSame($constraint, $result); + self::assertSame($columns, $constraint->getReferencedColumns()); } - public function testIsCheckReturnsFalseForOtherTypes(): void + public function testSetReferencedTableNameAndGetReferencedTableNameWithFluentInterface(): void { $constraint = new ConstraintObject('name', 'table', 'schema'); - // Verify isCheck returns false for PRIMARY KEY - $constraint->setType('PRIMARY KEY'); - self::assertFalse($constraint->isCheck()); + // Verify fluent interface and value update + $result = $constraint->setReferencedTableName('ref_table'); + self::assertSame($constraint, $result); + self::assertSame('ref_table', $constraint->getReferencedTableName()); + } - // Verify isCheck returns false for UNIQUE - $constraint->setType('UNIQUE'); - self::assertFalse($constraint->isCheck()); + public function testSetReferencedTableSchemaAndGetReferencedTableSchemaWithFluentInterface(): void + { + $constraint = new ConstraintObject('name', 'table', 'schema'); - // Verify isCheck returns false for FOREIGN KEY - $constraint->setType('FOREIGN KEY'); - self::assertFalse($constraint->isCheck()); + // Verify fluent interface and value update + $result = $constraint->setReferencedTableSchema('ref_schema'); + self::assertSame($constraint, $result); + self::assertSame('ref_schema', $constraint->getReferencedTableSchema()); } - public function testCompletePrimaryKeyConstraint(): void + public function testSetSchemaNameAndGetSchemaName(): void { - $constraint = new ConstraintObject('pk_users', 'users', 'public'); - $constraint->setType('PRIMARY KEY'); - $constraint->setColumns(['id']); + $constraint = new ConstraintObject('name', 'table', 'initial_schema'); - // Verify primary key constraint is configured correctly - self::assertSame('pk_users', $constraint->getName()); - self::assertSame('users', $constraint->getTableName()); - self::assertSame('public', $constraint->getSchemaName()); - self::assertTrue($constraint->isPrimaryKey()); - self::assertFalse($constraint->isUnique()); - self::assertFalse($constraint->isForeignKey()); - self::assertFalse($constraint->isCheck()); - self::assertSame(['id'], $constraint->getColumns()); - self::assertTrue($constraint->hasColumns()); + // Update schema and verify change + $constraint->setSchemaName('new_schema'); + self::assertSame('new_schema', $constraint->getSchemaName()); } - public function testCompleteForeignKeyConstraint(): void + public function testSetTableNameAndGetTableNameWithFluentInterface(): void { - $constraint = new ConstraintObject('fk_orders_user', 'orders', 'public'); - $constraint->setType('FOREIGN KEY'); - $constraint->setColumns(['user_id']) - ->setReferencedTableSchema('public') - ->setReferencedTableName('users') - ->setReferencedColumns(['id']) - ->setMatchOption('SIMPLE') - ->setUpdateRule('CASCADE') - ->setDeleteRule('RESTRICT'); + $constraint = new ConstraintObject('name', 'initial_table', 'schema'); - // Verify foreign key constraint is configured correctly - self::assertSame('fk_orders_user', $constraint->getName()); - self::assertTrue($constraint->isForeignKey()); - self::assertFalse($constraint->isPrimaryKey()); - self::assertFalse($constraint->isUnique()); - self::assertFalse($constraint->isCheck()); - self::assertSame(['user_id'], $constraint->getColumns()); - self::assertSame('public', $constraint->getReferencedTableSchema()); - self::assertSame('users', $constraint->getReferencedTableName()); - self::assertSame(['id'], $constraint->getReferencedColumns()); - self::assertSame('SIMPLE', $constraint->getMatchOption()); - self::assertSame('CASCADE', $constraint->getUpdateRule()); - self::assertSame('RESTRICT', $constraint->getDeleteRule()); + // Verify fluent interface and value update + $result = $constraint->setTableName('new_table'); + self::assertSame($constraint, $result); + self::assertSame('new_table', $constraint->getTableName()); } - public function testCompleteUniqueConstraint(): void + public function testSetTypeAndGetType(): void { - $constraint = new ConstraintObject('uq_users_email', 'users', 'public'); - $constraint->setType('UNIQUE'); - $constraint->setColumns(['email']); + $constraint = new ConstraintObject('name', 'table', 'schema'); - // Verify unique constraint is configured correctly - self::assertTrue($constraint->isUnique()); - self::assertFalse($constraint->isPrimaryKey()); - self::assertFalse($constraint->isForeignKey()); - self::assertFalse($constraint->isCheck()); - self::assertSame(['email'], $constraint->getColumns()); + // Set type and verify retrieval + $constraint->setType('PRIMARY KEY'); + self::assertSame('PRIMARY KEY', $constraint->getType()); + + // Verify mutation to different type + $constraint->setType('UNIQUE'); + self::assertSame('UNIQUE', $constraint->getType()); } - public function testCompleteCheckConstraint(): void + public function testSetUpdateRuleAndGetUpdateRuleWithFluentInterface(): void { - $constraint = new ConstraintObject('chk_users_age', 'users', 'public'); - $constraint->setType('CHECK'); - $constraint->setCheckClause('age >= 18 AND age <= 120'); + $constraint = new ConstraintObject('name', 'table', 'schema'); - // Verify check constraint is configured correctly - self::assertTrue($constraint->isCheck()); - self::assertFalse($constraint->isPrimaryKey()); - self::assertFalse($constraint->isUnique()); - self::assertFalse($constraint->isForeignKey()); - self::assertSame('age >= 18 AND age <= 120', $constraint->getCheckClause()); + // Verify fluent interface and value update + $result = $constraint->setUpdateRule('CASCADE'); + self::assertSame($constraint, $result); + self::assertSame('CASCADE', $constraint->getUpdateRule()); } } diff --git a/test/unit/Metadata/Object/TableObjectTest.php b/test/unit/Metadata/Object/TableObjectTest.php index a5f6b2a01..76ec3a6db 100644 --- a/test/unit/Metadata/Object/TableObjectTest.php +++ b/test/unit/Metadata/Object/TableObjectTest.php @@ -12,11 +12,38 @@ final class TableObjectTest extends TestCase { - public function testExtendsAbstractTableObject(): void + public function testCanBeInstantiated(): void { - $table = new TableObject('table_name'); + $table = new TableObject('test_table'); - // Verify table extends AbstractTableObject + // Verify object can be instantiated directly + self::assertInstanceOf(TableObject::class, $table); + self::assertSame('test_table', $table->getName()); + } + + public function testCompleteTableObjectWithAllInheritedFunctionality(): void + { + $table = new TableObject('orders'); + + $columns = [ + new ColumnObject('id', 'orders', 'public'), + new ColumnObject('user_id', 'orders', 'public'), + new ColumnObject('total', 'orders', 'public'), + new ColumnObject('created_at', 'orders', 'public'), + ]; + + $constraints = [ + new ConstraintObject('pk_orders', 'orders', 'public'), + new ConstraintObject('fk_orders_user', 'orders', 'public'), + ]; + + $table->setColumns($columns); + $table->setConstraints($constraints); + + // Verify all inherited functionality works correctly + self::assertSame('orders', $table->getName()); + self::assertCount(4, $table->getColumns()); + self::assertCount(2, $table->getConstraints()); self::assertInstanceOf(AbstractTableObject::class, $table); } @@ -36,13 +63,12 @@ public function testConstructorWithNullName(): void self::assertNull($table->getName()); } - public function testInheritedSetNameWorks(): void + public function testExtendsAbstractTableObject(): void { - $table = new TableObject('initial'); + $table = new TableObject('table_name'); - // Verify inherited setName method updates the name - $table->setName('updated'); - self::assertSame('updated', $table->getName()); + // Verify table extends AbstractTableObject + self::assertInstanceOf(AbstractTableObject::class, $table); } public function testInheritedSetColumnsWorks(): void @@ -72,38 +98,12 @@ public function testInheritedSetConstraintsWorks(): void self::assertCount(1, $table->getConstraints()); } - public function testCompleteTableObjectWithAllInheritedFunctionality(): void - { - $table = new TableObject('orders'); - - $columns = [ - new ColumnObject('id', 'orders', 'public'), - new ColumnObject('user_id', 'orders', 'public'), - new ColumnObject('total', 'orders', 'public'), - new ColumnObject('created_at', 'orders', 'public'), - ]; - - $constraints = [ - new ConstraintObject('pk_orders', 'orders', 'public'), - new ConstraintObject('fk_orders_user', 'orders', 'public'), - ]; - - $table->setColumns($columns); - $table->setConstraints($constraints); - - // Verify all inherited functionality works correctly - self::assertSame('orders', $table->getName()); - self::assertCount(4, $table->getColumns()); - self::assertCount(2, $table->getConstraints()); - self::assertInstanceOf(AbstractTableObject::class, $table); - } - - public function testCanBeInstantiated(): void + public function testInheritedSetNameWorks(): void { - $table = new TableObject('test_table'); + $table = new TableObject('initial'); - // Verify object can be instantiated directly - self::assertInstanceOf(TableObject::class, $table); - self::assertSame('test_table', $table->getName()); + // Verify inherited setName method updates the name + $table->setName('updated'); + self::assertSame('updated', $table->getName()); } } diff --git a/test/unit/Metadata/Object/TestAsset/ConcreteTableObject.php b/test/unit/Metadata/Object/TestAsset/ConcreteTableObject.php index 6641cc195..4e01d6211 100644 --- a/test/unit/Metadata/Object/TestAsset/ConcreteTableObject.php +++ b/test/unit/Metadata/Object/TestAsset/ConcreteTableObject.php @@ -6,6 +6,4 @@ use PhpDb\Metadata\Object\AbstractTableObject; -class ConcreteTableObject extends AbstractTableObject -{ -} +class ConcreteTableObject extends AbstractTableObject {} diff --git a/test/unit/Metadata/Object/TriggerObjectTest.php b/test/unit/Metadata/Object/TriggerObjectTest.php index fbbd6b6e0..888110ed9 100644 --- a/test/unit/Metadata/Object/TriggerObjectTest.php +++ b/test/unit/Metadata/Object/TriggerObjectTest.php @@ -10,58 +10,75 @@ final class TriggerObjectTest extends TestCase { - public function testSetNameAndGetNameWithFluentInterface(): void - { - $trigger = new TriggerObject(); - - // Verify fluent interface and value update - $result = $trigger->setName('trigger_name'); - self::assertSame($trigger, $result); - self::assertSame('trigger_name', $trigger->getName()); - - // Verify mutation with different name - $trigger->setName('updated_trigger'); - self::assertSame('updated_trigger', $trigger->getName()); - } - - public function testSetEventManipulationAndGetEventManipulationWithFluentInterface(): void + public function testCompleteTriggerObject(): void { $trigger = new TriggerObject(); + $created = new DateTime('2025-11-13 10:30:00'); - // Verify fluent interface and value update - $result = $trigger->setEventManipulation('INSERT'); - self::assertSame($trigger, $result); - self::assertSame('INSERT', $trigger->getEventManipulation()); - } - - public function testSetEventObjectCatalogAndGetEventObjectCatalogWithFluentInterface(): void - { - $trigger = new TriggerObject(); + $trigger->setName('audit_trigger') + ->setEventManipulation('UPDATE') + ->setEventObjectCatalog('main_catalog') + ->setEventObjectSchema('public') + ->setEventObjectTable('orders') + ->setActionOrder('1') + ->setActionCondition('WHEN (OLD.status != NEW.status)') + ->setActionStatement('BEGIN INSERT INTO audit_log VALUES (OLD.id, NOW()); END') + ->setActionOrientation('ROW') + ->setActionTiming('AFTER') + ->setActionReferenceOldTable('old_orders') + ->setActionReferenceNewTable('new_orders') + ->setActionReferenceOldRow('OLD') + ->setActionReferenceNewRow('NEW') + ->setCreated($created); - // Verify fluent interface and value update - $result = $trigger->setEventObjectCatalog('catalog_name'); - self::assertSame($trigger, $result); - self::assertSame('catalog_name', $trigger->getEventObjectCatalog()); + // Verify all properties are set correctly + self::assertSame('audit_trigger', $trigger->getName()); + self::assertSame('UPDATE', $trigger->getEventManipulation()); + self::assertSame('main_catalog', $trigger->getEventObjectCatalog()); + self::assertSame('public', $trigger->getEventObjectSchema()); + self::assertSame('orders', $trigger->getEventObjectTable()); + self::assertSame('1', $trigger->getActionOrder()); + self::assertSame('WHEN (OLD.status != NEW.status)', $trigger->getActionCondition()); + self::assertSame('BEGIN INSERT INTO audit_log VALUES (OLD.id, NOW()); END', $trigger->getActionStatement()); + self::assertSame('ROW', $trigger->getActionOrientation()); + self::assertSame('AFTER', $trigger->getActionTiming()); + self::assertSame('old_orders', $trigger->getActionReferenceOldTable()); + self::assertSame('new_orders', $trigger->getActionReferenceNewTable()); + self::assertSame('OLD', $trigger->getActionReferenceOldRow()); + self::assertSame('NEW', $trigger->getActionReferenceNewRow()); + self::assertSame($created, $trigger->getCreated()); } - public function testSetEventObjectSchemaAndGetEventObjectSchemaWithFluentInterface(): void + public function testNullValuesForAllProperties(): void { $trigger = new TriggerObject(); - // Verify fluent interface and value update - $result = $trigger->setEventObjectSchema('schema_name'); - self::assertSame($trigger, $result); - self::assertSame('schema_name', $trigger->getEventObjectSchema()); + // Verify all properties default to null + self::assertNull($trigger->getName()); + self::assertNull($trigger->getEventManipulation()); + self::assertNull($trigger->getEventObjectCatalog()); + self::assertNull($trigger->getEventObjectSchema()); + self::assertNull($trigger->getEventObjectTable()); + self::assertNull($trigger->getActionOrder()); + self::assertNull($trigger->getActionCondition()); + self::assertNull($trigger->getActionStatement()); + self::assertNull($trigger->getActionOrientation()); + self::assertNull($trigger->getActionTiming()); + self::assertNull($trigger->getActionReferenceOldTable()); + self::assertNull($trigger->getActionReferenceNewTable()); + self::assertNull($trigger->getActionReferenceOldRow()); + self::assertNull($trigger->getActionReferenceNewRow()); + self::assertNull($trigger->getCreated()); } - public function testSetEventObjectTableAndGetEventObjectTableWithFluentInterface(): void + public function testSetActionConditionAndGetActionConditionWithFluentInterface(): void { $trigger = new TriggerObject(); // Verify fluent interface and value update - $result = $trigger->setEventObjectTable('table_name'); + $result = $trigger->setActionCondition('WHEN (NEW.amount > 100)'); self::assertSame($trigger, $result); - self::assertSame('table_name', $trigger->getEventObjectTable()); + self::assertSame('WHEN (NEW.amount > 100)', $trigger->getActionCondition()); } public function testSetActionOrderAndGetActionOrderWithFluentInterface(): void @@ -74,44 +91,44 @@ public function testSetActionOrderAndGetActionOrderWithFluentInterface(): void self::assertSame('1', $trigger->getActionOrder()); } - public function testSetActionConditionAndGetActionConditionWithFluentInterface(): void + public function testSetActionOrientationAndGetActionOrientationWithFluentInterface(): void { $trigger = new TriggerObject(); // Verify fluent interface and value update - $result = $trigger->setActionCondition('WHEN (NEW.amount > 100)'); + $result = $trigger->setActionOrientation('ROW'); self::assertSame($trigger, $result); - self::assertSame('WHEN (NEW.amount > 100)', $trigger->getActionCondition()); + self::assertSame('ROW', $trigger->getActionOrientation()); } - public function testSetActionStatementAndGetActionStatementWithFluentInterface(): void + public function testSetActionReferenceNewRowAndGetActionReferenceNewRowWithFluentInterface(): void { $trigger = new TriggerObject(); // Verify fluent interface and value update - $result = $trigger->setActionStatement('BEGIN ... END'); + $result = $trigger->setActionReferenceNewRow('NEW'); self::assertSame($trigger, $result); - self::assertSame('BEGIN ... END', $trigger->getActionStatement()); + self::assertSame('NEW', $trigger->getActionReferenceNewRow()); } - public function testSetActionOrientationAndGetActionOrientationWithFluentInterface(): void + public function testSetActionReferenceNewTableAndGetActionReferenceNewTableWithFluentInterface(): void { $trigger = new TriggerObject(); // Verify fluent interface and value update - $result = $trigger->setActionOrientation('ROW'); + $result = $trigger->setActionReferenceNewTable('new_table'); self::assertSame($trigger, $result); - self::assertSame('ROW', $trigger->getActionOrientation()); + self::assertSame('new_table', $trigger->getActionReferenceNewTable()); } - public function testSetActionTimingAndGetActionTimingWithFluentInterface(): void + public function testSetActionReferenceOldRowAndGetActionReferenceOldRowWithFluentInterface(): void { $trigger = new TriggerObject(); // Verify fluent interface and value update - $result = $trigger->setActionTiming('BEFORE'); + $result = $trigger->setActionReferenceOldRow('OLD'); self::assertSame($trigger, $result); - self::assertSame('BEFORE', $trigger->getActionTiming()); + self::assertSame('OLD', $trigger->getActionReferenceOldRow()); } public function testSetActionReferenceOldTableAndGetActionReferenceOldTableWithFluentInterface(): void @@ -124,34 +141,24 @@ public function testSetActionReferenceOldTableAndGetActionReferenceOldTableWithF self::assertSame('old_table', $trigger->getActionReferenceOldTable()); } - public function testSetActionReferenceNewTableAndGetActionReferenceNewTableWithFluentInterface(): void - { - $trigger = new TriggerObject(); - - // Verify fluent interface and value update - $result = $trigger->setActionReferenceNewTable('new_table'); - self::assertSame($trigger, $result); - self::assertSame('new_table', $trigger->getActionReferenceNewTable()); - } - - public function testSetActionReferenceOldRowAndGetActionReferenceOldRowWithFluentInterface(): void + public function testSetActionStatementAndGetActionStatementWithFluentInterface(): void { $trigger = new TriggerObject(); // Verify fluent interface and value update - $result = $trigger->setActionReferenceOldRow('OLD'); + $result = $trigger->setActionStatement('BEGIN ... END'); self::assertSame($trigger, $result); - self::assertSame('OLD', $trigger->getActionReferenceOldRow()); + self::assertSame('BEGIN ... END', $trigger->getActionStatement()); } - public function testSetActionReferenceNewRowAndGetActionReferenceNewRowWithFluentInterface(): void + public function testSetActionTimingAndGetActionTimingWithFluentInterface(): void { $trigger = new TriggerObject(); // Verify fluent interface and value update - $result = $trigger->setActionReferenceNewRow('NEW'); + $result = $trigger->setActionTiming('BEFORE'); self::assertSame($trigger, $result); - self::assertSame('NEW', $trigger->getActionReferenceNewRow()); + self::assertSame('BEFORE', $trigger->getActionTiming()); } public function testSetCreatedAndGetCreatedWithFluentInterface(): void @@ -180,64 +187,57 @@ public function testSetCreatedWithDifferentDateTime(): void self::assertSame($dateTime2, $trigger->getCreated()); } - public function testNullValuesForAllProperties(): void + public function testSetEventManipulationAndGetEventManipulationWithFluentInterface(): void { $trigger = new TriggerObject(); - // Verify all properties default to null - self::assertNull($trigger->getName()); - self::assertNull($trigger->getEventManipulation()); - self::assertNull($trigger->getEventObjectCatalog()); - self::assertNull($trigger->getEventObjectSchema()); - self::assertNull($trigger->getEventObjectTable()); - self::assertNull($trigger->getActionOrder()); - self::assertNull($trigger->getActionCondition()); - self::assertNull($trigger->getActionStatement()); - self::assertNull($trigger->getActionOrientation()); - self::assertNull($trigger->getActionTiming()); - self::assertNull($trigger->getActionReferenceOldTable()); - self::assertNull($trigger->getActionReferenceNewTable()); - self::assertNull($trigger->getActionReferenceOldRow()); - self::assertNull($trigger->getActionReferenceNewRow()); - self::assertNull($trigger->getCreated()); + // Verify fluent interface and value update + $result = $trigger->setEventManipulation('INSERT'); + self::assertSame($trigger, $result); + self::assertSame('INSERT', $trigger->getEventManipulation()); } - public function testCompleteTriggerObject(): void + public function testSetEventObjectCatalogAndGetEventObjectCatalogWithFluentInterface(): void { $trigger = new TriggerObject(); - $created = new DateTime('2025-11-13 10:30:00'); - $trigger->setName('audit_trigger') - ->setEventManipulation('UPDATE') - ->setEventObjectCatalog('main_catalog') - ->setEventObjectSchema('public') - ->setEventObjectTable('orders') - ->setActionOrder('1') - ->setActionCondition('WHEN (OLD.status != NEW.status)') - ->setActionStatement('BEGIN INSERT INTO audit_log VALUES (OLD.id, NOW()); END') - ->setActionOrientation('ROW') - ->setActionTiming('AFTER') - ->setActionReferenceOldTable('old_orders') - ->setActionReferenceNewTable('new_orders') - ->setActionReferenceOldRow('OLD') - ->setActionReferenceNewRow('NEW') - ->setCreated($created); + // Verify fluent interface and value update + $result = $trigger->setEventObjectCatalog('catalog_name'); + self::assertSame($trigger, $result); + self::assertSame('catalog_name', $trigger->getEventObjectCatalog()); + } - // Verify all properties are set correctly - self::assertSame('audit_trigger', $trigger->getName()); - self::assertSame('UPDATE', $trigger->getEventManipulation()); - self::assertSame('main_catalog', $trigger->getEventObjectCatalog()); - self::assertSame('public', $trigger->getEventObjectSchema()); - self::assertSame('orders', $trigger->getEventObjectTable()); - self::assertSame('1', $trigger->getActionOrder()); - self::assertSame('WHEN (OLD.status != NEW.status)', $trigger->getActionCondition()); - self::assertSame('BEGIN INSERT INTO audit_log VALUES (OLD.id, NOW()); END', $trigger->getActionStatement()); - self::assertSame('ROW', $trigger->getActionOrientation()); - self::assertSame('AFTER', $trigger->getActionTiming()); - self::assertSame('old_orders', $trigger->getActionReferenceOldTable()); - self::assertSame('new_orders', $trigger->getActionReferenceNewTable()); - self::assertSame('OLD', $trigger->getActionReferenceOldRow()); - self::assertSame('NEW', $trigger->getActionReferenceNewRow()); - self::assertSame($created, $trigger->getCreated()); + public function testSetEventObjectSchemaAndGetEventObjectSchemaWithFluentInterface(): void + { + $trigger = new TriggerObject(); + + // Verify fluent interface and value update + $result = $trigger->setEventObjectSchema('schema_name'); + self::assertSame($trigger, $result); + self::assertSame('schema_name', $trigger->getEventObjectSchema()); + } + + public function testSetEventObjectTableAndGetEventObjectTableWithFluentInterface(): void + { + $trigger = new TriggerObject(); + + // Verify fluent interface and value update + $result = $trigger->setEventObjectTable('table_name'); + self::assertSame($trigger, $result); + self::assertSame('table_name', $trigger->getEventObjectTable()); + } + + public function testSetNameAndGetNameWithFluentInterface(): void + { + $trigger = new TriggerObject(); + + // Verify fluent interface and value update + $result = $trigger->setName('trigger_name'); + self::assertSame($trigger, $result); + self::assertSame('trigger_name', $trigger->getName()); + + // Verify mutation with different name + $trigger->setName('updated_trigger'); + self::assertSame('updated_trigger', $trigger->getName()); } } diff --git a/test/unit/Metadata/Object/ViewObjectTest.php b/test/unit/Metadata/Object/ViewObjectTest.php index 84dd55121..e00fc531b 100644 --- a/test/unit/Metadata/Object/ViewObjectTest.php +++ b/test/unit/Metadata/Object/ViewObjectTest.php @@ -12,12 +12,29 @@ final class ViewObjectTest extends TestCase { - public function testExtendsAbstractTableObject(): void + public function testCompleteViewObjectWithAllProperties(): void { - $view = new ViewObject('view_name'); + $view = new ViewObject('active_users'); - // Verify view extends AbstractTableObject - self::assertInstanceOf(AbstractTableObject::class, $view); + $definition = "SELECT id, username, email FROM users WHERE status = 'active'"; + $view->setViewDefinition($definition) + ->setCheckOption('CASCADED') + ->setIsUpdatable(false); + + $columns = [ + new ColumnObject('id', 'active_users', 'public'), + new ColumnObject('username', 'active_users', 'public'), + new ColumnObject('email', 'active_users', 'public'), + ]; + $view->setColumns($columns); + + // Verify all properties are set correctly + self::assertSame('active_users', $view->getName()); + self::assertSame($definition, $view->getViewDefinition()); + self::assertSame('CASCADED', $view->getCheckOption()); + self::assertFalse($view->isUpdatable()); + self::assertFalse($view->getIsUpdatable()); + self::assertCount(3, $view->getColumns()); } public function testConstructorWithName(): void @@ -36,25 +53,64 @@ public function testConstructorWithNullName(): void self::assertNull($view->getName()); } - public function testSetViewDefinitionAndGetViewDefinitionWithFluentInterface(): void + public function testExtendsAbstractTableObject(): void { - $view = new ViewObject('view'); - $definition = 'SELECT id, name FROM users WHERE active = 1'; + $view = new ViewObject('view_name'); - // Verify fluent interface and value update - $result = $view->setViewDefinition($definition); - self::assertSame($view, $result); - self::assertSame($definition, $view->getViewDefinition()); + // Verify view extends AbstractTableObject + self::assertInstanceOf(AbstractTableObject::class, $view); } - public function testSetViewDefinitionWithNull(): void + public function testInheritedColumnsWork(): void + { + $view = new ViewObject('user_summary'); + $columns = [ + new ColumnObject('id', 'user_summary', 'public'), + new ColumnObject('username', 'user_summary', 'public'), + ]; + + // Verify inherited setColumns method stores columns + $view->setColumns($columns); + self::assertSame($columns, $view->getColumns()); + self::assertCount(2, $view->getColumns()); + } + + public function testInheritedConstraintsWork(): void + { + $view = new ViewObject('user_summary'); + $constraints = [ + new ConstraintObject('uq_summary', 'user_summary', 'public'), + ]; + + // Verify inherited setConstraints method stores constraints + $view->setConstraints($constraints); + self::assertSame($constraints, $view->getConstraints()); + self::assertCount(1, $view->getConstraints()); + } + + public function testIsUpdatableAlias(): void { $view = new ViewObject('view'); - $view->setViewDefinition('SELECT * FROM table'); - // Set definition to null and verify - $view->setViewDefinition(null); - self::assertNull($view->getViewDefinition()); + // Verify alias returns same value when true + $view->setIsUpdatable(true); + self::assertTrue($view->isUpdatable()); + self::assertSame($view->getIsUpdatable(), $view->isUpdatable()); + + // Verify alias returns same value when false + $view->setIsUpdatable(false); + self::assertFalse($view->isUpdatable()); + self::assertSame($view->getIsUpdatable(), $view->isUpdatable()); + } + + public function testIsUpdatableAliasWithNull(): void + { + $view = new ViewObject('view'); + + // Verify alias returns same value when null + $view->setIsUpdatable(null); + self::assertNull($view->isUpdatable()); + self::assertSame($view->getIsUpdatable(), $view->isUpdatable()); } public function testSetCheckOptionAndGetCheckOptionWithFluentInterface(): void @@ -106,81 +162,34 @@ public function testSetIsUpdatableWithNull(): void self::assertNull($view->getIsUpdatable()); } - public function testIsUpdatableAlias(): void + public function testSetViewDefinitionAndGetViewDefinitionWithFluentInterface(): void { - $view = new ViewObject('view'); - - // Verify alias returns same value when true - $view->setIsUpdatable(true); - self::assertTrue($view->isUpdatable()); - self::assertSame($view->getIsUpdatable(), $view->isUpdatable()); + $view = new ViewObject('view'); + $definition = 'SELECT id, name FROM users WHERE active = 1'; - // Verify alias returns same value when false - $view->setIsUpdatable(false); - self::assertFalse($view->isUpdatable()); - self::assertSame($view->getIsUpdatable(), $view->isUpdatable()); + // Verify fluent interface and value update + $result = $view->setViewDefinition($definition); + self::assertSame($view, $result); + self::assertSame($definition, $view->getViewDefinition()); } - public function testIsUpdatableAliasWithNull(): void + public function testSetViewDefinitionWithNull(): void { $view = new ViewObject('view'); + $view->setViewDefinition('SELECT * FROM table'); - // Verify alias returns same value when null - $view->setIsUpdatable(null); - self::assertNull($view->isUpdatable()); - self::assertSame($view->getIsUpdatable(), $view->isUpdatable()); - } - - public function testInheritedColumnsWork(): void - { - $view = new ViewObject('user_summary'); - $columns = [ - new ColumnObject('id', 'user_summary', 'public'), - new ColumnObject('username', 'user_summary', 'public'), - ]; - - // Verify inherited setColumns method stores columns - $view->setColumns($columns); - self::assertSame($columns, $view->getColumns()); - self::assertCount(2, $view->getColumns()); - } - - public function testInheritedConstraintsWork(): void - { - $view = new ViewObject('user_summary'); - $constraints = [ - new ConstraintObject('uq_summary', 'user_summary', 'public'), - ]; - - // Verify inherited setConstraints method stores constraints - $view->setConstraints($constraints); - self::assertSame($constraints, $view->getConstraints()); - self::assertCount(1, $view->getConstraints()); + // Set definition to null and verify + $view->setViewDefinition(null); + self::assertNull($view->getViewDefinition()); } - public function testCompleteViewObjectWithAllProperties(): void + public function testViewObjectWithInheritedSetName(): void { - $view = new ViewObject('active_users'); - - $definition = "SELECT id, username, email FROM users WHERE status = 'active'"; - $view->setViewDefinition($definition) - ->setCheckOption('CASCADED') - ->setIsUpdatable(false); - - $columns = [ - new ColumnObject('id', 'active_users', 'public'), - new ColumnObject('username', 'active_users', 'public'), - new ColumnObject('email', 'active_users', 'public'), - ]; - $view->setColumns($columns); + $view = new ViewObject('initial_view'); - // Verify all properties are set correctly - self::assertSame('active_users', $view->getName()); - self::assertSame($definition, $view->getViewDefinition()); - self::assertSame('CASCADED', $view->getCheckOption()); - self::assertFalse($view->isUpdatable()); - self::assertFalse($view->getIsUpdatable()); - self::assertCount(3, $view->getColumns()); + // Verify inherited setName method updates the name + $view->setName('renamed_view'); + self::assertSame('renamed_view', $view->getName()); } public function testViewObjectWithNullProperties(): void @@ -193,13 +202,4 @@ public function testViewObjectWithNullProperties(): void self::assertNull($view->getIsUpdatable()); self::assertNull($view->isUpdatable()); } - - public function testViewObjectWithInheritedSetName(): void - { - $view = new ViewObject('initial_view'); - - // Verify inherited setName method updates the name - $view->setName('renamed_view'); - self::assertSame('renamed_view', $view->getName()); - } } diff --git a/test/unit/Metadata/Source/AbstractSourceTest.php b/test/unit/Metadata/Source/AbstractSourceTest.php index e7ce86656..23ffd5d81 100644 --- a/test/unit/Metadata/Source/AbstractSourceTest.php +++ b/test/unit/Metadata/Source/AbstractSourceTest.php @@ -55,53 +55,13 @@ #[Group('unit')] final class AbstractSourceTest extends TestCase { - protected MockObject|AbstractSource $abstractSourceMock; - - protected MockObject|AdapterInterface $adapterMock; - - #[Override] - protected function setUp(): void - { - /** @var AdapterInterface&SchemaAwareInterface&MockObject $adapterMock */ - $adapterMock = $this->createMockForIntersectionOfInterfaces([ - AdapterInterface::class, - SchemaAwareInterface::class, - ]); - $this->adapterMock = $adapterMock; - - $this->abstractSourceMock = $this->getMockBuilder(AbstractSource::class) - ->setConstructorArgs([$this->adapterMock]) - ->onlyMethods([ - 'loadSchemaData', - ]) - ->getMock(); - } - - /** - * @throws ReflectionException - */ - private function setMockData(array $data): void - { - $refProp = new ReflectionProperty($this->abstractSourceMock, 'data'); - $refProp->setValue($this->abstractSourceMock, $data); - } - - /** - * @throws ReflectionException - */ - private function getMockData(): array - { - $refProp = new ReflectionProperty($this->abstractSourceMock, 'data'); - return $refProp->getValue($this->abstractSourceMock); - } - /** * @throws ReflectionException */ - public function testConstructorWithSchemaFromAdapter(): void + public function testConstructorWithNullSchemaUsesDefaultConstant(): void { $adapter = $this->createMockForIntersectionOfInterfaces([AdapterInterface::class, SchemaAwareInterface::class]); - $adapter->method('getCurrentSchema')->willReturn('my_schema'); + $adapter->method('getCurrentSchema')->willReturn(false); $source = $this->getMockBuilder(AbstractSource::class) ->setConstructorArgs([$adapter]) @@ -110,17 +70,17 @@ public function testConstructorWithSchemaFromAdapter(): void $refProp = new ReflectionProperty($source, 'defaultSchema'); - // Verify schema is retrieved from adapter - self::assertSame('my_schema', $refProp->getValue($source)); + // Verify default constant is used when adapter returns false + self::assertSame(AbstractSource::DEFAULT_SCHEMA, $refProp->getValue($source)); } /** * @throws ReflectionException */ - public function testConstructorWithNullSchemaUsesDefaultConstant(): void + public function testConstructorWithSchemaFromAdapter(): void { $adapter = $this->createMockForIntersectionOfInterfaces([AdapterInterface::class, SchemaAwareInterface::class]); - $adapter->method('getCurrentSchema')->willReturn(false); + $adapter->method('getCurrentSchema')->willReturn('my_schema'); $source = $this->getMockBuilder(AbstractSource::class) ->setConstructorArgs([$adapter]) @@ -129,814 +89,881 @@ public function testConstructorWithNullSchemaUsesDefaultConstant(): void $refProp = new ReflectionProperty($source, 'defaultSchema'); - // Verify default constant is used when adapter returns false - self::assertSame(AbstractSource::DEFAULT_SCHEMA, $refProp->getValue($source)); + // Verify schema is retrieved from adapter + self::assertSame('my_schema', $refProp->getValue($source)); } /** - * Schema Methods - * * @throws ReflectionException + * @throws Exception */ - public function testGetSchemasCallsLoadSchemaData(): void + public function testGetColumn(): void { - $this->abstractSourceMock->expects($this->once()) - ->method('loadSchemaData'); + $this->setMockData([ + 'columns' => [ + 'public' => [ + 'users' => [ + 'username' => [ + 'ordinal_position' => 2, + 'column_default' => '', + 'is_nullable' => false, + 'data_type' => 'VARCHAR', + 'character_maximum_length' => 255, + 'character_octet_length' => 1024, + 'numeric_precision' => null, + 'numeric_scale' => null, + 'numeric_unsigned' => null, + 'erratas' => ['collation' => 'utf8_general_ci'], + ], + ], + ], + ], + ]); - $this->setMockData(['schemas' => ['schema1', 'schema2']]); + $column = $this->abstractSourceMock->getColumn('username', 'users', 'public'); + // Verify getColumn returns ColumnObject with all properties - // Verify getSchemas loads and returns schema list - $schemas = $this->abstractSourceMock->getSchemas(); - self::assertSame(['schema1', 'schema2'], $schemas); + self::assertInstanceOf(ColumnObject::class, $column); + self::assertSame('username', $column->getName()); + self::assertSame('users', $column->getTableName()); + self::assertSame('public', $column->getSchemaName()); + self::assertSame(2, $column->getOrdinalPosition()); + self::assertSame('', $column->getColumnDefault()); + self::assertFalse($column->getIsNullable()); + self::assertSame('VARCHAR', $column->getDataType()); + self::assertSame(255, $column->getCharacterMaximumLength()); + self::assertSame(1024, $column->getCharacterOctetLength()); + self::assertNull($column->getNumericPrecision()); + self::assertNull($column->getNumericScale()); + self::assertNull($column->getNumericUnsigned()); + self::assertSame('utf8_general_ci', $column->getErrata('collation')); } /** - * Table Name Methods + * Column Methods * * @throws ReflectionException - * @throws ReflectionException + * @throws Exception */ - public function testGetTableNamesWithNullSchemaUsesDefault(): void + public function testGetColumnNames(): void { - $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); - $refProp->setValue($this->abstractSourceMock, 'default_schema'); - $this->setMockData([ - 'table_names' => [ - 'default_schema' => [ - 'users' => ['table_type' => 'BASE TABLE'], - 'orders' => ['table_type' => 'BASE TABLE'], + 'columns' => [ + 'public' => [ + 'users' => [ + 'id' => [], + 'username' => [], + 'email' => [], + ], ], ], ]); - // Verify default schema is used when none provided - $tableNames = $this->abstractSourceMock->getTableNames(); + $columnNames = $this->abstractSourceMock->getColumnNames('users', 'public'); + // Verify getColumnNames returns array of column names - self::assertSame(['users', 'orders'], $tableNames); + self::assertSame(['id', 'username', 'email'], $columnNames); + } + + public function testGetColumnNamesThrowsWhenLoadColumnDataDoesNotPopulate(): void + { + $adapter = $this->createMockForIntersectionOfInterfaces([ + AdapterInterface::class, + SchemaAwareInterface::class, + ]); + $adapter->method('getCurrentSchema')->willReturn('public'); + + $source = new IncompleteSource($adapter); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('"nonexistent" does not exist'); + $source->getColumnNames('nonexistent', 'public'); } /** * @throws ReflectionException */ - public function testGetTableNamesWithSpecificSchema(): void + public function testGetColumnNamesUsesDefaultSchemaWhenNull(): void { + $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); + $refProp->setValue($this->abstractSourceMock, 'def'); + $this->setMockData([ - 'table_names' => [ - 'public' => [ - 'products' => ['table_type' => 'BASE TABLE'], + 'columns' => [ + 'def' => [ + 'users' => [ + 'id' => [], + 'name' => [], + ], ], ], ]); - // Verify table names for specific schema - $tableNames = $this->abstractSourceMock->getTableNames('public'); + $names = $this->abstractSourceMock->getColumnNames('users', null); - self::assertSame(['products'], $tableNames); + self::assertSame(['id', 'name'], $names); } /** * @throws ReflectionException */ - public function testGetTableNamesExcludesViewsByDefault(): void + public function testGetColumns(): void { $this->setMockData([ - 'table_names' => [ + 'columns' => [ 'public' => [ - 'users' => ['table_type' => 'BASE TABLE'], - 'user_summary' => ['table_type' => 'VIEW'], - 'orders' => ['table_type' => 'BASE TABLE'], + 'users' => [ + 'id' => [ + 'ordinal_position' => 1, + 'column_default' => null, + 'is_nullable' => false, + 'data_type' => 'INT', + 'character_maximum_length' => null, + 'character_octet_length' => null, + 'numeric_precision' => 10, + 'numeric_scale' => 0, + 'numeric_unsigned' => true, + 'erratas' => [], + ], + ], ], ], ]); - // Verify views are excluded by default - $tableNames = $this->abstractSourceMock->getTableNames('public'); + $columns = $this->abstractSourceMock->getColumns('users', 'public'); + // Verify getColumns returns array of ColumnObject instances - self::assertSame(['users', 'orders'], $tableNames); + self::assertCount(1, $columns); + self::assertInstanceOf(ColumnObject::class, $columns[0]); + self::assertSame('id', $columns[0]->getName()); } /** * @throws ReflectionException */ - public function testGetTableNamesIncludesViewsWhenRequested(): void + public function testGetColumnsUsesDefaultSchemaWhenNull(): void { + $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); + $refProp->setValue($this->abstractSourceMock, 'def'); + $this->setMockData([ - 'table_names' => [ - 'public' => [ - 'users' => ['table_type' => 'BASE TABLE'], - 'user_summary' => ['table_type' => 'VIEW'], + 'columns' => [ + 'def' => [ + 'users' => [ + 'id' => [ + 'ordinal_position' => 1, + 'column_default' => null, + 'is_nullable' => false, + 'data_type' => 'INT', + 'character_maximum_length' => null, + 'character_octet_length' => null, + 'numeric_precision' => null, + 'numeric_scale' => null, + 'numeric_unsigned' => null, + 'erratas' => [], + ], + ], ], ], ]); - // Verify views are included when flag is true - $tableNames = $this->abstractSourceMock->getTableNames('public', true); + $columns = $this->abstractSourceMock->getColumns('users', null); - self::assertSame(['users', 'user_summary'], $tableNames); + self::assertCount(1, $columns); + self::assertInstanceOf(ColumnObject::class, $columns[0]); } /** - * Table Object Methods - * * @throws ReflectionException */ - public function testGetTables(): void + public function testGetColumnThrowsExceptionForNonExistentColumn(): void { $this->setMockData([ - 'table_names' => [ - 'public' => [ - 'users' => ['table_type' => 'BASE TABLE'], - 'orders' => ['table_type' => 'BASE TABLE'], - ], - ], - 'columns' => [ - 'public' => [ - 'users' => [], - 'orders' => [], - ], - ], - 'constraints' => [ + 'columns' => [ 'public' => [ - 'users' => [], - 'orders' => [], + 'users' => [], ], ], ]); - // Verify getTables returns array of TableObject instances - $tables = $this->abstractSourceMock->getTables('public'); + $this->expectException(Exception::class); + $this->expectExceptionMessage('A column by that name was not found.'); - self::assertCount(2, $tables); - self::assertInstanceOf(TableObject::class, $tables[0]); - self::assertInstanceOf(TableObject::class, $tables[1]); + $this->abstractSourceMock->getColumn('non_existent', 'users', 'public'); + + // Verify exception is thrown for non-existent column } /** * @throws ReflectionException - * @throws Exception */ - public function testGetTableForBaseTable(): void + public function testGetColumnUsesDefaultSchemaWhenNull(): void { - $this->setMockData([ - 'table_names' => [ - 'public' => [ - 'users' => ['table_type' => 'BASE TABLE'], - ], - ], - 'columns' => [ - 'public' => [ - 'users' => [], - ], - ], - 'constraints' => [ - 'public' => [ - 'users' => [], + $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); + $refProp->setValue($this->abstractSourceMock, 'def'); + + $this->setMockData([ + 'columns' => [ + 'def' => [ + 'users' => [ + 'id' => [ + 'ordinal_position' => 1, + 'column_default' => null, + 'is_nullable' => false, + 'data_type' => 'INT', + 'character_maximum_length' => null, + 'character_octet_length' => null, + 'numeric_precision' => null, + 'numeric_scale' => null, + 'numeric_unsigned' => null, + 'erratas' => [], + ], + ], ], ], ]); - // Verify getTable returns TableObject for base table - $table = $this->abstractSourceMock->getTable('users', 'public'); + $column = $this->abstractSourceMock->getColumn('id', 'users', null); - self::assertInstanceOf(TableObject::class, $table); - self::assertSame('users', $table->getName()); + self::assertInstanceOf(ColumnObject::class, $column); } /** * @throws ReflectionException * @throws Exception */ - public function testGetTableForView(): void + public function testGetConstraint(): void { $this->setMockData([ - 'table_names' => [ - 'public' => [ - 'user_summary' => [ - 'table_type' => 'VIEW', - 'view_definition' => 'SELECT id, name FROM users', - 'check_option' => 'CASCADED', - 'is_updatable' => false, - ], - ], - ], - 'columns' => [ - 'public' => [ - 'user_summary' => [], - ], - ], 'constraints' => [ 'public' => [ - 'user_summary' => [], + 'orders' => [ + 'fk_orders_user' => [ + 'constraint_type' => 'FOREIGN KEY', + 'columns' => ['user_id'], + 'referenced_table_schema' => 'public', + 'referenced_table_name' => 'users', + 'referenced_columns' => ['id'], + 'match_option' => 'SIMPLE', + 'update_rule' => 'CASCADE', + 'delete_rule' => 'RESTRICT', + ], + ], ], ], ]); - $view = $this->abstractSourceMock->getTable('user_summary', 'public'); - // Verify getTable returns ViewObject for view type + $constraint = $this->abstractSourceMock->getConstraint('fk_orders_user', 'orders', 'public'); + // Verify getConstraint returns ConstraintObject with all properties - self::assertInstanceOf(ViewObject::class, $view); - self::assertSame('user_summary', $view->getName()); - self::assertSame('SELECT id, name FROM users', $view->getViewDefinition()); - self::assertSame('CASCADED', $view->getCheckOption()); - self::assertFalse($view->getIsUpdatable()); + self::assertInstanceOf(ConstraintObject::class, $constraint); + self::assertSame('fk_orders_user', $constraint->getName()); + self::assertSame('orders', $constraint->getTableName()); + self::assertSame('public', $constraint->getSchemaName()); + self::assertSame('FOREIGN KEY', $constraint->getType()); + self::assertSame(['user_id'], $constraint->getColumns()); + self::assertSame('public', $constraint->getReferencedTableSchema()); + self::assertSame('users', $constraint->getReferencedTableName()); + self::assertSame(['id'], $constraint->getReferencedColumns()); + self::assertSame('SIMPLE', $constraint->getMatchOption()); + self::assertSame('CASCADE', $constraint->getUpdateRule()); + self::assertSame('RESTRICT', $constraint->getDeleteRule()); } /** + * Constraint Key Methods + * * @throws ReflectionException */ - public function testGetTableThrowsExceptionForNonExistentTable(): void + public function testGetConstraintKeys(): void { - $this->setMockData([ - 'table_names' => [ - 'public' => [], + // internal data + $data = [ + 'constraint_references' => [ + 'foo_schema' => [ + [ + 'constraint_name' => 'bam_constraint', + 'update_rule' => 'UP', + 'delete_rule' => 'DOWN', + 'referenced_table_name' => 'another_table', + 'referenced_column_name' => 'another_column', + ], + ], ], - ]); + 'constraint_keys' => [ + 'foo_schema' => [ + [ + 'table_name' => 'bar_table', + 'constraint_name' => 'bam_constraint', + 'column_name' => 'a', + 'ordinal_position' => 1, + ], + ], + ], + ]; - $this->expectException(Exception::class); - $this->expectExceptionMessage('Table "non_existent" does not exist'); + $this->setMockData($data); + // Verify getConstraintKeys returns ConstraintKeyObject with references + $constraints = $this->abstractSourceMock->getConstraintKeys('bam_constraint', 'bar_table', 'foo_schema'); + self::assertCount(1, $constraints); - $this->abstractSourceMock->getTable('non_existent', 'public'); - // Verify exception is thrown for non-existent table + $constraintKeyObj = $constraints[0]; + self::assertInstanceOf(ConstraintKeyObject::class, $constraintKeyObj); + + // check value object is mapped correctly + self::assertEquals('a', $constraintKeyObj->getColumnName()); + // Verify value object is mapped correctly + self::assertEquals(1, $constraintKeyObj->getOrdinalPosition()); + self::assertEquals('another_table', $constraintKeyObj->getReferencedTableName()); + self::assertEquals('another_column', $constraintKeyObj->getReferencedColumnName()); + self::assertEquals('UP', $constraintKeyObj->getForeignKeyUpdateRule()); + self::assertEquals('DOWN', $constraintKeyObj->getForeignKeyDeleteRule()); } /** * @throws ReflectionException */ - public function testGetTableThrowsExceptionForUnsupportedTableType(): void + public function testGetConstraintKeysUsesDefaultSchemaWhenNull(): void { + $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); + $refProp->setValue($this->abstractSourceMock, 'def'); + $this->setMockData([ - 'table_names' => [ - 'public' => [ - 'special_table' => ['table_type' => 'UNSUPPORTED_TYPE'], + 'constraint_references' => [ + 'def' => [], + ], + 'constraint_keys' => [ + 'def' => [ + [ + 'table_name' => 'users', + 'constraint_name' => 'pk', + 'column_name' => 'id', + 'ordinal_position' => 1, + ], ], ], ]); - $this->expectException(Exception::class); - $this->expectExceptionMessage('Table "special_table" is of an unsupported type "UNSUPPORTED_TYPE"'); + $keys = $this->abstractSourceMock->getConstraintKeys('pk', 'users', null); - $this->abstractSourceMock->getTable('special_table', 'public'); - // Verify exception is thrown for unsupported table type + self::assertCount(1, $keys); + self::assertInstanceOf(ConstraintKeyObject::class, $keys[0]); } /** - * View Methods - * * @throws ReflectionException */ - public function testGetViewNames(): void + public function testGetConstraintKeysWithMultipleKeys(): void { - $this->setMockData([ - 'table_names' => [ + $data = [ + 'constraint_references' => [ 'public' => [ - 'users' => ['table_type' => 'BASE TABLE'], - 'user_summary' => ['table_type' => 'VIEW'], - 'order_summary' => ['table_type' => 'VIEW'], + [ + 'constraint_name' => 'fk_composite', + 'update_rule' => 'CASCADE', + 'delete_rule' => 'RESTRICT', + 'referenced_table_name' => 'ref_table', + 'referenced_column_name' => 'ref_col', + ], ], ], - ]); + 'constraint_keys' => [ + 'public' => [ + [ + 'table_name' => 'my_table', + 'constraint_name' => 'fk_composite', + 'column_name' => 'col1', + 'ordinal_position' => 1, + ], + [ + 'table_name' => 'my_table', + 'constraint_name' => 'fk_composite', + 'column_name' => 'col2', + 'ordinal_position' => 2, + ], + ], + ], + ]; - $viewNames = $this->abstractSourceMock->getViewNames('public'); + $this->setMockData($data); + // Verify composite constraint keys are returned in order + $keys = $this->abstractSourceMock->getConstraintKeys('fk_composite', 'my_table', 'public'); - // Verify getViewNames filters only view types - self::assertSame(['user_summary', 'order_summary'], $viewNames); + self::assertCount(2, $keys); + self::assertSame('col1', $keys[0]->getColumnName()); + self::assertSame('col2', $keys[1]->getColumnName()); } /** * @throws ReflectionException */ - public function testGetViews(): void + public function testGetConstraintKeysWithoutReferences(): void { - $this->setMockData([ - 'table_names' => [ - 'public' => [ - 'view1' => [ - 'table_type' => 'VIEW', - 'view_definition' => 'SELECT * FROM table1', - 'check_option' => null, - 'is_updatable' => true, - ], - ], - ], - 'columns' => [ - 'public' => [ - 'view1' => [], - ], + $data = [ + 'constraint_references' => [ + 'public' => [], ], - 'constraints' => [ + 'constraint_keys' => [ 'public' => [ - 'view1' => [], + [ + 'table_name' => 'users', + 'constraint_name' => 'pk_users', + 'column_name' => 'id', + 'ordinal_position' => 1, + ], ], ], - ]); + ]; - $views = $this->abstractSourceMock->getViews('public'); + $this->setMockData($data); + // Verify constraint keys without references have null references + $keys = $this->abstractSourceMock->getConstraintKeys('pk_users', 'users', 'public'); - // Verify getViews returns array of ViewObject instances - self::assertCount(1, $views); - self::assertInstanceOf(ViewObject::class, $views[0]); + self::assertCount(1, $keys); + self::assertSame('id', $keys[0]->getColumnName()); + self::assertNull($keys[0]->getReferencedTableName()); } /** + * Constraint Methods + * * @throws ReflectionException - * @throws Exception */ - public function testGetViewForExistingView(): void + public function testGetConstraints(): void { $this->setMockData([ - 'table_names' => [ - 'public' => [ - 'my_view' => [ - 'table_type' => 'VIEW', - 'view_definition' => 'SELECT * FROM users', - 'check_option' => 'LOCAL', - 'is_updatable' => true, - ], - ], - ], - 'columns' => [ - 'public' => [ - 'my_view' => [], - ], - ], 'constraints' => [ 'public' => [ - 'my_view' => [], + 'users' => [ + 'pk_users' => [ + 'constraint_type' => 'PRIMARY KEY', + 'columns' => ['id'], + ], + 'uq_users_email' => [ + 'constraint_type' => 'UNIQUE', + 'columns' => ['email'], + ], + ], ], ], ]); - $view = $this->abstractSourceMock->getView('my_view', 'public'); - // Verify getView returns ViewObject with all properties + $constraints = $this->abstractSourceMock->getConstraints('users', 'public'); + // Verify getConstraints returns array of ConstraintObject instances - self::assertInstanceOf(ViewObject::class, $view); - self::assertSame('my_view', $view->getName()); + self::assertCount(2, $constraints); + self::assertInstanceOf(ConstraintObject::class, $constraints[0]); + self::assertInstanceOf(ConstraintObject::class, $constraints[1]); } /** * @throws ReflectionException */ - public function testGetViewThrowsExceptionForNonExistentView(): void + public function testGetConstraintsUsesDefaultSchemaWhenNull(): void { + $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); + $refProp->setValue($this->abstractSourceMock, 'def'); + $this->setMockData([ - 'table_names' => [ - 'public' => [], + 'constraints' => [ + 'def' => [ + 'users' => [ + 'pk' => [ + 'constraint_type' => 'PRIMARY KEY', + 'columns' => ['id'], + ], + ], + ], ], ]); - $this->expectException(Exception::class); - $this->expectExceptionMessage('View "non_existent_view" does not exist'); + $constraints = $this->abstractSourceMock->getConstraints('users', null); - $this->abstractSourceMock->getView('non_existent_view', 'public'); - // Verify exception is thrown for non-existent view + self::assertCount(1, $constraints); } /** * @throws ReflectionException */ - public function testGetViewThrowsExceptionForTable(): void + public function testGetConstraintThrowsExceptionForNonExistent(): void { $this->setMockData([ - 'table_names' => [ + 'constraints' => [ 'public' => [ - 'users' => ['table_type' => 'BASE TABLE'], + 'users' => [], ], ], ]); $this->expectException(Exception::class); - $this->expectExceptionMessage('View "users" does not exist'); + $this->expectExceptionMessage('Cannot find a constraint by that name in this table'); - $this->abstractSourceMock->getView('users', 'public'); - // Verify exception is thrown when requesting view for a table + $this->abstractSourceMock->getConstraint('non_existent', 'users', 'public'); + + // Verify exception is thrown for non-existent constraint } /** - * Column Methods - * * @throws ReflectionException - * @throws Exception */ - public function testGetColumnNames(): void + public function testGetConstraintUsesDefaultSchemaWhenNull(): void { + $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); + $refProp->setValue($this->abstractSourceMock, 'def'); + $this->setMockData([ - 'columns' => [ - 'public' => [ + 'constraints' => [ + 'def' => [ 'users' => [ - 'id' => [], - 'username' => [], - 'email' => [], + 'pk' => [ + 'constraint_type' => 'PRIMARY KEY', + 'columns' => ['id'], + ], ], ], ], ]); - $columnNames = $this->abstractSourceMock->getColumnNames('users', 'public'); - // Verify getColumnNames returns array of column names + $constraint = $this->abstractSourceMock->getConstraint('pk', 'users', null); - self::assertSame(['id', 'username', 'email'], $columnNames); + self::assertInstanceOf(ConstraintObject::class, $constraint); } /** * @throws ReflectionException + * @throws Exception */ - public function testGetColumns(): void + public function testGetConstraintWithCheckClause(): void { $this->setMockData([ - 'columns' => [ + 'constraints' => [ 'public' => [ 'users' => [ - 'id' => [ - 'ordinal_position' => 1, - 'column_default' => null, - 'is_nullable' => false, - 'data_type' => 'INT', - 'character_maximum_length' => null, - 'character_octet_length' => null, - 'numeric_precision' => 10, - 'numeric_scale' => 0, - 'numeric_unsigned' => true, - 'erratas' => [], + 'chk_age' => [ + 'constraint_type' => 'CHECK', + 'check_clause' => 'age >= 18', ], ], ], ], ]); - $columns = $this->abstractSourceMock->getColumns('users', 'public'); - // Verify getColumns returns array of ColumnObject instances + $constraint = $this->abstractSourceMock->getConstraint('chk_age', 'users', 'public'); + // Verify getConstraint returns constraint with check clause - self::assertCount(1, $columns); - self::assertInstanceOf(ColumnObject::class, $columns[0]); - self::assertSame('id', $columns[0]->getName()); + self::assertSame('CHECK', $constraint->getType()); + self::assertSame('age >= 18', $constraint->getCheckClause()); + } + + /** + * Schema Methods + * + * @throws ReflectionException + */ + public function testGetSchemasCallsLoadSchemaData(): void + { + $this->abstractSourceMock->expects($this->once())->method('loadSchemaData'); + + $this->setMockData(['schemas' => ['schema1', 'schema2']]); + + // Verify getSchemas loads and returns schema list + $schemas = $this->abstractSourceMock->getSchemas(); + self::assertSame(['schema1', 'schema2'], $schemas); } /** * @throws ReflectionException * @throws Exception */ - public function testGetColumn(): void + public function testGetTableForBaseTable(): void { $this->setMockData([ - 'columns' => [ + 'table_names' => [ 'public' => [ - 'users' => [ - 'username' => [ - 'ordinal_position' => 2, - 'column_default' => '', - 'is_nullable' => false, - 'data_type' => 'VARCHAR', - 'character_maximum_length' => 255, - 'character_octet_length' => 1024, - 'numeric_precision' => null, - 'numeric_scale' => null, - 'numeric_unsigned' => null, - 'erratas' => ['collation' => 'utf8_general_ci'], - ], - ], + 'users' => ['table_type' => 'BASE TABLE'], + ], + ], + 'columns' => [ + 'public' => [ + 'users' => [], + ], + ], + 'constraints' => [ + 'public' => [ + 'users' => [], ], ], ]); - $column = $this->abstractSourceMock->getColumn('username', 'users', 'public'); - // Verify getColumn returns ColumnObject with all properties + // Verify getTable returns TableObject for base table + $table = $this->abstractSourceMock->getTable('users', 'public'); - self::assertInstanceOf(ColumnObject::class, $column); - self::assertSame('username', $column->getName()); - self::assertSame('users', $column->getTableName()); - self::assertSame('public', $column->getSchemaName()); - self::assertSame(2, $column->getOrdinalPosition()); - self::assertSame('', $column->getColumnDefault()); - self::assertFalse($column->getIsNullable()); - self::assertSame('VARCHAR', $column->getDataType()); - self::assertSame(255, $column->getCharacterMaximumLength()); - self::assertSame(1024, $column->getCharacterOctetLength()); - self::assertNull($column->getNumericPrecision()); - self::assertNull($column->getNumericScale()); - self::assertNull($column->getNumericUnsigned()); - self::assertSame('utf8_general_ci', $column->getErrata('collation')); + self::assertInstanceOf(TableObject::class, $table); + self::assertSame('users', $table->getName()); } /** * @throws ReflectionException + * @throws Exception */ - public function testGetColumnThrowsExceptionForNonExistentColumn(): void + public function testGetTableForView(): void { $this->setMockData([ - 'columns' => [ + 'table_names' => [ + 'public' => [ + 'user_summary' => [ + 'table_type' => 'VIEW', + 'view_definition' => 'SELECT id, name FROM users', + 'check_option' => 'CASCADED', + 'is_updatable' => false, + ], + ], + ], + 'columns' => [ 'public' => [ - 'users' => [], + 'user_summary' => [], + ], + ], + 'constraints' => [ + 'public' => [ + 'user_summary' => [], ], ], ]); - $this->expectException(Exception::class); - $this->expectExceptionMessage('A column by that name was not found.'); + $view = $this->abstractSourceMock->getTable('user_summary', 'public'); + // Verify getTable returns ViewObject for view type - $this->abstractSourceMock->getColumn('non_existent', 'users', 'public'); - // Verify exception is thrown for non-existent column + self::assertInstanceOf(ViewObject::class, $view); + self::assertSame('user_summary', $view->getName()); + self::assertSame('SELECT id, name FROM users', $view->getViewDefinition()); + self::assertSame('CASCADED', $view->getCheckOption()); + self::assertFalse($view->getIsUpdatable()); } /** - * Constraint Methods - * * @throws ReflectionException */ - public function testGetConstraints(): void + public function testGetTableNamesExcludesViewsByDefault(): void { $this->setMockData([ - 'constraints' => [ + 'table_names' => [ 'public' => [ - 'users' => [ - 'pk_users' => [ - 'constraint_type' => 'PRIMARY KEY', - 'columns' => ['id'], - ], - 'uq_users_email' => [ - 'constraint_type' => 'UNIQUE', - 'columns' => ['email'], - ], - ], + 'users' => ['table_type' => 'BASE TABLE'], + 'user_summary' => ['table_type' => 'VIEW'], + 'orders' => ['table_type' => 'BASE TABLE'], ], ], ]); - $constraints = $this->abstractSourceMock->getConstraints('users', 'public'); - // Verify getConstraints returns array of ConstraintObject instances + // Verify views are excluded by default + $tableNames = $this->abstractSourceMock->getTableNames('public'); - self::assertCount(2, $constraints); - self::assertInstanceOf(ConstraintObject::class, $constraints[0]); - self::assertInstanceOf(ConstraintObject::class, $constraints[1]); + self::assertSame(['users', 'orders'], $tableNames); } /** * @throws ReflectionException - * @throws Exception */ - public function testGetConstraint(): void + public function testGetTableNamesIncludesViewsWhenRequested(): void { $this->setMockData([ - 'constraints' => [ + 'table_names' => [ 'public' => [ - 'orders' => [ - 'fk_orders_user' => [ - 'constraint_type' => 'FOREIGN KEY', - 'columns' => ['user_id'], - 'referenced_table_schema' => 'public', - 'referenced_table_name' => 'users', - 'referenced_columns' => ['id'], - 'match_option' => 'SIMPLE', - 'update_rule' => 'CASCADE', - 'delete_rule' => 'RESTRICT', - ], - ], + 'users' => ['table_type' => 'BASE TABLE'], + 'user_summary' => ['table_type' => 'VIEW'], ], ], ]); - $constraint = $this->abstractSourceMock->getConstraint('fk_orders_user', 'orders', 'public'); - // Verify getConstraint returns ConstraintObject with all properties + // Verify views are included when flag is true + $tableNames = $this->abstractSourceMock->getTableNames('public', true); - self::assertInstanceOf(ConstraintObject::class, $constraint); - self::assertSame('fk_orders_user', $constraint->getName()); - self::assertSame('orders', $constraint->getTableName()); - self::assertSame('public', $constraint->getSchemaName()); - self::assertSame('FOREIGN KEY', $constraint->getType()); - self::assertSame(['user_id'], $constraint->getColumns()); - self::assertSame('public', $constraint->getReferencedTableSchema()); - self::assertSame('users', $constraint->getReferencedTableName()); - self::assertSame(['id'], $constraint->getReferencedColumns()); - self::assertSame('SIMPLE', $constraint->getMatchOption()); - self::assertSame('CASCADE', $constraint->getUpdateRule()); - self::assertSame('RESTRICT', $constraint->getDeleteRule()); + self::assertSame(['users', 'user_summary'], $tableNames); } /** + * Table Name Methods + * + * @throws ReflectionException * @throws ReflectionException - * @throws Exception */ - public function testGetConstraintWithCheckClause(): void + public function testGetTableNamesWithNullSchemaUsesDefault(): void { + $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); + $refProp->setValue($this->abstractSourceMock, 'default_schema'); + $this->setMockData([ - 'constraints' => [ - 'public' => [ - 'users' => [ - 'chk_age' => [ - 'constraint_type' => 'CHECK', - 'check_clause' => 'age >= 18', - ], - ], + 'table_names' => [ + 'default_schema' => [ + 'users' => ['table_type' => 'BASE TABLE'], + 'orders' => ['table_type' => 'BASE TABLE'], ], ], ]); - $constraint = $this->abstractSourceMock->getConstraint('chk_age', 'users', 'public'); - // Verify getConstraint returns constraint with check clause + // Verify default schema is used when none provided + $tableNames = $this->abstractSourceMock->getTableNames(); - self::assertSame('CHECK', $constraint->getType()); - self::assertSame('age >= 18', $constraint->getCheckClause()); + self::assertSame(['users', 'orders'], $tableNames); } /** * @throws ReflectionException */ - public function testGetConstraintThrowsExceptionForNonExistent(): void + public function testGetTableNamesWithSpecificSchema(): void { $this->setMockData([ - 'constraints' => [ + 'table_names' => [ 'public' => [ - 'users' => [], + 'products' => ['table_type' => 'BASE TABLE'], ], ], ]); - $this->expectException(Exception::class); - $this->expectExceptionMessage('Cannot find a constraint by that name in this table'); + // Verify table names for specific schema + $tableNames = $this->abstractSourceMock->getTableNames('public'); - $this->abstractSourceMock->getConstraint('non_existent', 'users', 'public'); - // Verify exception is thrown for non-existent constraint + self::assertSame(['products'], $tableNames); } /** - * Constraint Key Methods + * Table Object Methods * * @throws ReflectionException */ - public function testGetConstraintKeys(): void + public function testGetTables(): void { - // internal data - $data = [ - 'constraint_references' => [ - 'foo_schema' => [ - [ - 'constraint_name' => 'bam_constraint', - 'update_rule' => 'UP', - 'delete_rule' => 'DOWN', - 'referenced_table_name' => 'another_table', - 'referenced_column_name' => 'another_column', - ], + $this->setMockData([ + 'table_names' => [ + 'public' => [ + 'users' => ['table_type' => 'BASE TABLE'], + 'orders' => ['table_type' => 'BASE TABLE'], ], ], - 'constraint_keys' => [ - 'foo_schema' => [ - [ - 'table_name' => 'bar_table', - 'constraint_name' => 'bam_constraint', - 'column_name' => 'a', - 'ordinal_position' => 1, - ], + 'columns' => [ + 'public' => [ + 'users' => [], + 'orders' => [], ], ], - ]; - - $this->setMockData($data); - // Verify getConstraintKeys returns ConstraintKeyObject with references - $constraints = $this->abstractSourceMock->getConstraintKeys('bam_constraint', 'bar_table', 'foo_schema'); - self::assertCount(1, $constraints); + 'constraints' => [ + 'public' => [ + 'users' => [], + 'orders' => [], + ], + ], + ]); - $constraintKeyObj = $constraints[0]; - self::assertInstanceOf(ConstraintKeyObject::class, $constraintKeyObj); + // Verify getTables returns array of TableObject instances + $tables = $this->abstractSourceMock->getTables('public'); - // check value object is mapped correctly - self::assertEquals('a', $constraintKeyObj->getColumnName()); - // Verify value object is mapped correctly - self::assertEquals(1, $constraintKeyObj->getOrdinalPosition()); - self::assertEquals('another_table', $constraintKeyObj->getReferencedTableName()); - self::assertEquals('another_column', $constraintKeyObj->getReferencedColumnName()); - self::assertEquals('UP', $constraintKeyObj->getForeignKeyUpdateRule()); - self::assertEquals('DOWN', $constraintKeyObj->getForeignKeyDeleteRule()); + self::assertCount(2, $tables); + self::assertInstanceOf(TableObject::class, $tables[0]); + self::assertInstanceOf(TableObject::class, $tables[1]); } /** * @throws ReflectionException */ - public function testGetConstraintKeysWithMultipleKeys(): void + public function testGetTablesUsesDefaultSchemaWhenNull(): void { - $data = [ - 'constraint_references' => [ - 'public' => [ - [ - 'constraint_name' => 'fk_composite', - 'update_rule' => 'CASCADE', - 'delete_rule' => 'RESTRICT', - 'referenced_table_name' => 'ref_table', - 'referenced_column_name' => 'ref_col', - ], + $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); + $refProp->setValue($this->abstractSourceMock, 'def'); + + $this->setMockData([ + 'table_names' => [ + 'def' => [ + 'users' => ['table_type' => 'BASE TABLE'], ], ], - 'constraint_keys' => [ - 'public' => [ - [ - 'table_name' => 'my_table', - 'constraint_name' => 'fk_composite', - 'column_name' => 'col1', - 'ordinal_position' => 1, - ], - [ - 'table_name' => 'my_table', - 'constraint_name' => 'fk_composite', - 'column_name' => 'col2', - 'ordinal_position' => 2, - ], + 'columns' => [ + 'def' => [ + 'users' => [], ], ], - ]; + 'constraints' => [ + 'def' => [ + 'users' => [], + ], + ], + ]); - $this->setMockData($data); - // Verify composite constraint keys are returned in order - $keys = $this->abstractSourceMock->getConstraintKeys('fk_composite', 'my_table', 'public'); + $tables = $this->abstractSourceMock->getTables(null); - self::assertCount(2, $keys); - self::assertSame('col1', $keys[0]->getColumnName()); - self::assertSame('col2', $keys[1]->getColumnName()); + self::assertCount(1, $tables); + self::assertInstanceOf(TableObject::class, $tables[0]); } /** * @throws ReflectionException */ - public function testGetConstraintKeysWithoutReferences(): void + public function testGetTableThrowsExceptionForNonExistentTable(): void { - $data = [ - 'constraint_references' => [ + $this->setMockData([ + 'table_names' => [ 'public' => [], ], - 'constraint_keys' => [ - 'public' => [ - [ - 'table_name' => 'users', - 'constraint_name' => 'pk_users', - 'column_name' => 'id', - 'ordinal_position' => 1, - ], - ], - ], - ]; + ]); - $this->setMockData($data); - // Verify constraint keys without references have null references - $keys = $this->abstractSourceMock->getConstraintKeys('pk_users', 'users', 'public'); + $this->expectException(Exception::class); + $this->expectExceptionMessage('Table "non_existent" does not exist'); - self::assertCount(1, $keys); - self::assertSame('id', $keys[0]->getColumnName()); - self::assertNull($keys[0]->getReferencedTableName()); + $this->abstractSourceMock->getTable('non_existent', 'public'); + + // Verify exception is thrown for non-existent table } /** - * Trigger Methods - * * @throws ReflectionException */ - public function testGetTriggerNames(): void + public function testGetTableThrowsExceptionForUnsupportedTableType(): void { $this->setMockData([ - 'triggers' => [ + 'table_names' => [ 'public' => [ - 'audit_trigger' => [], - 'update_timestamp' => [], + 'special_table' => ['table_type' => 'UNSUPPORTED_TYPE'], ], ], ]); - $triggerNames = $this->abstractSourceMock->getTriggerNames('public'); - // Verify getTriggerNames returns array of trigger names + $this->expectException(Exception::class); + $this->expectExceptionMessage('Table "special_table" is of an unsupported type "UNSUPPORTED_TYPE"'); - self::assertSame(['audit_trigger', 'update_timestamp'], $triggerNames); + $this->abstractSourceMock->getTable('special_table', 'public'); + + // Verify exception is thrown for unsupported table type } /** * @throws ReflectionException */ - public function testGetTriggers(): void + public function testGetTableUsesDefaultSchemaWhenNull(): void { + $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); + $refProp->setValue($this->abstractSourceMock, 'def'); + $this->setMockData([ - 'triggers' => [ - 'public' => [ - 'trigger1' => [ - 'event_manipulation' => 'INSERT', - 'event_object_catalog' => 'catalog', - 'event_object_schema' => 'public', - 'event_object_table' => 'users', - 'action_order' => '1', - 'action_condition' => null, - 'action_statement' => 'BEGIN ... END', - 'action_orientation' => 'ROW', - 'action_timing' => 'BEFORE', - 'action_reference_old_table' => null, - 'action_reference_new_table' => null, - 'action_reference_old_row' => 'OLD', - 'action_reference_new_row' => 'NEW', - 'created' => null, - ], + 'table_names' => [ + 'def' => [ + 'users' => ['table_type' => 'BASE TABLE'], + ], + ], + 'columns' => [ + 'def' => [ + 'users' => [], + ], + ], + 'constraints' => [ + 'def' => [ + 'users' => [], ], ], ]); - $triggers = $this->abstractSourceMock->getTriggers('public'); - // Verify getTriggers returns array of TriggerObject instances + $table = $this->abstractSourceMock->getTable('users', null); - self::assertCount(1, $triggers); - self::assertInstanceOf(TriggerObject::class, $triggers[0]); + self::assertInstanceOf(TableObject::class, $table); } /** @@ -990,248 +1017,233 @@ public function testGetTrigger(): void } /** + * Trigger Methods + * * @throws ReflectionException */ - public function testGetTriggerThrowsExceptionForNonExistent(): void + public function testGetTriggerNames(): void { $this->setMockData([ 'triggers' => [ - 'public' => [], + 'public' => [ + 'audit_trigger' => [], + 'update_timestamp' => [], + ], ], ]); - $this->expectException(Exception::class); - $this->expectExceptionMessage('Trigger "non_existent" does not exist'); - - $this->abstractSourceMock->getTrigger('non_existent', 'public'); - // Verify exception is thrown for non-existent trigger - } - - /** - * Helper Methods - * - * @throws ReflectionException - * @throws ReflectionException - * @throws ReflectionException - */ - public function testPrepareDataHierarchyWithSingleKey(): void - { - $source = $this->getMockBuilder(AbstractSource::class) - ->setConstructorArgs([$this->adapterMock]) - ->onlyMethods(['loadSchemaData']) - ->getMock(); - - $method = new ReflectionMethod($source, 'prepareDataHierarchy'); - $method->invoke($source, 'test_key'); - - $refProp = new ReflectionProperty($source, 'data'); - - $data = $refProp->getValue($source); + $triggerNames = $this->abstractSourceMock->getTriggerNames('public'); + // Verify getTriggerNames returns array of trigger names - // Verify single key hierarchy is created - self::assertArrayHasKey('test_key', $data); + self::assertSame(['audit_trigger', 'update_timestamp'], $triggerNames); } /** * @throws ReflectionException */ - public function testPrepareDataHierarchyWithMultipleKeys(): void + public function testGetTriggerNamesUsesDefaultSchemaWhenNull(): void { - $source = $this->getMockBuilder(AbstractSource::class) - ->setConstructorArgs([$this->adapterMock]) - ->onlyMethods(['loadSchemaData']) - ->getMock(); - - $method = new ReflectionMethod($source, 'prepareDataHierarchy'); - $method->invoke($source, 'level1', 'level2', 'level3'); - - $refProp = new ReflectionProperty($source, 'data'); - - $data = $refProp->getValue($source); - - // Verify nested hierarchy is created - self::assertArrayHasKey('level1', $data); - self::assertArrayHasKey('level2', $data['level1']); - self::assertArrayHasKey('level3', $data['level1']['level2']); - } + $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); + $refProp->setValue($this->abstractSourceMock, 'def'); - /** - * @throws ReflectionException - */ - public function testLoadTableNameDataEarlyReturnWhenDataExists(): void - { $this->setMockData([ - 'table_names' => [ - 'public' => ['existing' => []], + 'triggers' => [ + 'def' => [ + 'trig1' => [], + ], ], ]); - $method = new ReflectionMethod($this->abstractSourceMock, 'loadTableNameData'); - $method->invoke($this->abstractSourceMock, 'public'); + $names = $this->abstractSourceMock->getTriggerNames(null); - $data = $this->getMockData(); - // Verify method returns early when data exists - self::assertArrayHasKey('existing', $data['table_names']['public']); + self::assertSame(['trig1'], $names); } /** * @throws ReflectionException */ - public function testLoadColumnDataEarlyReturnWhenDataExists(): void + public function testGetTriggers(): void { $this->setMockData([ - 'columns' => [ + 'triggers' => [ 'public' => [ - 'users' => ['existing_column' => []], + 'trigger1' => [ + 'event_manipulation' => 'INSERT', + 'event_object_catalog' => 'catalog', + 'event_object_schema' => 'public', + 'event_object_table' => 'users', + 'action_order' => '1', + 'action_condition' => null, + 'action_statement' => 'BEGIN ... END', + 'action_orientation' => 'ROW', + 'action_timing' => 'BEFORE', + 'action_reference_old_table' => null, + 'action_reference_new_table' => null, + 'action_reference_old_row' => 'OLD', + 'action_reference_new_row' => 'NEW', + 'created' => null, + ], ], ], ]); - $method = new ReflectionMethod($this->abstractSourceMock, 'loadColumnData'); - $method->invoke($this->abstractSourceMock, 'users', 'public'); + $triggers = $this->abstractSourceMock->getTriggers('public'); + // Verify getTriggers returns array of TriggerObject instances - $data = $this->getMockData(); - // Verify method returns early when data exists - self::assertArrayHasKey('existing_column', $data['columns']['public']['users']); + self::assertCount(1, $triggers); + self::assertInstanceOf(TriggerObject::class, $triggers[0]); } /** * @throws ReflectionException */ - public function testLoadConstraintDataEarlyReturnWhenDataExists(): void + public function testGetTriggersUsesDefaultSchemaWhenNull(): void { - $this->setMockData([ - 'constraints' => [ - 'public' => ['existing' => []], - ], - ]); - - $method = new ReflectionMethod($this->abstractSourceMock, 'loadConstraintData'); - $method->invoke($this->abstractSourceMock, 'table', 'public'); - - $data = $this->getMockData(); - // Verify method returns early when data exists - self::assertArrayHasKey('existing', $data['constraints']['public']); - } + $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); + $refProp->setValue($this->abstractSourceMock, 'def'); - /** - * @throws ReflectionException - */ - public function testLoadConstraintDataKeysEarlyReturnWhenDataExists(): void - { $this->setMockData([ - 'constraint_keys' => [ - 'public' => ['existing' => []], + 'triggers' => [ + 'def' => [ + 'trig1' => [ + 'event_manipulation' => 'INSERT', + 'event_object_catalog' => 'cat', + 'event_object_schema' => 'def', + 'event_object_table' => 'users', + 'action_order' => '1', + 'action_condition' => null, + 'action_statement' => 'BEGIN END', + 'action_orientation' => 'ROW', + 'action_timing' => 'BEFORE', + 'action_reference_old_table' => null, + 'action_reference_new_table' => null, + 'action_reference_old_row' => 'OLD', + 'action_reference_new_row' => 'NEW', + 'created' => null, + ], + ], ], ]); - $method = new ReflectionMethod($this->abstractSourceMock, 'loadConstraintDataKeys'); - $method->invoke($this->abstractSourceMock, 'public'); + $triggers = $this->abstractSourceMock->getTriggers(null); - $data = $this->getMockData(); - // Verify method returns early when data exists - self::assertArrayHasKey('existing', $data['constraint_keys']['public']); + self::assertCount(1, $triggers); + self::assertInstanceOf(TriggerObject::class, $triggers[0]); } /** * @throws ReflectionException */ - public function testLoadConstraintReferencesEarlyReturnWhenDataExists(): void + public function testGetTriggerThrowsExceptionForNonExistent(): void { $this->setMockData([ - 'constraint_references' => [ - 'public' => ['existing' => []], + 'triggers' => [ + 'public' => [], ], ]); - $method = new ReflectionMethod($this->abstractSourceMock, 'loadConstraintReferences'); - $method->invoke($this->abstractSourceMock, 'table', 'public'); + $this->expectException(Exception::class); + $this->expectExceptionMessage('Trigger "non_existent" does not exist'); - $data = $this->getMockData(); - // Verify method returns early when data exists - self::assertArrayHasKey('existing', $data['constraint_references']['public']); + $this->abstractSourceMock->getTrigger('non_existent', 'public'); + + // Verify exception is thrown for non-existent trigger } /** * @throws ReflectionException */ - public function testLoadTriggerDataEarlyReturnWhenDataExists(): void + public function testGetTriggerUsesDefaultSchemaWhenNull(): void { + $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); + $refProp->setValue($this->abstractSourceMock, 'def'); + $this->setMockData([ 'triggers' => [ - 'public' => ['existing' => []], + 'def' => [ + 'trig1' => [ + 'event_manipulation' => 'INSERT', + 'event_object_catalog' => 'cat', + 'event_object_schema' => 'def', + 'event_object_table' => 'users', + 'action_order' => '1', + 'action_condition' => null, + 'action_statement' => 'BEGIN END', + 'action_orientation' => 'ROW', + 'action_timing' => 'BEFORE', + 'action_reference_old_table' => null, + 'action_reference_new_table' => null, + 'action_reference_old_row' => 'OLD', + 'action_reference_new_row' => 'NEW', + 'created' => null, + ], + ], ], ]); - $method = new ReflectionMethod($this->abstractSourceMock, 'loadTriggerData'); - $method->invoke($this->abstractSourceMock, 'public'); + $trigger = $this->abstractSourceMock->getTrigger('trig1', null); - $data = $this->getMockData(); - // Verify method returns early when data exists - self::assertArrayHasKey('existing', $data['triggers']['public']); + self::assertInstanceOf(TriggerObject::class, $trigger); + self::assertSame('trig1', $trigger->getName()); } /** * @throws ReflectionException + * @throws Exception */ - public function testGetTablesUsesDefaultSchemaWhenNull(): void + public function testGetViewForExistingView(): void { - $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); - $refProp->setValue($this->abstractSourceMock, 'def'); - $this->setMockData([ 'table_names' => [ - 'def' => [ - 'users' => ['table_type' => 'BASE TABLE'], + 'public' => [ + 'my_view' => [ + 'table_type' => 'VIEW', + 'view_definition' => 'SELECT * FROM users', + 'check_option' => 'LOCAL', + 'is_updatable' => true, + ], ], ], 'columns' => [ - 'def' => [ - 'users' => [], + 'public' => [ + 'my_view' => [], ], ], 'constraints' => [ - 'def' => [ - 'users' => [], + 'public' => [ + 'my_view' => [], ], ], ]); - $tables = $this->abstractSourceMock->getTables(null); + $view = $this->abstractSourceMock->getView('my_view', 'public'); + // Verify getView returns ViewObject with all properties - self::assertCount(1, $tables); - self::assertInstanceOf(TableObject::class, $tables[0]); + self::assertInstanceOf(ViewObject::class, $view); + self::assertSame('my_view', $view->getName()); } /** + * View Methods + * * @throws ReflectionException */ - public function testGetTableUsesDefaultSchemaWhenNull(): void + public function testGetViewNames(): void { - $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); - $refProp->setValue($this->abstractSourceMock, 'def'); - $this->setMockData([ 'table_names' => [ - 'def' => [ - 'users' => ['table_type' => 'BASE TABLE'], - ], - ], - 'columns' => [ - 'def' => [ - 'users' => [], - ], - ], - 'constraints' => [ - 'def' => [ - 'users' => [], + 'public' => [ + 'users' => ['table_type' => 'BASE TABLE'], + 'user_summary' => ['table_type' => 'VIEW'], + 'order_summary' => ['table_type' => 'VIEW'], ], ], ]); - $table = $this->abstractSourceMock->getTable('users', null); + $viewNames = $this->abstractSourceMock->getViewNames('public'); - self::assertInstanceOf(TableObject::class, $table); + // Verify getViewNames filters only view types + self::assertSame(['user_summary', 'order_summary'], $viewNames); } /** @@ -1258,35 +1270,42 @@ public function testGetViewNamesUsesDefaultSchemaWhenNull(): void /** * @throws ReflectionException */ - public function testGetViewsUsesDefaultSchemaWhenNull(): void + public function testGetViews(): void { - $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); - $refProp->setValue($this->abstractSourceMock, 'def'); - $this->setMockData([ 'table_names' => [ - 'def' => [ - 'v1' => [ + 'public' => [ + 'view1' => [ 'table_type' => 'VIEW', - 'view_definition' => 'SELECT 1', + 'view_definition' => 'SELECT * FROM table1', 'check_option' => null, 'is_updatable' => true, ], ], ], - 'columns' => ['def' => ['v1' => []]], - 'constraints' => ['def' => ['v1' => []]], + 'columns' => [ + 'public' => [ + 'view1' => [], + ], + ], + 'constraints' => [ + 'public' => [ + 'view1' => [], + ], + ], ]); - $views = $this->abstractSourceMock->getViews(null); + $views = $this->abstractSourceMock->getViews('public'); + // Verify getViews returns array of ViewObject instances self::assertCount(1, $views); + self::assertInstanceOf(ViewObject::class, $views[0]); } /** * @throws ReflectionException */ - public function testGetViewUsesDefaultSchemaWhenNull(): void + public function testGetViewsUsesDefaultSchemaWhenNull(): void { $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); $refProp->setValue($this->abstractSourceMock, 'def'); @@ -1298,7 +1317,7 @@ public function testGetViewUsesDefaultSchemaWhenNull(): void 'table_type' => 'VIEW', 'view_definition' => 'SELECT 1', 'check_option' => null, - 'is_updatable' => false, + 'is_updatable' => true, ], ], ], @@ -1306,279 +1325,212 @@ public function testGetViewUsesDefaultSchemaWhenNull(): void 'constraints' => ['def' => ['v1' => []]], ]); - $view = $this->abstractSourceMock->getView('v1', null); + $views = $this->abstractSourceMock->getViews(null); - self::assertInstanceOf(ViewObject::class, $view); + self::assertCount(1, $views); } /** * @throws ReflectionException */ - public function testGetColumnNamesUsesDefaultSchemaWhenNull(): void + public function testGetViewThrowsExceptionForNonExistentView(): void { - $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); - $refProp->setValue($this->abstractSourceMock, 'def'); - $this->setMockData([ - 'columns' => [ - 'def' => [ - 'users' => [ - 'id' => [], - 'name' => [], - ], - ], + 'table_names' => [ + 'public' => [], ], ]); - $names = $this->abstractSourceMock->getColumnNames('users', null); + $this->expectException(Exception::class); + $this->expectExceptionMessage('View "non_existent_view" does not exist'); - self::assertSame(['id', 'name'], $names); + $this->abstractSourceMock->getView('non_existent_view', 'public'); + + // Verify exception is thrown for non-existent view } /** * @throws ReflectionException */ - public function testGetColumnsUsesDefaultSchemaWhenNull(): void + public function testGetViewThrowsExceptionForTable(): void { - $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); - $refProp->setValue($this->abstractSourceMock, 'def'); - $this->setMockData([ - 'columns' => [ - 'def' => [ - 'users' => [ - 'id' => [ - 'ordinal_position' => 1, - 'column_default' => null, - 'is_nullable' => false, - 'data_type' => 'INT', - 'character_maximum_length' => null, - 'character_octet_length' => null, - 'numeric_precision' => null, - 'numeric_scale' => null, - 'numeric_unsigned' => null, - 'erratas' => [], - ], - ], + 'table_names' => [ + 'public' => [ + 'users' => ['table_type' => 'BASE TABLE'], ], ], ]); - $columns = $this->abstractSourceMock->getColumns('users', null); + $this->expectException(Exception::class); + $this->expectExceptionMessage('View "users" does not exist'); - self::assertCount(1, $columns); - self::assertInstanceOf(ColumnObject::class, $columns[0]); + $this->abstractSourceMock->getView('users', 'public'); + + // Verify exception is thrown when requesting view for a table } /** * @throws ReflectionException */ - public function testGetColumnUsesDefaultSchemaWhenNull(): void + public function testGetViewUsesDefaultSchemaWhenNull(): void { $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); $refProp->setValue($this->abstractSourceMock, 'def'); $this->setMockData([ - 'columns' => [ + 'table_names' => [ 'def' => [ - 'users' => [ - 'id' => [ - 'ordinal_position' => 1, - 'column_default' => null, - 'is_nullable' => false, - 'data_type' => 'INT', - 'character_maximum_length' => null, - 'character_octet_length' => null, - 'numeric_precision' => null, - 'numeric_scale' => null, - 'numeric_unsigned' => null, - 'erratas' => [], - ], + 'v1' => [ + 'table_type' => 'VIEW', + 'view_definition' => 'SELECT 1', + 'check_option' => null, + 'is_updatable' => false, ], ], ], + 'columns' => ['def' => ['v1' => []]], + 'constraints' => ['def' => ['v1' => []]], ]); - $column = $this->abstractSourceMock->getColumn('id', 'users', null); + $view = $this->abstractSourceMock->getView('v1', null); - self::assertInstanceOf(ColumnObject::class, $column); + self::assertInstanceOf(ViewObject::class, $view); } + protected MockObject|AbstractSource $abstractSourceMock; + + protected MockObject|AdapterInterface $adapterMock; + /** * @throws ReflectionException */ - public function testGetConstraintsUsesDefaultSchemaWhenNull(): void + public function testLoadColumnDataCallsPrepareDataHierarchy(): void { - $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); - $refProp->setValue($this->abstractSourceMock, 'def'); - - $this->setMockData([ - 'constraints' => [ - 'def' => [ - 'users' => [ - 'pk' => [ - 'constraint_type' => 'PRIMARY KEY', - 'columns' => ['id'], - ], - ], - ], - ], - ]); - - $constraints = $this->abstractSourceMock->getConstraints('users', null); + $method = new ReflectionMethod($this->abstractSourceMock, 'loadColumnData'); + $method->invoke($this->abstractSourceMock, 'users', 'test_schema'); - self::assertCount(1, $constraints); + $data = $this->getMockData(); + self::assertArrayHasKey('columns', $data); + self::assertArrayHasKey('test_schema', $data['columns']); + self::assertArrayHasKey('users', $data['columns']['test_schema']); } /** * @throws ReflectionException */ - public function testGetConstraintUsesDefaultSchemaWhenNull(): void + public function testLoadColumnDataEarlyReturnWhenDataExists(): void { - $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); - $refProp->setValue($this->abstractSourceMock, 'def'); - $this->setMockData([ - 'constraints' => [ - 'def' => [ - 'users' => [ - 'pk' => [ - 'constraint_type' => 'PRIMARY KEY', - 'columns' => ['id'], - ], - ], + 'columns' => [ + 'public' => [ + 'users' => ['existing_column' => []], ], ], ]); - $constraint = $this->abstractSourceMock->getConstraint('pk', 'users', null); + $method = new ReflectionMethod($this->abstractSourceMock, 'loadColumnData'); + $method->invoke($this->abstractSourceMock, 'users', 'public'); + + $data = $this->getMockData(); + // Verify method returns early when data exists + self::assertArrayHasKey('existing_column', $data['columns']['public']['users']); + } + + /** + * @throws ReflectionException + */ + public function testLoadConstraintDataCallsPrepareDataHierarchy(): void + { + $method = new ReflectionMethod($this->abstractSourceMock, 'loadConstraintData'); + $method->invoke($this->abstractSourceMock, 'users', 'test_schema'); - self::assertInstanceOf(ConstraintObject::class, $constraint); + $data = $this->getMockData(); + self::assertArrayHasKey('constraints', $data); + self::assertArrayHasKey('test_schema', $data['constraints']); } /** * @throws ReflectionException */ - public function testGetConstraintKeysUsesDefaultSchemaWhenNull(): void + public function testLoadConstraintDataEarlyReturnWhenDataExists(): void { - $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); - $refProp->setValue($this->abstractSourceMock, 'def'); - $this->setMockData([ - 'constraint_references' => [ - 'def' => [], - ], - 'constraint_keys' => [ - 'def' => [ - [ - 'table_name' => 'users', - 'constraint_name' => 'pk', - 'column_name' => 'id', - 'ordinal_position' => 1, - ], - ], + 'constraints' => [ + 'public' => ['existing' => []], ], ]); - $keys = $this->abstractSourceMock->getConstraintKeys('pk', 'users', null); + $method = new ReflectionMethod($this->abstractSourceMock, 'loadConstraintData'); + $method->invoke($this->abstractSourceMock, 'table', 'public'); - self::assertCount(1, $keys); - self::assertInstanceOf(ConstraintKeyObject::class, $keys[0]); + $data = $this->getMockData(); + // Verify method returns early when data exists + self::assertArrayHasKey('existing', $data['constraints']['public']); } /** * @throws ReflectionException */ - public function testGetTriggerNamesUsesDefaultSchemaWhenNull(): void + public function testLoadConstraintDataKeysCallsPrepareDataHierarchy(): void { - $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); - $refProp->setValue($this->abstractSourceMock, 'def'); - - $this->setMockData([ - 'triggers' => [ - 'def' => [ - 'trig1' => [], - ], - ], - ]); - - $names = $this->abstractSourceMock->getTriggerNames(null); + $method = new ReflectionMethod($this->abstractSourceMock, 'loadConstraintDataKeys'); + $method->invoke($this->abstractSourceMock, 'test_schema'); - self::assertSame(['trig1'], $names); + $data = $this->getMockData(); + self::assertArrayHasKey('constraint_keys', $data); + self::assertArrayHasKey('test_schema', $data['constraint_keys']); } /** * @throws ReflectionException */ - public function testGetTriggersUsesDefaultSchemaWhenNull(): void + public function testLoadConstraintDataKeysEarlyReturnWhenDataExists(): void { - $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); - $refProp->setValue($this->abstractSourceMock, 'def'); - $this->setMockData([ - 'triggers' => [ - 'def' => [ - 'trig1' => [ - 'event_manipulation' => 'INSERT', - 'event_object_catalog' => 'cat', - 'event_object_schema' => 'def', - 'event_object_table' => 'users', - 'action_order' => '1', - 'action_condition' => null, - 'action_statement' => 'BEGIN END', - 'action_orientation' => 'ROW', - 'action_timing' => 'BEFORE', - 'action_reference_old_table' => null, - 'action_reference_new_table' => null, - 'action_reference_old_row' => 'OLD', - 'action_reference_new_row' => 'NEW', - 'created' => null, - ], - ], + 'constraint_keys' => [ + 'public' => ['existing' => []], ], ]); - $triggers = $this->abstractSourceMock->getTriggers(null); + $method = new ReflectionMethod($this->abstractSourceMock, 'loadConstraintDataKeys'); + $method->invoke($this->abstractSourceMock, 'public'); - self::assertCount(1, $triggers); - self::assertInstanceOf(TriggerObject::class, $triggers[0]); + $data = $this->getMockData(); + // Verify method returns early when data exists + self::assertArrayHasKey('existing', $data['constraint_keys']['public']); } /** * @throws ReflectionException */ - public function testGetTriggerUsesDefaultSchemaWhenNull(): void + public function testLoadConstraintReferencesCallsPrepareDataHierarchy(): void { - $refProp = new ReflectionProperty($this->abstractSourceMock, 'defaultSchema'); - $refProp->setValue($this->abstractSourceMock, 'def'); + $method = new ReflectionMethod($this->abstractSourceMock, 'loadConstraintReferences'); + $method->invoke($this->abstractSourceMock, 'users', 'test_schema'); + + $data = $this->getMockData(); + self::assertArrayHasKey('constraint_references', $data); + self::assertArrayHasKey('test_schema', $data['constraint_references']); + } + /** + * @throws ReflectionException + */ + public function testLoadConstraintReferencesEarlyReturnWhenDataExists(): void + { $this->setMockData([ - 'triggers' => [ - 'def' => [ - 'trig1' => [ - 'event_manipulation' => 'INSERT', - 'event_object_catalog' => 'cat', - 'event_object_schema' => 'def', - 'event_object_table' => 'users', - 'action_order' => '1', - 'action_condition' => null, - 'action_statement' => 'BEGIN END', - 'action_orientation' => 'ROW', - 'action_timing' => 'BEFORE', - 'action_reference_old_table' => null, - 'action_reference_new_table' => null, - 'action_reference_old_row' => 'OLD', - 'action_reference_new_row' => 'NEW', - 'created' => null, - ], - ], + 'constraint_references' => [ + 'public' => ['existing' => []], ], ]); - $trigger = $this->abstractSourceMock->getTrigger('trig1', null); + $method = new ReflectionMethod($this->abstractSourceMock, 'loadConstraintReferences'); + $method->invoke($this->abstractSourceMock, 'table', 'public'); - self::assertInstanceOf(TriggerObject::class, $trigger); - self::assertSame('trig1', $trigger->getName()); + $data = $this->getMockData(); + // Verify method returns early when data exists + self::assertArrayHasKey('existing', $data['constraint_references']['public']); } /** @@ -1597,81 +1549,135 @@ public function testLoadTableNameDataCallsPrepareDataHierarchy(): void /** * @throws ReflectionException */ - public function testLoadColumnDataCallsPrepareDataHierarchy(): void + public function testLoadTableNameDataEarlyReturnWhenDataExists(): void { - $method = new ReflectionMethod($this->abstractSourceMock, 'loadColumnData'); - $method->invoke($this->abstractSourceMock, 'users', 'test_schema'); + $this->setMockData([ + 'table_names' => [ + 'public' => ['existing' => []], + ], + ]); + + $method = new ReflectionMethod($this->abstractSourceMock, 'loadTableNameData'); + $method->invoke($this->abstractSourceMock, 'public'); $data = $this->getMockData(); - self::assertArrayHasKey('columns', $data); - self::assertArrayHasKey('test_schema', $data['columns']); - self::assertArrayHasKey('users', $data['columns']['test_schema']); + // Verify method returns early when data exists + self::assertArrayHasKey('existing', $data['table_names']['public']); } /** * @throws ReflectionException */ - public function testLoadConstraintDataCallsPrepareDataHierarchy(): void + public function testLoadTriggerDataCallsPrepareDataHierarchy(): void { - $method = new ReflectionMethod($this->abstractSourceMock, 'loadConstraintData'); - $method->invoke($this->abstractSourceMock, 'users', 'test_schema'); + $method = new ReflectionMethod($this->abstractSourceMock, 'loadTriggerData'); + $method->invoke($this->abstractSourceMock, 'test_schema'); $data = $this->getMockData(); - self::assertArrayHasKey('constraints', $data); - self::assertArrayHasKey('test_schema', $data['constraints']); + self::assertArrayHasKey('triggers', $data); + self::assertArrayHasKey('test_schema', $data['triggers']); } /** * @throws ReflectionException */ - public function testLoadConstraintDataKeysCallsPrepareDataHierarchy(): void + public function testLoadTriggerDataEarlyReturnWhenDataExists(): void { - $method = new ReflectionMethod($this->abstractSourceMock, 'loadConstraintDataKeys'); - $method->invoke($this->abstractSourceMock, 'test_schema'); + $this->setMockData([ + 'triggers' => [ + 'public' => ['existing' => []], + ], + ]); + + $method = new ReflectionMethod($this->abstractSourceMock, 'loadTriggerData'); + $method->invoke($this->abstractSourceMock, 'public'); $data = $this->getMockData(); - self::assertArrayHasKey('constraint_keys', $data); - self::assertArrayHasKey('test_schema', $data['constraint_keys']); + // Verify method returns early when data exists + self::assertArrayHasKey('existing', $data['triggers']['public']); } /** * @throws ReflectionException */ - public function testLoadConstraintReferencesCallsPrepareDataHierarchy(): void + public function testPrepareDataHierarchyWithMultipleKeys(): void { - $method = new ReflectionMethod($this->abstractSourceMock, 'loadConstraintReferences'); - $method->invoke($this->abstractSourceMock, 'users', 'test_schema'); + $source = $this->getMockBuilder(AbstractSource::class) + ->setConstructorArgs([$this->adapterMock]) + ->onlyMethods(['loadSchemaData']) + ->getMock(); - $data = $this->getMockData(); - self::assertArrayHasKey('constraint_references', $data); - self::assertArrayHasKey('test_schema', $data['constraint_references']); + $method = new ReflectionMethod($source, 'prepareDataHierarchy'); + $method->invoke($source, 'level1', 'level2', 'level3'); + + $refProp = new ReflectionProperty($source, 'data'); + + $data = $refProp->getValue($source); + + // Verify nested hierarchy is created + self::assertArrayHasKey('level1', $data); + self::assertArrayHasKey('level2', $data['level1']); + self::assertArrayHasKey('level3', $data['level1']['level2']); } /** + * Helper Methods + * + * @throws ReflectionException + * @throws ReflectionException * @throws ReflectionException */ - public function testLoadTriggerDataCallsPrepareDataHierarchy(): void + public function testPrepareDataHierarchyWithSingleKey(): void { - $method = new ReflectionMethod($this->abstractSourceMock, 'loadTriggerData'); - $method->invoke($this->abstractSourceMock, 'test_schema'); + $source = $this->getMockBuilder(AbstractSource::class) + ->setConstructorArgs([$this->adapterMock]) + ->onlyMethods(['loadSchemaData']) + ->getMock(); - $data = $this->getMockData(); - self::assertArrayHasKey('triggers', $data); - self::assertArrayHasKey('test_schema', $data['triggers']); + $method = new ReflectionMethod($source, 'prepareDataHierarchy'); + $method->invoke($source, 'test_key'); + + $refProp = new ReflectionProperty($source, 'data'); + + $data = $refProp->getValue($source); + + // Verify single key hierarchy is created + self::assertArrayHasKey('test_key', $data); } - public function testGetColumnNamesThrowsWhenLoadColumnDataDoesNotPopulate(): void + #[Override] + protected function setUp(): void { - $adapter = $this->createMockForIntersectionOfInterfaces([ + /** @var AdapterInterface&SchemaAwareInterface&MockObject $adapterMock */ + $adapterMock = $this->createMockForIntersectionOfInterfaces([ AdapterInterface::class, SchemaAwareInterface::class, ]); - $adapter->method('getCurrentSchema')->willReturn('public'); + $this->adapterMock = $adapterMock; - $source = new IncompleteSource($adapter); + $this->abstractSourceMock = $this->getMockBuilder(AbstractSource::class) + ->setConstructorArgs([$this->adapterMock]) + ->onlyMethods([ + 'loadSchemaData', + ]) + ->getMock(); + } - $this->expectException(Exception::class); - $this->expectExceptionMessage('"nonexistent" does not exist'); - $source->getColumnNames('nonexistent', 'public'); + /** + * @throws ReflectionException + */ + private function getMockData(): array + { + $refProp = new ReflectionProperty($this->abstractSourceMock, 'data'); + return $refProp->getValue($this->abstractSourceMock); + } + + /** + * @throws ReflectionException + */ + private function setMockData(array $data): void + { + $refProp = new ReflectionProperty($this->abstractSourceMock, 'data'); + $refProp->setValue($this->abstractSourceMock, $data); } } diff --git a/test/unit/Metadata/Source/TestAsset/IncompleteSource.php b/test/unit/Metadata/Source/TestAsset/IncompleteSource.php index 9d856bd36..f28026528 100644 --- a/test/unit/Metadata/Source/TestAsset/IncompleteSource.php +++ b/test/unit/Metadata/Source/TestAsset/IncompleteSource.php @@ -12,11 +12,7 @@ */ class IncompleteSource extends AbstractSource { - protected function loadSchemaData(): void - { - } + protected function loadColumnData(string $table, string $schema): void {} - protected function loadColumnData(string $table, string $schema): void - { - } + protected function loadSchemaData(): void {} } diff --git a/test/unit/ResultSet/AbstractResultSetIntegrationTest.php b/test/unit/ResultSet/AbstractResultSetIntegrationTest.php index 39d09e827..3cde30794 100644 --- a/test/unit/ResultSet/AbstractResultSetIntegrationTest.php +++ b/test/unit/ResultSet/AbstractResultSetIntegrationTest.php @@ -17,20 +17,6 @@ final class AbstractResultSetIntegrationTest extends TestCase { protected MockObject|AbstractResultSet $resultSet; - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - * - * @throws Exception - */ - #[Override] - protected function setUp(): void - { - $this->resultSet = $this->getMockBuilder(AbstractResultSet::class) - ->onlyMethods(['setRowPrototype', 'getRowPrototype']) - ->getMock(); - } - /** * @throws \Exception */ @@ -61,4 +47,18 @@ public function testCurrentCallsDataSourceCurrentOnceWithBuffer(): void $this->resultSet->current(); self::assertEquals($value1, $value2); } + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + * + * @throws Exception + */ + #[Override] + protected function setUp(): void + { + $this->resultSet = $this->getMockBuilder(AbstractResultSet::class) + ->onlyMethods(['setRowPrototype', 'getRowPrototype']) + ->getMock(); + } } diff --git a/test/unit/ResultSet/AbstractResultSetTest.php b/test/unit/ResultSet/AbstractResultSetTest.php index 384768808..208d6b629 100644 --- a/test/unit/ResultSet/AbstractResultSetTest.php +++ b/test/unit/ResultSet/AbstractResultSetTest.php @@ -40,65 +40,6 @@ final class AbstractResultSetTest extends TestCase { protected MockObject|AbstractResultSet $resultSet; - private function createResultSetMock(): MockObject|AbstractResultSet - { - return $this->getMockBuilder(AbstractResultSet::class) - ->onlyMethods(['setRowPrototype', 'getRowPrototype']) - ->getMock(); - } - - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - #[Override] - protected function setUp(): void - { - $this->resultSet = $this->createResultSetMock(); - } - - /** - * @throws Exception - */ - public function testInitialize(): void - { - $resultSet = $this->createResultSetMock(); - - // Verify initialize() accepts array data and returns fluent interface - self::assertSame($resultSet, $resultSet->initialize([ - ['id' => 1, 'name' => 'one'], - ['id' => 2, 'name' => 'two'], - ['id' => 3, 'name' => 'three'], - ])); - - // Verify invalid data type throws exception - $this->expectException(TypeError::class); - /** @noinspection ALL */ - $resultSet->initialize('foo'); - } - - /** - * @throws Exception - */ - public function testInitializeDoesNotCallCount(): void - { - $resultSet = $this->createResultSetMock(); - $result = $this->getMockBuilder(ResultInterface::class)->onlyMethods([])->getMock(); - $result->expects($this->never())->method('count'); - // Initialize with result and verify count() is never called - $resultSet->initialize($result); - } - - /** - * @throws Exception - */ - public function testInitializeWithEmptyArray(): void - { - $resultSet = $this->createResultSetMock(); - // Verify initialize() accepts empty array - self::assertSame($resultSet, $resultSet->initialize([])); - } - /** * @throws Exception */ @@ -121,20 +62,13 @@ public function testBuffer(): void $resultSet->buffer(); } - public function testIsBuffered(): void - { - $resultSet = $this->createResultSetMock(); - // Verify buffering is disabled by default - self::assertFalse($resultSet->isBuffered()); - $resultSet->buffer(); - // Verify buffering is enabled after buffer() call - self::assertTrue($resultSet->isBuffered()); - } - /** + * Test multiple iterations with buffer + * * @throws Exception */ - public function testGetDataSource(): void + #[Group('issue-6845')] + public function testBufferIterations(): void { $resultSet = $this->createResultSetMock(); $resultSet->initialize(new ArrayIterator([ @@ -142,61 +76,61 @@ public function testGetDataSource(): void ['id' => 2, 'name' => 'two'], ['id' => 3, 'name' => 'three'], ])); - // Verify getDataSource() returns the initialized iterator - self::assertInstanceOf(ArrayIterator::class, $resultSet->getDataSource()); + $resultSet->buffer(); + + // Iterate through rows and verify data + $data = $resultSet->current(); + self::assertEquals(1, $data['id']); + $resultSet->next(); + $data = $resultSet->current(); + self::assertEquals(2, $data['id']); + + // Rewind and iterate again to verify buffering allows rewind + $resultSet->rewind(); + $data = $resultSet->current(); + self::assertEquals(1, $data['id']); + $resultSet->next(); + $data = $resultSet->current(); + self::assertEquals(2, $data['id']); + $resultSet->next(); + $data = $resultSet->current(); + self::assertEquals(3, $data['id']); } /** * @throws Exception */ - public function testGetFieldCount(): void + public function testCount(): void { $resultSet = $this->createResultSetMock(); $resultSet->initialize(new ArrayIterator([ ['id' => 1, 'name' => 'one'], + ['id' => 2, 'name' => 'two'], + ['id' => 3, 'name' => 'three'], ])); - // Verify getFieldCount() returns number of columns in current row - self::assertEquals(2, $resultSet->getFieldCount()); + // Verify count() returns total number of rows + self::assertEquals(3, $resultSet->count()); } - /** - * @throws Exception - */ - public function testNext(): void + public function testCountReturnsCachedResult(): void { - $rows = [ - ['id' => 1, 'name' => 'one'], - ['id' => 2, 'name' => 'two'], - ['id' => 3, 'name' => 'three'], - ]; - $resultSet = $this->createResultSetMock(); - $resultSet->initialize(new ArrayIterator($rows)); + $resultSet->initialize([['id' => 1], ['id' => 2]]); - // Verify next() advances iterator position - self::assertSame(0, $resultSet->key()); - $resultSet->next(); - self::assertSame(1, $resultSet->key()); + $first = $resultSet->count(); + $second = $resultSet->count(); + + self::assertSame(2, $first); + self::assertSame($first, $second); } - /** - * @throws Exception - */ - public function testKey(): void + public function testCountReturnsNullForUncountableDataSource(): void { $resultSet = $this->createResultSetMock(); - $resultSet->initialize(new ArrayIterator([ - ['id' => 1, 'name' => 'one'], - ['id' => 2, 'name' => 'two'], - ['id' => 3, 'name' => 'three'], - ])); - // Verify key() returns current iterator position - $resultSet->next(); - self::assertEquals(1, $resultSet->key()); - $resultSet->next(); - self::assertEquals(2, $resultSet->key()); - $resultSet->next(); - self::assertEquals(3, $resultSet->key()); + $iterator = new NoRewindIterator(new ArrayIterator([['id' => 1]])); + $resultSet->initialize($iterator); + + self::assertNull($resultSet->count()); } /** @@ -214,53 +148,34 @@ public function testCurrent(): void self::assertEquals(['id' => 1, 'name' => 'one'], $resultSet->current()); } - /** - * @throws Exception - */ - public function testValid(): void + public function testCurrentReturnsBufferedDataOnSecondPass(): void { $resultSet = $this->createResultSetMock(); $resultSet->initialize(new ArrayIterator([ ['id' => 1, 'name' => 'one'], ['id' => 2, 'name' => 'two'], - ['id' => 3, 'name' => 'three'], ])); - // Verify valid() returns true when iterator is at valid position - self::assertTrue($resultSet->valid()); - $resultSet->next(); - $resultSet->next(); - $resultSet->next(); - // Verify valid() returns false after iterating past last element - self::assertFalse($resultSet->valid()); - } + $resultSet->buffer(); - /** - * @throws Exception - */ - public function testRewindResetsIteratorPosition(): void - { - $rows = [ - ['id' => 1, 'name' => 'one'], - ['id' => 2, 'name' => 'two'], - ['id' => 3, 'name' => 'three'], - ]; + $firstPass = []; + foreach ($resultSet as $row) { + $firstPass[] = $row; + } - $this->resultSet->initialize(new ArrayIterator($rows)); + $resultSet->rewind(); - // Move forward to ensure position changes - $this->resultSet->next(); - self::assertSame(1, $this->resultSet->key()); + $secondPass = []; + foreach ($resultSet as $row) { + $secondPass[] = $row; + } - // Verify rewind() resets iterator position and current row - $this->resultSet->rewind(); - self::assertSame(0, $this->resultSet->key()); - self::assertEquals($rows[0], $this->resultSet->current()); + self::assertEquals($firstPass, $secondPass); } /** * @throws Exception */ - public function testCount(): void + public function testGetDataSource(): void { $resultSet = $this->createResultSetMock(); $resultSet->initialize(new ArrayIterator([ @@ -268,74 +183,102 @@ public function testCount(): void ['id' => 2, 'name' => 'two'], ['id' => 3, 'name' => 'three'], ])); - // Verify count() returns total number of rows - self::assertEquals(3, $resultSet->count()); + // Verify getDataSource() returns the initialized iterator + self::assertInstanceOf(ArrayIterator::class, $resultSet->getDataSource()); } /** * @throws Exception */ - public function testToArray(): void + public function testGetFieldCount(): void { $resultSet = $this->createResultSetMock(); $resultSet->initialize(new ArrayIterator([ ['id' => 1, 'name' => 'one'], - ['id' => 2, 'name' => 'two'], - ['id' => 3, 'name' => 'three'], ])); - // Verify toArray() returns all rows as array - self::assertEquals( - [ - ['id' => 1, 'name' => 'one'], - ['id' => 2, 'name' => 'two'], - ['id' => 3, 'name' => 'three'], - ], - $resultSet->toArray() - ); + // Verify getFieldCount() returns number of columns in current row + self::assertEquals(2, $resultSet->getFieldCount()); } - public function testCurrentReturnsBufferedDataOnSecondPass(): void + public function testGetFieldCountReturnsCachedValue(): void { $resultSet = $this->createResultSetMock(); - $resultSet->initialize(new ArrayIterator([ + $resultSet->initialize([['a' => 1, 'b' => 2]]); + + $first = $resultSet->getFieldCount(); + $second = $resultSet->getFieldCount(); + + self::assertSame(2, $first); + self::assertSame($first, $second); + } + + public function testGetFieldCountReturnsZeroForEmptyIterator(): void + { + $resultSet = $this->createResultSetMock(); + $resultSet->initialize(new ArrayIterator([])); + + self::assertSame(0, $resultSet->getFieldCount()); + } + + public function testGetFieldCountReturnsZeroWithNoDataSource(): void + { + $resultSet = $this->createResultSetMock(); + + self::assertSame(0, $resultSet->getFieldCount()); + } + + public function testGetFieldCountWithCountableRow(): void + { + $resultSet = $this->createResultSetMock(); + $resultSet->initialize(new ArrayIterator([new ArrayObject(['a' => 1, 'b' => 2, 'c' => 3])])); + + self::assertSame(3, $resultSet->getFieldCount()); + } + + /** + * @throws Exception + */ + public function testInitialize(): void + { + $resultSet = $this->createResultSetMock(); + + // Verify initialize() accepts array data and returns fluent interface + self::assertSame($resultSet, $resultSet->initialize([ ['id' => 1, 'name' => 'one'], ['id' => 2, 'name' => 'two'], + ['id' => 3, 'name' => 'three'], ])); - $resultSet->buffer(); - $firstPass = []; - foreach ($resultSet as $row) { - $firstPass[] = $row; - } - - $resultSet->rewind(); - - $secondPass = []; - foreach ($resultSet as $row) { - $secondPass[] = $row; - } + // Verify invalid data type throws exception + $this->expectException(TypeError::class); + /** @noinspection ALL */ + $resultSet->initialize('foo'); + } - self::assertEquals($firstPass, $secondPass); + /** + * @throws Exception + */ + public function testInitializeDoesNotCallCount(): void + { + $resultSet = $this->createResultSetMock(); + $result = $this->getMockBuilder(ResultInterface::class)->onlyMethods([])->getMock(); + $result->expects($this->never())->method('count'); + // Initialize with result and verify count() is never called + $resultSet->initialize($result); } - public function testToArrayConvertsArrayObjectsViaGetArrayCopy(): void + /** + * @throws Exception + */ + public function testInitializeResetsBufferWhenAlreadyBuffered(): void { $resultSet = $this->createResultSetMock(); - $resultSet->initialize([ - new ArrayObject(['id' => 1, 'name' => 'one']), - ]); - - $result = $resultSet->toArray(); - - self::assertSame([['id' => 1, 'name' => 'one']], $result); - } + $resultSet->initialize(new ArrayIterator([['id' => 1]])); + $resultSet->buffer(); - public function testGetFieldCountReturnsZeroForEmptyIterator(): void - { - $resultSet = $this->createResultSetMock(); - $resultSet->initialize(new ArrayIterator([])); + $resultSet->initialize(new ArrayIterator([['id' => 2]])); - self::assertSame(0, $resultSet->getFieldCount()); + self::assertSame(2, $resultSet->current()['id']); } public function testInitializeWithBufferedResultInterface(): void @@ -350,34 +293,65 @@ public function testInitializeWithBufferedResultInterface(): void self::assertTrue($resultSet->isBuffered()); } - public function testCountReturnsNullForUncountableDataSource(): void + /** + * @throws Exception + */ + public function testInitializeWithEmptyArray(): void { $resultSet = $this->createResultSetMock(); - $iterator = new NoRewindIterator(new ArrayIterator([['id' => 1]])); - $resultSet->initialize($iterator); + // Verify initialize() accepts empty array + self::assertSame($resultSet, $resultSet->initialize([])); + } - self::assertNull($resultSet->count()); + /** + * @throws Exception + */ + public function testInitializeWithIteratorAggregate(): void + { + $resultSet = $this->createResultSetMock(); + $aggregate = new class implements IteratorAggregate { + public function getIterator(): ArrayIterator + { + return new ArrayIterator([['id' => 1], ['id' => 2]]); + } + }; + + $resultSet->initialize($aggregate); + + self::assertSame(1, $resultSet->current()['id']); } - public function testValidReturnsFalseAfterLastElement(): void + /** + * @throws Exception + */ + public function testInitializeWithResultInterfaceRewindsWhenBuffered(): void { $resultSet = $this->createResultSetMock(); - $resultSet->initialize(new ArrayIterator([ - ['id' => 1], - ])); + $resultSet->initialize(new ArrayIterator([['id' => 1]])); + $resultSet->buffer(); - self::assertTrue($resultSet->valid()); - $resultSet->next(); - self::assertFalse($resultSet->valid()); + $result = $this->createMock(ResultInterface::class); + $result->method('getFieldCount')->willReturn(2); + $result->method('isBuffered')->willReturn(false); + $result->expects(self::once())->method('rewind'); + + $resultSet->initialize($result); + } + + public function testIsBuffered(): void + { + $resultSet = $this->createResultSetMock(); + // Verify buffering is disabled by default + self::assertFalse($resultSet->isBuffered()); + $resultSet->buffer(); + // Verify buffering is enabled after buffer() call + self::assertTrue($resultSet->isBuffered()); } /** - * Test multiple iterations with buffer - * * @throws Exception */ - #[Group('issue-6845')] - public function testBufferIterations(): void + public function testKey(): void { $resultSet = $this->createResultSetMock(); $resultSet->initialize(new ArrayIterator([ @@ -385,25 +359,13 @@ public function testBufferIterations(): void ['id' => 2, 'name' => 'two'], ['id' => 3, 'name' => 'three'], ])); - $resultSet->buffer(); - - // Iterate through rows and verify data - $data = $resultSet->current(); - self::assertEquals(1, $data['id']); + // Verify key() returns current iterator position $resultSet->next(); - $data = $resultSet->current(); - self::assertEquals(2, $data['id']); - - // Rewind and iterate again to verify buffering allows rewind - $resultSet->rewind(); - $data = $resultSet->current(); - self::assertEquals(1, $data['id']); + self::assertEquals(1, $resultSet->key()); $resultSet->next(); - $data = $resultSet->current(); - self::assertEquals(2, $data['id']); + self::assertEquals(2, $resultSet->key()); $resultSet->next(); - $data = $resultSet->current(); - self::assertEquals(3, $data['id']); + self::assertEquals(3, $resultSet->key()); } /** @@ -425,7 +387,7 @@ public function testMultipleRewindBufferIterations(): void assert($stub instanceof PDOStatement); // to suppress IDE type warnings $stub->expects($this->any()) ->method('fetch') - ->willReturnCallback(function () use ($data) { + ->willReturnCallback(static function () use ($data) { $r = $data->current(); $data->next(); return $r; @@ -463,38 +425,47 @@ public function testMultipleRewindBufferIterations(): void /** * @throws Exception */ - public function testInitializeResetsBufferWhenAlreadyBuffered(): void + public function testNext(): void { - $resultSet = $this->createResultSetMock(); - $resultSet->initialize(new ArrayIterator([['id' => 1]])); - $resultSet->buffer(); + $rows = [ + ['id' => 1, 'name' => 'one'], + ['id' => 2, 'name' => 'two'], + ['id' => 3, 'name' => 'three'], + ]; - $resultSet->initialize(new ArrayIterator([['id' => 2]])); + $resultSet = $this->createResultSetMock(); + $resultSet->initialize(new ArrayIterator($rows)); - self::assertSame(2, $resultSet->current()['id']); + // Verify next() advances iterator position + self::assertSame(0, $resultSet->key()); + $resultSet->next(); + self::assertSame(1, $resultSet->key()); } /** * @throws Exception */ - public function testInitializeWithResultInterfaceRewindsWhenBuffered(): void + public function testRewindResetsIteratorPosition(): void { - $resultSet = $this->createResultSetMock(); - $resultSet->initialize(new ArrayIterator([['id' => 1]])); - $resultSet->buffer(); + $rows = [ + ['id' => 1, 'name' => 'one'], + ['id' => 2, 'name' => 'two'], + ['id' => 3, 'name' => 'three'], + ]; - $result = $this->createMock(ResultInterface::class); - $result->method('getFieldCount')->willReturn(2); - $result->method('isBuffered')->willReturn(false); - $result->expects(self::once())->method('rewind'); + $this->resultSet->initialize(new ArrayIterator($rows)); - $resultSet->initialize($result); + // Move forward to ensure position changes + $this->resultSet->next(); + self::assertSame(1, $this->resultSet->key()); + + // Verify rewind() resets iterator position and current row + $this->resultSet->rewind(); + self::assertSame(0, $this->resultSet->key()); + self::assertEquals($rows[0], $this->resultSet->current()); } - /** - * @throws Exception - */ - public function testInitializeWithIteratorAggregate(): void + public function testRewindWithNonIteratorDataSource(): void { $resultSet = $this->createResultSetMock(); $aggregate = new class implements IteratorAggregate { @@ -505,89 +476,118 @@ public function getIterator(): ArrayIterator }; $resultSet->initialize($aggregate); + $resultSet->next(); + $resultSet->rewind(); - self::assertSame(1, $resultSet->current()['id']); + self::assertSame(0, $resultSet->key()); } - public function testGetFieldCountReturnsCachedValue(): void + /** + * @throws Exception + */ + public function testToArray(): void { $resultSet = $this->createResultSetMock(); - $resultSet->initialize([['a' => 1, 'b' => 2]]); - - $first = $resultSet->getFieldCount(); - $second = $resultSet->getFieldCount(); - - self::assertSame(2, $first); - self::assertSame($first, $second); + $resultSet->initialize(new ArrayIterator([ + ['id' => 1, 'name' => 'one'], + ['id' => 2, 'name' => 'two'], + ['id' => 3, 'name' => 'three'], + ])); + // Verify toArray() returns all rows as array + self::assertEquals( + [ + ['id' => 1, 'name' => 'one'], + ['id' => 2, 'name' => 'two'], + ['id' => 3, 'name' => 'three'], + ], + $resultSet->toArray(), + ); } - public function testGetFieldCountReturnsZeroWithNoDataSource(): void + public function testToArrayConvertsArrayObjectsViaGetArrayCopy(): void { $resultSet = $this->createResultSetMock(); + $resultSet->initialize([ + new ArrayObject(['id' => 1, 'name' => 'one']), + ]); - self::assertSame(0, $resultSet->getFieldCount()); + $result = $resultSet->toArray(); + + self::assertSame([['id' => 1, 'name' => 'one']], $result); } - public function testGetFieldCountWithCountableRow(): void + public function testToArrayThrowsOnNonCastableRows(): void { $resultSet = $this->createResultSetMock(); - $resultSet->initialize(new ArrayIterator([new ArrayObject(['a' => 1, 'b' => 2, 'c' => 3])])); + $resultSet->initialize(new ArrayIterator([new stdClass()])); - self::assertSame(3, $resultSet->getFieldCount()); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('cannot be cast to an array'); + $resultSet->toArray(); } - public function testValidWithNonIteratorDataSource(): void + /** + * @throws Exception + */ + public function testValid(): void { $resultSet = $this->createResultSetMock(); - $aggregate = new class implements IteratorAggregate { - public function getIterator(): ArrayIterator - { - return new ArrayIterator([['id' => 1]]); - } - }; + $resultSet->initialize(new ArrayIterator([ + ['id' => 1, 'name' => 'one'], + ['id' => 2, 'name' => 'two'], + ['id' => 3, 'name' => 'three'], + ])); + // Verify valid() returns true when iterator is at valid position + self::assertTrue($resultSet->valid()); + $resultSet->next(); + $resultSet->next(); + $resultSet->next(); + // Verify valid() returns false after iterating past last element + self::assertFalse($resultSet->valid()); + } - $resultSet->initialize($aggregate); - $resultSet->rewind(); + public function testValidReturnsFalseAfterLastElement(): void + { + $resultSet = $this->createResultSetMock(); + $resultSet->initialize(new ArrayIterator([ + ['id' => 1], + ])); self::assertTrue($resultSet->valid()); + $resultSet->next(); + self::assertFalse($resultSet->valid()); } - public function testRewindWithNonIteratorDataSource(): void + public function testValidWithNonIteratorDataSource(): void { $resultSet = $this->createResultSetMock(); $aggregate = new class implements IteratorAggregate { public function getIterator(): ArrayIterator { - return new ArrayIterator([['id' => 1], ['id' => 2]]); + return new ArrayIterator([['id' => 1]]); } }; $resultSet->initialize($aggregate); - $resultSet->next(); $resultSet->rewind(); - self::assertSame(0, $resultSet->key()); + self::assertTrue($resultSet->valid()); } - public function testCountReturnsCachedResult(): void + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + #[Override] + protected function setUp(): void { - $resultSet = $this->createResultSetMock(); - $resultSet->initialize([['id' => 1], ['id' => 2]]); - - $first = $resultSet->count(); - $second = $resultSet->count(); - - self::assertSame(2, $first); - self::assertSame($first, $second); + $this->resultSet = $this->createResultSetMock(); } - public function testToArrayThrowsOnNonCastableRows(): void + private function createResultSetMock(): MockObject|AbstractResultSet { - $resultSet = $this->createResultSetMock(); - $resultSet->initialize(new ArrayIterator([new stdClass()])); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('cannot be cast to an array'); - $resultSet->toArray(); + return $this->getMockBuilder(AbstractResultSet::class) + ->onlyMethods(['setRowPrototype', 'getRowPrototype']) + ->getMock(); } } diff --git a/test/unit/ResultSet/HydratingResultSetTest.php b/test/unit/ResultSet/HydratingResultSetTest.php index dc63d2683..bc0d56f7b 100644 --- a/test/unit/ResultSet/HydratingResultSetTest.php +++ b/test/unit/ResultSet/HydratingResultSetTest.php @@ -33,36 +33,74 @@ final class HydratingResultSetTest extends TestCase private string $classMethodsHydratorClass; - #[Override] - protected function setUp(): void + public function testConstructorDefaultsToArraySerializableHydrator(): void { - $this->arraySerializableHydratorClass = ArraySerializableHydrator::class; - $this->classMethodsHydratorClass = ClassMethodsHydrator::class; + $hydratingRs = new HydratingResultSet(); + + self::assertInstanceOf(ArraySerializableHydrator::class, $hydratingRs->getHydrator()); } - public function testSetObjectPrototype(): void + public function testCurrentDisablesBufferingImplicitly(): void { - $prototype1 = new stdClass(); - $prototype1->property1 = 'value1'; - $prototype2 = new stdClass(); - $prototype2->property2 = 'value2'; - $hydratingRs = new HydratingResultSet(); + $hydratingRs = new HydratingResultSet(); + $hydratingRs->initialize(new ArrayIterator([ + ['id' => 1], + ])); - // First mutation - $result = $hydratingRs->setObjectPrototype($prototype1); + $hydratingRs->current(); - // Verify fluent interface - self::assertSame($hydratingRs, $result); + $this->expectException(RuntimeException::class); + $hydratingRs->buffer(); + } - // Verify the first mutation occurred - self::assertSame($prototype1, $hydratingRs->getObjectPrototype()); + /** + * @throws Exception + */ + public function testCurrentDoesnotHasData(): void + { + $hydratingRs = new HydratingResultSet(); + $hydratingRs->initialize([]); - // Second mutation to verify mutability - $hydratingRs->setObjectPrototype($prototype2); + // Verify current() returns null when no data exists + $result = $hydratingRs->current(); + self::assertNull($result); + } - // Verify the instance was actually mutated - self::assertSame($prototype2, $hydratingRs->getObjectPrototype()); - self::assertNotSame($prototype1, $hydratingRs->getObjectPrototype()); + /** + * @throws Exception + */ + public function testCurrentHasData(): void + { + $hydratingRs = new HydratingResultSet(); + $hydratingRs->initialize([ + ['id' => 1, 'name' => 'one'], + ]); + // Verify current() returns hydrated object when data exists + $obj = $hydratingRs->current(); + self::assertInstanceOf('ArrayObject', $obj); + } + + public function testCurrentWithBufferReturnsBufferedObject(): void + { + $hydratingRs = new HydratingResultSet(); + $hydratingRs->initialize(new ArrayIterator([ + ['id' => 1, 'name' => 'one'], + ['id' => 2, 'name' => 'two'], + ])); + $hydratingRs->buffer(); + + $first = $hydratingRs->current(); + $hydratingRs->rewind(); + $buffered = $hydratingRs->current(); + + self::assertSame($first, $buffered); + } + + public function testGetHydrator(): void + { + $hydratingRs = new HydratingResultSet(); + // Verify getHydrator() returns default ArraySerializable hydrator + self::assertInstanceOf($this->arraySerializableHydratorClass, $hydratingRs->getHydrator()); } public function testGetObjectPrototype(): void @@ -72,6 +110,13 @@ public function testGetObjectPrototype(): void self::assertInstanceOf('ArrayObject', $hydratingRs->getObjectPrototype()); } + public function testGetRowPrototypeReturnsDefaultArrayObject(): void + { + $hydratingRs = new HydratingResultSet(); + + self::assertInstanceOf(ArrayObject::class, $hydratingRs->getRowPrototype()); + } + public function testSetHydrator(): void { $hydratingRs = new HydratingResultSet(); @@ -98,60 +143,29 @@ public function testSetHydrator(): void self::assertNotSame($hydrator1, $hydratingRs->getHydrator()); } - public function testGetHydrator(): void - { - $hydratingRs = new HydratingResultSet(); - // Verify getHydrator() returns default ArraySerializable hydrator - self::assertInstanceOf($this->arraySerializableHydratorClass, $hydratingRs->getHydrator()); - } - - /** - * @throws Exception - */ - public function testCurrentHasData(): void + public function testSetObjectPrototype(): void { - $hydratingRs = new HydratingResultSet(); - $hydratingRs->initialize([ - ['id' => 1, 'name' => 'one'], - ]); - // Verify current() returns hydrated object when data exists - $obj = $hydratingRs->current(); - self::assertInstanceOf('ArrayObject', $obj); - } + $prototype1 = new stdClass(); + $prototype1->property1 = 'value1'; + $prototype2 = new stdClass(); + $prototype2->property2 = 'value2'; + $hydratingRs = new HydratingResultSet(); - /** - * @throws Exception - */ - public function testCurrentDoesnotHasData(): void - { - $hydratingRs = new HydratingResultSet(); - $hydratingRs->initialize([]); + // First mutation + $result = $hydratingRs->setObjectPrototype($prototype1); - // Verify current() returns null when no data exists - $result = $hydratingRs->current(); - self::assertNull($result); - } + // Verify fluent interface + self::assertSame($hydratingRs, $result); - /** - * @throws Exception - * @todo Implement testToArray(). - */ - public function testToArray(): void - { - $hydratingRs = new HydratingResultSet(); - $hydratingRs->initialize([ - ['id' => 1, 'name' => 'one'], - ]); - // Verify toArray() returns array of hydrated objects - $obj = $hydratingRs->toArray(); - self::assertIsArray($obj); - } + // Verify the first mutation occurred + self::assertSame($prototype1, $hydratingRs->getObjectPrototype()); - public function testConstructorDefaultsToArraySerializableHydrator(): void - { - $hydratingRs = new HydratingResultSet(); + // Second mutation to verify mutability + $hydratingRs->setObjectPrototype($prototype2); - self::assertInstanceOf(ArraySerializableHydrator::class, $hydratingRs->getHydrator()); + // Verify the instance was actually mutated + self::assertSame($prototype2, $hydratingRs->getObjectPrototype()); + self::assertNotSame($prototype1, $hydratingRs->getObjectPrototype()); } public function testSetRowPrototypeStoresPrototype(): void @@ -165,27 +179,19 @@ public function testSetRowPrototypeStoresPrototype(): void self::assertSame($prototype, $hydratingRs->getRowPrototype()); } - public function testGetRowPrototypeReturnsDefaultArrayObject(): void - { - $hydratingRs = new HydratingResultSet(); - - self::assertInstanceOf(ArrayObject::class, $hydratingRs->getRowPrototype()); - } - - public function testCurrentWithBufferReturnsBufferedObject(): void + /** + * @throws Exception + * @todo Implement testToArray(). + */ + public function testToArray(): void { $hydratingRs = new HydratingResultSet(); - $hydratingRs->initialize(new ArrayIterator([ + $hydratingRs->initialize([ ['id' => 1, 'name' => 'one'], - ['id' => 2, 'name' => 'two'], - ])); - $hydratingRs->buffer(); - - $first = $hydratingRs->current(); - $hydratingRs->rewind(); - $buffered = $hydratingRs->current(); - - self::assertSame($first, $buffered); + ]); + // Verify toArray() returns array of hydrated objects + $obj = $hydratingRs->toArray(); + self::assertIsArray($obj); } public function testToArrayUsesHydratorExtract(): void @@ -202,16 +208,10 @@ public function testToArrayUsesHydratorExtract(): void self::assertSame(1, $result[0]['id']); } - public function testCurrentDisablesBufferingImplicitly(): void + #[Override] + protected function setUp(): void { - $hydratingRs = new HydratingResultSet(); - $hydratingRs->initialize(new ArrayIterator([ - ['id' => 1], - ])); - - $hydratingRs->current(); - - $this->expectException(RuntimeException::class); - $hydratingRs->buffer(); + $this->arraySerializableHydratorClass = ArraySerializableHydrator::class; + $this->classMethodsHydratorClass = ClassMethodsHydrator::class; } } diff --git a/test/unit/ResultSet/ResultSetIntegrationTest.php b/test/unit/ResultSet/ResultSetIntegrationTest.php index 4987fb20b..e5f63094f 100644 --- a/test/unit/ResultSet/ResultSetIntegrationTest.php +++ b/test/unit/ResultSet/ResultSetIntegrationTest.php @@ -37,56 +37,6 @@ final class ResultSetIntegrationTest extends TestCase { protected ResultSet $resultSet; - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - #[Override] - protected function setUp(): void - { - $this->resultSet = new ResultSet(); - } - - public function testRowObjectPrototypeIsPopulatedByRowObjectByDefault(): void - { - // Verify default row object prototype is ArrayObject - $row = $this->resultSet->getArrayObjectPrototype(); - self::assertInstanceOf('ArrayObject', $row); - } - - public function testRowObjectPrototypeIsMutable(): void - { - $row1 = new ArrayObject(['test1' => 'value1']); - $row2 = new ArrayObject(['test2' => 'value2']); - - // First mutation - $this->resultSet->setArrayObjectPrototype($row1); - - // Verify the first mutation occurred - self::assertSame($row1, $this->resultSet->getArrayObjectPrototype()); - - // Second mutation to verify mutability - $this->resultSet->setArrayObjectPrototype($row2); - - // Verify the instance was actually mutated - self::assertSame($row2, $this->resultSet->getArrayObjectPrototype()); - self::assertNotSame($row1, $this->resultSet->getArrayObjectPrototype()); - } - - public function testRowObjectPrototypeMayBePassedToConstructor(): void - { - $row = new ArrayObject(); - // Verify prototype can be passed to constructor - $resultSet = new ResultSet(ResultSet::TYPE_ARRAYOBJECT, $row); - self::assertSame($row, $resultSet->getArrayObjectPrototype()); - } - - public function testReturnTypeIsObjectByDefault(): void - { - // Verify default return type is ArrayObject - self::assertEquals(ResultSetReturnType::ArrayObject, $this->resultSet->getReturnType()); - } - /** @psalm-return array */ public static function invalidReturnTypes(): array { @@ -100,29 +50,32 @@ public static function invalidReturnTypes(): array ]; } - #[DataProvider('invalidReturnTypes')] - public function testSettingInvalidReturnTypeRaisesException(mixed $type): void + public function getArrayDataSource(int $count): ArrayIterator { - // Verify invalid return type throws TypeError - $this->expectException(TypeError::class); - new ResultSet(ResultSet::TYPE_ARRAYOBJECT, $type); - } + $array = []; + for ($i = 0; $i < $count; $i++) { + $array[] = [ + 'id' => $i, + 'title' => "title {$i}", + ]; + } - public function testDataSourceIsNullByDefault(): void - { - // Verify data source is null before initialization - self::assertNull($this->resultSet->getDataSource()); + return new ArrayIterator($array); } /** + * @throws Exception * @throws \Exception */ - public function testCanProvideIteratorAsDataSource(): void + public function testBufferCalledAfterIterationThrowsException(): void { - $it = new SplStack(); - // Initialize with iterator and verify it is stored as data source - $this->resultSet->initialize($it); - self::assertSame($it, $this->resultSet->getDataSource()); + $this->resultSet->initialize($this->createMock(ResultInterface::class)); + $this->resultSet->current(); + + // Verify buffer() throws exception when called after iteration has started + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Buffering must be enabled before iteration is started'); + $this->resultSet->buffer(); } /** @@ -161,77 +114,12 @@ public function testCanProvideIteratorAggregateAsDataSource(): void /** * @throws \Exception */ - #[DataProvider('invalidReturnTypes')] - public function testInvalidDataSourceRaisesException(mixed $dataSource): void - { - if (is_array($dataSource)) { - $this->expectNotToPerformAssertions(); - // this is valid - return; - } - - // Verify invalid data source throws TypeError - $this->expectException(TypeError::class); - $this->resultSet->initialize($dataSource); - } - - public function testFieldCountIsZeroWithNoDataSourcePresent(): void - { - // Verify field count is 0 when no data source is set - self::assertEquals(0, $this->resultSet->getFieldCount()); - } - - public function getArrayDataSource(int $count): ArrayIterator - { - $array = []; - for ($i = 0; $i < $count; $i++) { - $array[] = [ - 'id' => $i, - 'title' => 'title ' . $i, - ]; - } - - return new ArrayIterator($array); - } - - /** - * @throws \Exception - */ - public function testFieldCountRepresentsNumberOfFieldsInARowOfData(): void - { - $resultSet = new ResultSet(ResultSet::TYPE_ARRAY); - $dataSource = $this->getArrayDataSource(10); - // Verify field count matches number of columns in row data - $resultSet->initialize($dataSource); - self::assertEquals(2, $resultSet->getFieldCount()); - } - - /** - * @throws \Exception - */ - public function testWhenReturnTypeIsArrayThenIterationReturnsArrays(): void - { - $resultSet = new ResultSet(ResultSet::TYPE_ARRAY); - $dataSource = $this->getArrayDataSource(10); - $resultSet->initialize($dataSource); - // Iterate and verify each row is returned as array - foreach ($resultSet as $index => $row) { - self::assertEquals($dataSource[$index], $row); - } - } - - /** - * @throws \Exception - */ - public function testWhenReturnTypeIsObjectThenIterationReturnsRowObjects(): void + public function testCanProvideIteratorAsDataSource(): void { - $dataSource = $this->getArrayDataSource(10); - $this->resultSet->initialize($dataSource); - // Iterate and verify each row is returned as ArrayObject - foreach ($this->resultSet as $index => $row) { - self::assertInstanceOf('ArrayObject', $row); - self::assertEquals($dataSource[$index], $row->getArrayCopy()); - } + $it = new SplStack(); + // Initialize with iterator and verify it is stored as data source + $this->resultSet->initialize($it); + self::assertSame($it, $this->resultSet->getDataSource()); } /** @@ -247,36 +135,57 @@ public function testCountReturnsCountOfRows(): void self::assertEquals($count, $this->resultSet->count()); } - /** - * @throws RandomException - * @throws \Exception - */ - public function testToArrayRaisesExceptionForRowsThatAreNotArraysOrArrayCastable(): void + public function testCurrentClonesRowPrototypeOnEachCall(): void { - $count = random_int(3, 75); - $dataSource = $this->getArrayDataSource($count); - foreach ($dataSource as $index => $row) { - $dataSource[$index] = (object) $row; - } + $resultSet = new ResultSet(ResultSetReturnType::ArrayObject); + $resultSet->initialize([ + ['id' => 1, 'name' => 'one'], + ['id' => 2, 'name' => 'two'], + ]); - // Verify toArray() throws exception for non-array-castable objects - $this->resultSet->initialize($dataSource); - $this->expectException(RuntimeException::class); - $this->resultSet->toArray(); + $first = $resultSet->current(); + $resultSet->next(); + $second = $resultSet->current(); + + self::assertNotSame($first, $second); + } + + public function testCurrentReturnsArrayObjectWhenReturnTypeIsArrayObject(): void + { + $resultSet = new ResultSet(ResultSetReturnType::ArrayObject); + $resultSet->initialize([['id' => 1, 'name' => 'one']]); + + $current = $resultSet->current(); + + self::assertInstanceOf(ArrayObject::class, $current); + self::assertSame(1, $current['id']); + } + + public function testCurrentReturnsArrayWhenReturnTypeIsArray(): void + { + $resultSet = new ResultSet(ResultSetReturnType::Array); + $resultSet->initialize([['id' => 1, 'name' => 'one']]); + + $current = $resultSet->current(); + + self::assertIsArray($current); + self::assertSame(1, $current['id']); } /** - * @throws RandomException + * @throws Exception * @throws \Exception */ - public function testToArrayCreatesArrayOfArraysRepresentingRows(): void + public function testCurrentReturnsNullForNonExistingValues(): void { - $count = random_int(3, 75); - $dataSource = $this->getArrayDataSource($count); - // Verify toArray() returns array representation of all rows - $this->resultSet->initialize($dataSource); - $test = $this->resultSet->toArray(); - self::assertEquals($dataSource->getArrayCopy(), $test, var_export($test, true)); + $mockResult = $this->createMock(ResultInterface::class); + $mockResult->expects($this->once())->method('current')->willReturn('Not an Array'); + + $this->resultSet->initialize($mockResult); + $this->resultSet->buffer(); + + // Verify current() returns null when data source returns non-array value + self::assertNull($this->resultSet->current()); } /** @@ -296,86 +205,177 @@ public function testCurrentWithBufferingCallsDataSourceCurrentOnce(): void $this->resultSet->current(); } + public function testDataSourceIsNullByDefault(): void + { + // Verify data source is null before initialization + self::assertNull($this->resultSet->getDataSource()); + } + + public function testFieldCountIsZeroWithNoDataSourcePresent(): void + { + // Verify field count is 0 when no data source is set + self::assertEquals(0, $this->resultSet->getFieldCount()); + } + /** - * @throws Exception * @throws \Exception */ - public function testBufferCalledAfterIterationThrowsException(): void + public function testFieldCountRepresentsNumberOfFieldsInARowOfData(): void { - $this->resultSet->initialize($this->createMock(ResultInterface::class)); - $this->resultSet->current(); + $resultSet = new ResultSet(ResultSet::TYPE_ARRAY); + $dataSource = $this->getArrayDataSource(10); + // Verify field count matches number of columns in row data + $resultSet->initialize($dataSource); + self::assertEquals(2, $resultSet->getFieldCount()); + } - // Verify buffer() throws exception when called after iteration has started - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Buffering must be enabled before iteration is started'); - $this->resultSet->buffer(); + public function testGetArrayObjectPrototypeDelegatesToGetRowPrototype(): void + { + self::assertSame( + $this->resultSet->getRowPrototype(), + $this->resultSet->getArrayObjectPrototype(), + ); + } + + public function testGetReturnTypeReturnsArrayWhenSetToArray(): void + { + $resultSet = new ResultSet(ResultSetReturnType::Array); + + self::assertSame(ResultSetReturnType::Array, $resultSet->getReturnType()); } /** - * @throws Exception * @throws \Exception */ - public function testCurrentReturnsNullForNonExistingValues(): void + #[DataProvider('invalidReturnTypes')] + public function testInvalidDataSourceRaisesException(mixed $dataSource): void { - $mockResult = $this->createMock(ResultInterface::class); - $mockResult->expects($this->once())->method('current')->willReturn("Not an Array"); + if (is_array($dataSource)) { + $this->expectNotToPerformAssertions(); + // this is valid + return; + } - $this->resultSet->initialize($mockResult); - $this->resultSet->buffer(); + // Verify invalid data source throws TypeError + $this->expectException(TypeError::class); + $this->resultSet->initialize($dataSource); + } - // Verify current() returns null when data source returns non-array value - self::assertNull($this->resultSet->current()); + public function testReturnTypeIsObjectByDefault(): void + { + // Verify default return type is ArrayObject + self::assertEquals(ResultSetReturnType::ArrayObject, $this->resultSet->getReturnType()); } - public function testCurrentReturnsArrayObjectWhenReturnTypeIsArrayObject(): void + public function testRowObjectPrototypeIsMutable(): void { - $resultSet = new ResultSet(ResultSetReturnType::ArrayObject); - $resultSet->initialize([['id' => 1, 'name' => 'one']]); + $row1 = new ArrayObject(['test1' => 'value1']); + $row2 = new ArrayObject(['test2' => 'value2']); - $current = $resultSet->current(); + // First mutation + $this->resultSet->setArrayObjectPrototype($row1); - self::assertInstanceOf(ArrayObject::class, $current); - self::assertSame(1, $current['id']); + // Verify the first mutation occurred + self::assertSame($row1, $this->resultSet->getArrayObjectPrototype()); + + // Second mutation to verify mutability + $this->resultSet->setArrayObjectPrototype($row2); + + // Verify the instance was actually mutated + self::assertSame($row2, $this->resultSet->getArrayObjectPrototype()); + self::assertNotSame($row1, $this->resultSet->getArrayObjectPrototype()); } - public function testCurrentReturnsArrayWhenReturnTypeIsArray(): void + public function testRowObjectPrototypeIsPopulatedByRowObjectByDefault(): void { - $resultSet = new ResultSet(ResultSetReturnType::Array); - $resultSet->initialize([['id' => 1, 'name' => 'one']]); + // Verify default row object prototype is ArrayObject + $row = $this->resultSet->getArrayObjectPrototype(); + self::assertInstanceOf('ArrayObject', $row); + } - $current = $resultSet->current(); + public function testRowObjectPrototypeMayBePassedToConstructor(): void + { + $row = new ArrayObject(); + // Verify prototype can be passed to constructor + $resultSet = new ResultSet(ResultSet::TYPE_ARRAYOBJECT, $row); + self::assertSame($row, $resultSet->getArrayObjectPrototype()); + } - self::assertIsArray($current); - self::assertSame(1, $current['id']); + #[DataProvider('invalidReturnTypes')] + public function testSettingInvalidReturnTypeRaisesException(mixed $type): void + { + // Verify invalid return type throws TypeError + $this->expectException(TypeError::class); + new ResultSet(ResultSet::TYPE_ARRAYOBJECT, $type); } - public function testCurrentClonesRowPrototypeOnEachCall(): void + /** + * @throws RandomException + * @throws \Exception + */ + public function testToArrayCreatesArrayOfArraysRepresentingRows(): void { - $resultSet = new ResultSet(ResultSetReturnType::ArrayObject); - $resultSet->initialize([ - ['id' => 1, 'name' => 'one'], - ['id' => 2, 'name' => 'two'], - ]); + $count = random_int(3, 75); + $dataSource = $this->getArrayDataSource($count); + // Verify toArray() returns array representation of all rows + $this->resultSet->initialize($dataSource); + $test = $this->resultSet->toArray(); + self::assertEquals($dataSource->getArrayCopy(), $test, var_export($test, true)); + } - $first = $resultSet->current(); - $resultSet->next(); - $second = $resultSet->current(); + /** + * @throws RandomException + * @throws \Exception + */ + public function testToArrayRaisesExceptionForRowsThatAreNotArraysOrArrayCastable(): void + { + $count = random_int(3, 75); + $dataSource = $this->getArrayDataSource($count); + foreach ($dataSource as $index => $row) { + $dataSource[$index] = (object) $row; + } - self::assertNotSame($first, $second); + // Verify toArray() throws exception for non-array-castable objects + $this->resultSet->initialize($dataSource); + $this->expectException(RuntimeException::class); + $this->resultSet->toArray(); } - public function testGetReturnTypeReturnsArrayWhenSetToArray(): void + /** + * @throws \Exception + */ + public function testWhenReturnTypeIsArrayThenIterationReturnsArrays(): void { - $resultSet = new ResultSet(ResultSetReturnType::Array); + $resultSet = new ResultSet(ResultSet::TYPE_ARRAY); + $dataSource = $this->getArrayDataSource(10); + $resultSet->initialize($dataSource); + // Iterate and verify each row is returned as array + foreach ($resultSet as $index => $row) { + self::assertEquals($dataSource[$index], $row); + } + } - self::assertSame(ResultSetReturnType::Array, $resultSet->getReturnType()); + /** + * @throws \Exception + */ + public function testWhenReturnTypeIsObjectThenIterationReturnsRowObjects(): void + { + $dataSource = $this->getArrayDataSource(10); + $this->resultSet->initialize($dataSource); + // Iterate and verify each row is returned as ArrayObject + foreach ($this->resultSet as $index => $row) { + self::assertInstanceOf('ArrayObject', $row); + self::assertEquals($dataSource[$index], $row->getArrayCopy()); + } } - public function testGetArrayObjectPrototypeDelegatesToGetRowPrototype(): void + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + #[Override] + protected function setUp(): void { - self::assertSame( - $this->resultSet->getRowPrototype(), - $this->resultSet->getArrayObjectPrototype() - ); + $this->resultSet = new ResultSet(); } } diff --git a/test/unit/RowGateway/AbstractRowGatewayTest.php b/test/unit/RowGateway/AbstractRowGatewayTest.php index aa63b7bd5..cb41a0e3e 100644 --- a/test/unit/RowGateway/AbstractRowGatewayTest.php +++ b/test/unit/RowGateway/AbstractRowGatewayTest.php @@ -55,56 +55,24 @@ final class AbstractRowGatewayTest extends TestCase /** @var ResultInterface&MockObject */ protected ResultInterface|MockObject $mockResult; - /** - * @throws ReflectionException - * @throws Exception - */ - #[Override] - protected function setUp(): void - { - $mockResult = $this->getMockBuilder(ResultInterface::class)->getMock(); - $mockResult->expects($this->any())->method('getAffectedRows')->willReturn(1); - $this->mockResult = $mockResult; - $mockStatement = $this->getMockBuilder(StatementInterface::class)->getMock(); - $mockStatement->expects($this->any())->method('execute')->willReturn($mockResult); - $mockConnection = $this->getMockBuilder(ConnectionInterface::class)->getMock(); - $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); - $mockDriver->expects($this->any())->method('createStatement')->willReturn($mockStatement); - $mockDriver->expects($this->any())->method('getConnection')->willReturn($mockConnection); - - $this->mockAdapter = $this->getMockBuilder(Adapter::class) - ->onlyMethods([]) - ->setConstructorArgs( - [ - $mockDriver, - new TrustingSql92Platform(), - ] - )->getMock(); - - $this->rowGateway = $this->getMockBuilder(AbstractRowGateway::class)->onlyMethods([])->getMock(); - - $rgPropertyValues = [ - 'primaryKeyColumn' => ['id'], - 'table' => 'foo', - 'sql' => new Sql($this->mockAdapter), - ]; - $this->setRowGatewayState($rgPropertyValues); - } - - public function testOffsetSet(): void + // @codingStandardsIgnoreStart + public function test__get(): void { - $this->rowGateway['testColumn'] = 'test'; + // @codingStandardsIgnoreEnd + $this->rowGateway->testColumn = 'test'; self::assertEquals('test', $this->rowGateway->testColumn); self::assertEquals('test', $this->rowGateway['testColumn']); } // @codingStandardsIgnoreStart - public function test__set(): void + public function test__getThrowsExceptionForInvalidColumn(): void { // @codingStandardsIgnoreEnd - $this->rowGateway->testColumn = 'test'; - self::assertEquals('test', $this->rowGateway->testColumn); - self::assertEquals('test', $this->rowGateway['testColumn']); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Not a valid column in this row'); + + /** @phpstan-ignore property.notFound, expr.resultUnused */ + $this->rowGateway->nonExistentColumn; } // @codingStandardsIgnoreStart @@ -116,11 +84,13 @@ public function test__isset(): void self::assertTrue(isset($this->rowGateway->foo)); } - public function testOffsetExists(): void + // @codingStandardsIgnoreStart + public function test__set(): void { - self::assertFalse(isset($this->rowGateway['foo'])); - $this->rowGateway['foo'] = 'bar'; - self::assertTrue(isset($this->rowGateway['foo'])); + // @codingStandardsIgnoreEnd + $this->rowGateway->testColumn = 'test'; + self::assertEquals('test', $this->rowGateway->testColumn); + self::assertEquals('test', $this->rowGateway['testColumn']); } // @codingStandardsIgnoreStart @@ -134,117 +104,10 @@ public function test__unset(): void self::assertEmpty($this->rowGateway['foo']); } - public function testOffsetUnset(): void - { - $this->rowGateway['foo'] = 'bar'; - self::assertEquals('bar', $this->rowGateway['foo']); - unset($this->rowGateway['foo']); - self::assertEmpty($this->rowGateway->foo); - self::assertEmpty($this->rowGateway['foo']); - } - - public function testOffsetGet(): void - { - $this->rowGateway['testColumn'] = 'test'; - self::assertEquals('test', $this->rowGateway->testColumn); - self::assertEquals('test', $this->rowGateway['testColumn']); - } - - // @codingStandardsIgnoreStart - public function test__get(): void - { - // @codingStandardsIgnoreEnd - $this->rowGateway->testColumn = 'test'; - self::assertEquals('test', $this->rowGateway->testColumn); - self::assertEquals('test', $this->rowGateway['testColumn']); - } - - public function testSaveInsert(): void - { - $this->mockResult->expects($this->any())->method('current') - ->willReturn(['id' => 5, 'name' => 'foo']); - $this->mockResult->expects($this->any())->method('getGeneratedValue')->willReturn(5); - $this->rowGateway->populate(['name' => 'foo']); - $this->rowGateway->save(); - self::assertEquals(5, $this->rowGateway->id); - self::assertEquals(5, $this->rowGateway['id']); - } - - /** - * @throws ReflectionException - * @throws Exception - */ - #[RequiresPhp('<= 8.6')] - public function testSaveInsertMultiKey(): void - { - $this->rowGateway = $this->getMockBuilder(AbstractRowGateway::class)->onlyMethods([])->getMock(); - - $mockSql = $this->getMockBuilder(Sql::class) - ->setConstructorArgs([$this->mockAdapter]) - ->onlyMethods([]) - ->getMock(); - - $rgPropertyValues = [ - 'primaryKeyColumn' => ['one', 'two'], - 'table' => 'foo', - 'sql' => $mockSql, - ]; - $this->setRowGatewayState($rgPropertyValues); - - $this->mockResult->expects($this->any())->method('current') - ->willReturn(['one' => 'foo', 'two' => 'bar']); - - // @todo Need to assert that $where was filled in - - $refRowGateway = new ReflectionObject($this->rowGateway); - $refRowGatewayProp = $refRowGateway->getProperty('primaryKeyData'); - - $this->rowGateway->populate(['one' => 'foo', 'two' => 'bar']); - - self::assertNull($refRowGatewayProp->getValue($this->rowGateway)); - - $this->rowGateway->save(); - - self::assertEquals(['one' => 'foo', 'two' => 'bar'], $refRowGatewayProp->getValue($this->rowGateway)); - } - - public function testSaveUpdate(): void - { - $this->mockResult->expects($this->any())->method('current') - ->willReturn(['id' => 6, 'name' => 'foo']); - $this->rowGateway->populate(['id' => 6, 'name' => 'foo'], true); - $this->rowGateway->save(); - self::assertEquals(6, $this->rowGateway['id']); - } - - public function testSaveUpdateChangingPrimaryKey(): void + public function testCount(): void { - $selectMock = $this->getMockBuilder(Select::class) - ->onlyMethods(['where']) - ->getMock(); - $selectMock->expects($this->once()) - ->method('where') - ->with($this->equalTo(['id' => 7])) - ->willReturn($selectMock); - - $sqlMock = $this->getMockBuilder(Sql::class) - ->onlyMethods(['select']) - ->setConstructorArgs([$this->mockAdapter]) - ->getMock(); - $sqlMock->expects($this->any()) - ->method('select') - ->willReturn($selectMock); - - $this->setRowGatewayState(['sql' => $sqlMock]); - - $this->mockResult->expects($this->any()) - ->method('current') - ->willReturn(['id' => 7, 'name' => 'fooUpdated']); - - $this->rowGateway->populate(['id' => 6, 'name' => 'foo'], true); - $this->rowGateway->id = 7; - $this->rowGateway->save(); - self::assertEquals(['id' => 7, 'name' => 'fooUpdated'], $this->rowGateway->toArray()); + $this->rowGateway->populate(['id' => 5, 'name' => 'foo'], true); + self::assertEquals(2, $this->rowGateway->count()); } public function testDelete(): void @@ -255,38 +118,6 @@ public function testDelete(): void self::assertEquals(1, $affectedRows); } - public function testPopulate(): void - { - $this->rowGateway->populate(['id' => 5, 'name' => 'foo']); - self::assertEquals(5, $this->rowGateway['id']); - self::assertEquals('foo', $this->rowGateway['name']); - self::assertFalse($this->rowGateway->rowExistsInDatabase()); - - $this->rowGateway->populate(['id' => 5, 'name' => 'foo'], true); - self::assertTrue($this->rowGateway->rowExistsInDatabase()); - } - - public function testProcessPrimaryKeyData(): void - { - $this->rowGateway->populate(['id' => 5, 'name' => 'foo'], true); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('a known key id was not found'); - $this->rowGateway->populate(['boo' => 5, 'name' => 'foo'], true); - } - - public function testCount(): void - { - $this->rowGateway->populate(['id' => 5, 'name' => 'foo'], true); - self::assertEquals(2, $this->rowGateway->count()); - } - - public function testToArray(): void - { - $this->rowGateway->populate(['id' => 5, 'name' => 'foo'], true); - self::assertEquals(['id' => 5, 'name' => 'foo'], $this->rowGateway->toArray()); - } - public function testExchangeArray(): void { $this->rowGateway->populate(['id' => 5, 'name' => 'foo'], true); @@ -303,44 +134,57 @@ public function testExchangeArray(): void self::assertTrue($this->rowGateway->rowExistsInDatabase()); } - public function testRowExistsInDatabaseReturnsFalseWhenNew(): void + public function testInitializeCreatesFeatureSetIfNotSet(): void { - $this->rowGateway->populate(['name' => 'foo']); - self::assertFalse($this->rowGateway->rowExistsInDatabase()); - } + $rowGateway = $this->getMockBuilder(AbstractRowGateway::class)->onlyMethods([])->getMock(); - public function testRowExistsInDatabaseReturnsTrueAfterPopulateWithTrue(): void - { - $this->rowGateway->populate(['id' => 5, 'name' => 'foo'], true); - self::assertTrue($this->rowGateway->rowExistsInDatabase()); - } + $refRowGateway = new ReflectionObject($rowGateway); - // @codingStandardsIgnoreStart - public function test__getThrowsExceptionForInvalidColumn(): void - { - // @codingStandardsIgnoreEnd - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Not a valid column in this row'); + $tableProp = $refRowGateway->getProperty('table'); + $tableProp->setValue($rowGateway, 'foo'); - /** @phpstan-ignore property.notFound, expr.resultUnused */ - $this->rowGateway->nonExistentColumn; + $pkProp = $refRowGateway->getProperty('primaryKeyColumn'); + $pkProp->setValue($rowGateway, ['id']); + + $sqlProp = $refRowGateway->getProperty('sql'); + $sqlProp->setValue($rowGateway, new Sql($this->mockAdapter)); + + $featureSetProp = $refRowGateway->getProperty('featureSet'); + self::assertNull($featureSetProp->getValue($rowGateway)); + + $rowGateway->populate(['id' => 1, 'name' => 'test'], true); + + self::assertInstanceOf(FeatureSet::class, $featureSetProp->getValue($rowGateway)); } - public function testInitializeThrowsExceptionWhenTableIsNull(): void + public function testInitializeEarlyReturnWhenAlreadyInitialized(): void { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This row object does not have a valid table set.'); + $rowGateway = new RowGateway('id', 'test_table', $this->mockAdapter); - $rowGateway = new RowGateway('id', 'temp_table', $this->mockAdapter); + $refRowGateway = new ReflectionObject($rowGateway); + $featureSetProp = $refRowGateway->getProperty('featureSet'); + $originalFeatureSet = $featureSetProp->getValue($rowGateway); - $refRowGateway = new ReflectionObject($rowGateway); - $tableProp = $refRowGateway->getProperty('table'); - $tableProp->setValue($rowGateway, null); + $isInitializedProp = $refRowGateway->getProperty('isInitialized'); + self::assertTrue($isInitializedProp->getValue($rowGateway)); + + $rowGateway->populate(['id' => 2, 'name' => 'bar'], true); + + self::assertSame($originalFeatureSet, $featureSetProp->getValue($rowGateway)); + } + public function testInitializeOnlyRunsOnce(): void + { + $this->rowGateway->populate(['id' => 1, 'name' => 'foo'], true); + + $refRowGateway = new ReflectionObject($this->rowGateway); $isInitializedProp = $refRowGateway->getProperty('isInitialized'); - $isInitializedProp->setValue($rowGateway, false); + self::assertTrue($isInitializedProp->getValue($this->rowGateway)); - $rowGateway->populate(['name' => 'test']); + $this->rowGateway->populate(['id' => 2, 'name' => 'bar'], true); + + self::assertEquals(2, $this->rowGateway['id']); + self::assertEquals('bar', $this->rowGateway['name']); } public function testInitializeThrowsExceptionWhenPrimaryKeyColumnIsNull(): void @@ -379,57 +223,175 @@ public function testInitializeThrowsExceptionWhenSqlIsNull(): void $rowGateway->populate(['name' => 'test']); } - public function testInitializeOnlyRunsOnce(): void + public function testInitializeThrowsExceptionWhenTableIsNull(): void { - $this->rowGateway->populate(['id' => 1, 'name' => 'foo'], true); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('This row object does not have a valid table set.'); + + $rowGateway = new RowGateway('id', 'temp_table', $this->mockAdapter); + + $refRowGateway = new ReflectionObject($rowGateway); + $tableProp = $refRowGateway->getProperty('table'); + $tableProp->setValue($rowGateway, null); - $refRowGateway = new ReflectionObject($this->rowGateway); $isInitializedProp = $refRowGateway->getProperty('isInitialized'); - self::assertTrue($isInitializedProp->getValue($this->rowGateway)); + $isInitializedProp->setValue($rowGateway, false); - $this->rowGateway->populate(['id' => 2, 'name' => 'bar'], true); + $rowGateway->populate(['name' => 'test']); + } - self::assertEquals(2, $this->rowGateway['id']); - self::assertEquals('bar', $this->rowGateway['name']); + public function testOffsetExists(): void + { + self::assertFalse(isset($this->rowGateway['foo'])); + $this->rowGateway['foo'] = 'bar'; + self::assertTrue(isset($this->rowGateway['foo'])); } - public function testInitializeEarlyReturnWhenAlreadyInitialized(): void + public function testOffsetGet(): void { - $rowGateway = new RowGateway('id', 'test_table', $this->mockAdapter); + $this->rowGateway['testColumn'] = 'test'; + self::assertEquals('test', $this->rowGateway->testColumn); + self::assertEquals('test', $this->rowGateway['testColumn']); + } - $refRowGateway = new ReflectionObject($rowGateway); - $featureSetProp = $refRowGateway->getProperty('featureSet'); - $originalFeatureSet = $featureSetProp->getValue($rowGateway); + public function testOffsetSet(): void + { + $this->rowGateway['testColumn'] = 'test'; + self::assertEquals('test', $this->rowGateway->testColumn); + self::assertEquals('test', $this->rowGateway['testColumn']); + } - $isInitializedProp = $refRowGateway->getProperty('isInitialized'); - self::assertTrue($isInitializedProp->getValue($rowGateway)); + public function testOffsetUnset(): void + { + $this->rowGateway['foo'] = 'bar'; + self::assertEquals('bar', $this->rowGateway['foo']); + unset($this->rowGateway['foo']); + self::assertEmpty($this->rowGateway->foo); + self::assertEmpty($this->rowGateway['foo']); + } - $rowGateway->populate(['id' => 2, 'name' => 'bar'], true); + public function testPopulate(): void + { + $this->rowGateway->populate(['id' => 5, 'name' => 'foo']); + self::assertEquals(5, $this->rowGateway['id']); + self::assertEquals('foo', $this->rowGateway['name']); + self::assertFalse($this->rowGateway->rowExistsInDatabase()); - self::assertSame($originalFeatureSet, $featureSetProp->getValue($rowGateway)); + $this->rowGateway->populate(['id' => 5, 'name' => 'foo'], true); + self::assertTrue($this->rowGateway->rowExistsInDatabase()); } - public function testInitializeCreatesFeatureSetIfNotSet(): void + public function testProcessPrimaryKeyData(): void { - $rowGateway = $this->getMockBuilder(AbstractRowGateway::class)->onlyMethods([])->getMock(); + $this->rowGateway->populate(['id' => 5, 'name' => 'foo'], true); - $refRowGateway = new ReflectionObject($rowGateway); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('a known key id was not found'); + $this->rowGateway->populate(['boo' => 5, 'name' => 'foo'], true); + } - $tableProp = $refRowGateway->getProperty('table'); - $tableProp->setValue($rowGateway, 'foo'); + public function testRowExistsInDatabaseReturnsFalseWhenNew(): void + { + $this->rowGateway->populate(['name' => 'foo']); + self::assertFalse($this->rowGateway->rowExistsInDatabase()); + } - $pkProp = $refRowGateway->getProperty('primaryKeyColumn'); - $pkProp->setValue($rowGateway, ['id']); + public function testRowExistsInDatabaseReturnsTrueAfterPopulateWithTrue(): void + { + $this->rowGateway->populate(['id' => 5, 'name' => 'foo'], true); + self::assertTrue($this->rowGateway->rowExistsInDatabase()); + } - $sqlProp = $refRowGateway->getProperty('sql'); - $sqlProp->setValue($rowGateway, new Sql($this->mockAdapter)); + public function testSaveInsert(): void + { + $this->mockResult->expects($this->any())->method('current')->willReturn(['id' => 5, 'name' => 'foo']); + $this->mockResult->expects($this->any())->method('getGeneratedValue')->willReturn(5); + $this->rowGateway->populate(['name' => 'foo']); + $this->rowGateway->save(); + self::assertEquals(5, $this->rowGateway->id); + self::assertEquals(5, $this->rowGateway['id']); + } - $featureSetProp = $refRowGateway->getProperty('featureSet'); - self::assertNull($featureSetProp->getValue($rowGateway)); + /** + * @throws ReflectionException + * @throws Exception + */ + #[RequiresPhp('<= 8.6')] + public function testSaveInsertMultiKey(): void + { + $this->rowGateway = $this->getMockBuilder(AbstractRowGateway::class)->onlyMethods([])->getMock(); - $rowGateway->populate(['id' => 1, 'name' => 'test'], true); + $mockSql = $this->getMockBuilder(Sql::class) + ->setConstructorArgs([$this->mockAdapter]) + ->onlyMethods([]) + ->getMock(); - self::assertInstanceOf(FeatureSet::class, $featureSetProp->getValue($rowGateway)); + $rgPropertyValues = [ + 'primaryKeyColumn' => ['one', 'two'], + 'table' => 'foo', + 'sql' => $mockSql, + ]; + $this->setRowGatewayState($rgPropertyValues); + + $this->mockResult->expects($this->any())->method('current')->willReturn(['one' => 'foo', 'two' => 'bar']); + + // @todo Need to assert that $where was filled in + + $refRowGateway = new ReflectionObject($this->rowGateway); + $refRowGatewayProp = $refRowGateway->getProperty('primaryKeyData'); + + $this->rowGateway->populate(['one' => 'foo', 'two' => 'bar']); + + self::assertNull($refRowGatewayProp->getValue($this->rowGateway)); + + $this->rowGateway->save(); + + self::assertEquals(['one' => 'foo', 'two' => 'bar'], $refRowGatewayProp->getValue($this->rowGateway)); + } + + public function testSaveUpdate(): void + { + $this->mockResult->expects($this->any())->method('current')->willReturn(['id' => 6, 'name' => 'foo']); + $this->rowGateway->populate(['id' => 6, 'name' => 'foo'], true); + $this->rowGateway->save(); + self::assertEquals(6, $this->rowGateway['id']); + } + + public function testSaveUpdateChangingPrimaryKey(): void + { + $selectMock = $this->getMockBuilder(Select::class) + ->onlyMethods(['where']) + ->getMock(); + $selectMock->expects($this->once()) + ->method('where') + ->with($this->equalTo(['id' => 7])) + ->willReturn($selectMock); + + $sqlMock = $this->getMockBuilder(Sql::class) + ->onlyMethods(['select']) + ->setConstructorArgs([$this->mockAdapter]) + ->getMock(); + $sqlMock->expects($this->any()) + ->method('select') + ->willReturn($selectMock); + + $this->setRowGatewayState(['sql' => $sqlMock]); + + $this->mockResult + ->expects($this->any()) + ->method('current') + ->willReturn(['id' => 7, 'name' => 'fooUpdated']); + + $this->rowGateway->populate(['id' => 6, 'name' => 'foo'], true); + $this->rowGateway->id = 7; + $this->rowGateway->save(); + self::assertEquals(['id' => 7, 'name' => 'fooUpdated'], $this->rowGateway->toArray()); + } + + public function testToArray(): void + { + $this->rowGateway->populate(['id' => 5, 'name' => 'foo'], true); + self::assertEquals(['id' => 5, 'name' => 'foo'], $this->rowGateway->toArray()); } /** @@ -444,4 +406,41 @@ protected function setRowGatewayState(array $properties): void $refRowGatewayProp->setValue($this->rowGateway, $rgPropertyValue); } } + + /** + * @throws ReflectionException + * @throws Exception + */ + #[Override] + protected function setUp(): void + { + $mockResult = $this->getMockBuilder(ResultInterface::class)->getMock(); + $mockResult->expects($this->any())->method('getAffectedRows')->willReturn(1); + $this->mockResult = $mockResult; + $mockStatement = $this->getMockBuilder(StatementInterface::class)->getMock(); + $mockStatement->expects($this->any())->method('execute')->willReturn($mockResult); + $mockConnection = $this->getMockBuilder(ConnectionInterface::class)->getMock(); + $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + $mockDriver->expects($this->any())->method('createStatement')->willReturn($mockStatement); + $mockDriver->expects($this->any())->method('getConnection')->willReturn($mockConnection); + + $this->mockAdapter = $this->getMockBuilder(Adapter::class) + ->onlyMethods([]) + ->setConstructorArgs( + [ + $mockDriver, + new TrustingSql92Platform(), + ], + ) + ->getMock(); + + $this->rowGateway = $this->getMockBuilder(AbstractRowGateway::class)->onlyMethods([])->getMock(); + + $rgPropertyValues = [ + 'primaryKeyColumn' => ['id'], + 'table' => 'foo', + 'sql' => new Sql($this->mockAdapter), + ]; + $this->setRowGatewayState($rgPropertyValues); + } } diff --git a/test/unit/RowGateway/Feature/AbstractFeatureTest.php b/test/unit/RowGateway/Feature/AbstractFeatureTest.php index 16c3345a8..ef16e46dc 100644 --- a/test/unit/RowGateway/Feature/AbstractFeatureTest.php +++ b/test/unit/RowGateway/Feature/AbstractFeatureTest.php @@ -15,12 +15,13 @@ class AbstractFeatureTest extends TestCase { private AbstractFeature&MockObject $feature; - protected function setUp(): void + public function testGetMagicMethodSpecificationsReturnsEmptyArray(): void { - $this->feature = $this->getMockBuilder(AbstractFeature::class) - ->disableOriginalConstructor() - ->onlyMethods([]) - ->getMock(); + $result = $this->feature->getMagicMethodSpecifications(); + + /** @phpstan-ignore staticMethod.alreadyNarrowedType */ + self::assertIsArray($result); + self::assertEmpty($result); } public function testGetNameReturnsClassName(): void @@ -33,6 +34,14 @@ public function testGetNameReturnsClassName(): void self::assertNotEmpty($name); } + public function testInitializeThrowsRuntimeException(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('This method is not intended to be called on this object.'); + + $this->feature->initialize(); + } + public function testSetRowGateway(): void { /** @var AbstractRowGateway&MockObject $rowGateway */ @@ -49,20 +58,11 @@ public function testSetRowGateway(): void self::assertSame($rowGateway, $value); } - public function testInitializeThrowsRuntimeException(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This method is not intended to be called on this object.'); - - $this->feature->initialize(); - } - - public function testGetMagicMethodSpecificationsReturnsEmptyArray(): void + protected function setUp(): void { - $result = $this->feature->getMagicMethodSpecifications(); - - /** @phpstan-ignore staticMethod.alreadyNarrowedType */ - self::assertIsArray($result); - self::assertEmpty($result); + $this->feature = $this->getMockBuilder(AbstractFeature::class) + ->disableOriginalConstructor() + ->onlyMethods([]) + ->getMock(); } } diff --git a/test/unit/RowGateway/Feature/FeatureSetTest.php b/test/unit/RowGateway/Feature/FeatureSetTest.php index 5507a1261..cc7bc384a 100644 --- a/test/unit/RowGateway/Feature/FeatureSetTest.php +++ b/test/unit/RowGateway/Feature/FeatureSetTest.php @@ -13,20 +13,18 @@ class FeatureSetTest extends TestCase { - public function testConstructorWithEmptyArray(): void + public function testAddFeature(): void { + $feature = $this->createMock(AbstractFeature::class); + $featureSet = new FeatureSet(); - self::assertInstanceOf(FeatureSet::class, $featureSet); - } + $result = $featureSet->addFeature($feature); - public function testConstructorWithFeatures(): void - { - $feature = $this->createMock(AbstractFeature::class); - $featureSet = new FeatureSet([$feature]); - self::assertInstanceOf(FeatureSet::class, $featureSet); + self::assertSame($featureSet, $result); + self::assertSame($feature, $featureSet->getFeatureByClassName(AbstractFeature::class)); } - public function testSetRowGateway(): void + public function testAddFeatureCallsSetRowGatewayWhenRowGatewayIsSet(): void { /** @var AbstractRowGateway&MockObject $rowGateway */ $rowGateway = $this->getMockBuilder(AbstractRowGateway::class) @@ -38,29 +36,9 @@ public function testSetRowGateway(): void ->method('setRowGateway') ->with($rowGateway); - $featureSet = new FeatureSet([$feature]); - $result = $featureSet->setRowGateway($rowGateway); - - self::assertSame($featureSet, $result); - } - - public function testGetFeatureByClassNameReturnsFeature(): void - { - $feature = $this->createMock(AbstractFeature::class); - $featureSet = new FeatureSet([$feature]); - - $result = $featureSet->getFeatureByClassName(AbstractFeature::class); - - self::assertSame($feature, $result); - } - - public function testGetFeatureByClassNameReturnsNullWhenNotFound(): void - { $featureSet = new FeatureSet(); - - $result = $featureSet->getFeatureByClassName(AbstractFeature::class); - - self::assertNull($result); + $featureSet->setRowGateway($rowGateway); + $featureSet->addFeature($feature); } public function testAddFeatures(): void @@ -75,34 +53,6 @@ public function testAddFeatures(): void self::assertSame($feature1, $featureSet->getFeatureByClassName(AbstractFeature::class)); } - public function testAddFeature(): void - { - $feature = $this->createMock(AbstractFeature::class); - - $featureSet = new FeatureSet(); - $result = $featureSet->addFeature($feature); - - self::assertSame($featureSet, $result); - self::assertSame($feature, $featureSet->getFeatureByClassName(AbstractFeature::class)); - } - - public function testAddFeatureCallsSetRowGatewayWhenRowGatewayIsSet(): void - { - /** @var AbstractRowGateway&MockObject $rowGateway */ - $rowGateway = $this->getMockBuilder(AbstractRowGateway::class) - ->disableOriginalConstructor() - ->getMock(); - - $feature = $this->createMock(AbstractFeature::class); - $feature->expects($this->once()) - ->method('setRowGateway') - ->with($rowGateway); - - $featureSet = new FeatureSet(); - $featureSet->setRowGateway($rowGateway); - $featureSet->addFeature($feature); - } - public function testApplyCallsMethodOnFeatures(): void { $feature = new TestRowGatewayFeature(); @@ -139,11 +89,10 @@ public function testApplySkipsFeatureWithoutMethod(): void self::assertTrue(true); } - public function testCanCallMagicGetReturnsFalse(): void + public function testCallMagicCallReturnsNull(): void { $featureSet = new FeatureSet(); - /** @phpstan-ignore staticMethod.impossibleType */ - self::assertFalse($featureSet->canCallMagicGet('property')); + self::assertNull($featureSet->callMagicCall('method', [])); } public function testCallMagicGetReturnsNull(): void @@ -152,6 +101,25 @@ public function testCallMagicGetReturnsNull(): void self::assertNull($featureSet->callMagicGet('property')); } + public function testCallMagicSetReturnsNull(): void + { + $featureSet = new FeatureSet(); + self::assertNull($featureSet->callMagicSet('property', 'value')); + } + + public function testCanCallMagicCallReturnsFalse(): void + { + $featureSet = new FeatureSet(); + self::assertFalse($featureSet->canCallMagicCall('method')); + } + + public function testCanCallMagicGetReturnsFalse(): void + { + $featureSet = new FeatureSet(); + /** @phpstan-ignore staticMethod.impossibleType */ + self::assertFalse($featureSet->canCallMagicGet('property')); + } + public function testCanCallMagicSetReturnsFalse(): void { $featureSet = new FeatureSet(); @@ -159,21 +127,53 @@ public function testCanCallMagicSetReturnsFalse(): void self::assertFalse($featureSet->canCallMagicSet('property')); } - public function testCallMagicSetReturnsNull(): void + public function testConstructorWithEmptyArray(): void { $featureSet = new FeatureSet(); - self::assertNull($featureSet->callMagicSet('property', 'value')); + self::assertInstanceOf(FeatureSet::class, $featureSet); } - public function testCanCallMagicCallReturnsFalse(): void + public function testConstructorWithFeatures(): void { - $featureSet = new FeatureSet(); - self::assertFalse($featureSet->canCallMagicCall('method')); + $feature = $this->createMock(AbstractFeature::class); + $featureSet = new FeatureSet([$feature]); + self::assertInstanceOf(FeatureSet::class, $featureSet); } - public function testCallMagicCallReturnsNull(): void + public function testGetFeatureByClassNameReturnsFeature(): void + { + $feature = $this->createMock(AbstractFeature::class); + $featureSet = new FeatureSet([$feature]); + + $result = $featureSet->getFeatureByClassName(AbstractFeature::class); + + self::assertSame($feature, $result); + } + + public function testGetFeatureByClassNameReturnsNullWhenNotFound(): void { $featureSet = new FeatureSet(); - self::assertNull($featureSet->callMagicCall('method', [])); + + $result = $featureSet->getFeatureByClassName(AbstractFeature::class); + + self::assertNull($result); + } + + public function testSetRowGateway(): void + { + /** @var AbstractRowGateway&MockObject $rowGateway */ + $rowGateway = $this->getMockBuilder(AbstractRowGateway::class) + ->disableOriginalConstructor() + ->getMock(); + + $feature = $this->createMock(AbstractFeature::class); + $feature->expects($this->once()) + ->method('setRowGateway') + ->with($rowGateway); + + $featureSet = new FeatureSet([$feature]); + $result = $featureSet->setRowGateway($rowGateway); + + self::assertSame($featureSet, $result); } } diff --git a/test/unit/RowGateway/Feature/TestAsset/TestRowGatewayFeature.php b/test/unit/RowGateway/Feature/TestAsset/TestRowGatewayFeature.php index 6a41fc2d8..ce7a75c5d 100644 --- a/test/unit/RowGateway/Feature/TestAsset/TestRowGatewayFeature.php +++ b/test/unit/RowGateway/Feature/TestAsset/TestRowGatewayFeature.php @@ -15,15 +15,15 @@ class TestRowGatewayFeature extends AbstractFeature public ?string $returnValue = null; + public function postInitialize(): void + { + $this->called = true; + } + public function preInitialize(string ...$args): mixed { $this->called = true; $this->receivedArgs = $args; return $this->returnValue; } - - public function postInitialize(): void - { - $this->called = true; - } } diff --git a/test/unit/RowGateway/RowGatewayTest.php b/test/unit/RowGateway/RowGatewayTest.php index f0fd2ffb0..94120bf4b 100644 --- a/test/unit/RowGateway/RowGatewayTest.php +++ b/test/unit/RowGateway/RowGatewayTest.php @@ -30,48 +30,14 @@ final class RowGatewayTest extends TestCase /** @var ResultInterface&MockObject */ protected ResultInterface|MockObject $mockResult; - #[Override] - protected function setUp(): void - { - $mockResult = $this->getMockBuilder(ResultInterface::class)->getMock(); - $mockResult->expects($this->any())->method('getAffectedRows')->willReturn(1); - $this->mockResult = $mockResult; - - $mockStatement = $this->getMockBuilder(StatementInterface::class)->getMock(); - $mockStatement->expects($this->any())->method('execute')->willReturn($mockResult); - - $mockConnection = $this->getMockBuilder(ConnectionInterface::class)->getMock(); - - $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); - $mockDriver->expects($this->any())->method('createStatement')->willReturn($mockStatement); - $mockDriver->expects($this->any())->method('getConnection')->willReturn($mockConnection); - - $this->mockAdapter = $this->getMockBuilder(Adapter::class) - ->onlyMethods([]) - ->setConstructorArgs( - [ - $mockDriver, - $this->getMockBuilder(PlatformInterface::class)->getMock(), - ] - )->getMock(); - } - - public function testEmptyPrimaryKey(): void - { - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This row object does not have a primary key column set.'); - $this->rowGateway = new RowGateway('', 'foo', $this->mockAdapter); - } - - public function testConstructorWithStringPrimaryKey(): void + public function testConstructorThrowsExceptionWhenSqlTableDoesNotMatch(): void { - $rowGateway = new RowGateway('id', 'foo', $this->mockAdapter); + $sql = new Sql($this->mockAdapter, 'bar'); - $tableProp = new ReflectionProperty(RowGateway::class, 'table'); - $sqlProp = new ReflectionProperty(RowGateway::class, 'sql'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The Sql object provided does not have a table that matches this row object'); - self::assertEquals('foo', $tableProp->getValue($rowGateway)); - self::assertInstanceOf(Sql::class, $sqlProp->getValue($rowGateway)); + new RowGateway('id', 'foo', $sql); } public function testConstructorWithArrayPrimaryKey(): void @@ -93,15 +59,6 @@ public function testConstructorWithNullPrimaryKey(): void new RowGateway(null, 'foo', $this->mockAdapter); } - public function testConstructorWithTableIdentifier(): void - { - $tableIdentifier = new TableIdentifier('foo', 'schema'); - $rowGateway = new RowGateway('id', $tableIdentifier, $this->mockAdapter); - - $tableProp = new ReflectionProperty(RowGateway::class, 'table'); - self::assertSame($tableIdentifier, $tableProp->getValue($rowGateway)); - } - public function testConstructorWithSqlObject(): void { $sql = new Sql($this->mockAdapter, 'foo'); @@ -114,14 +71,31 @@ public function testConstructorWithSqlObject(): void self::assertEquals('foo', $tableProp->getValue($rowGateway)); } - public function testConstructorThrowsExceptionWhenSqlTableDoesNotMatch(): void + public function testConstructorWithStringPrimaryKey(): void { - $sql = new Sql($this->mockAdapter, 'bar'); + $rowGateway = new RowGateway('id', 'foo', $this->mockAdapter); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The Sql object provided does not have a table that matches this row object'); + $tableProp = new ReflectionProperty(RowGateway::class, 'table'); + $sqlProp = new ReflectionProperty(RowGateway::class, 'sql'); - new RowGateway('id', 'foo', $sql); + self::assertEquals('foo', $tableProp->getValue($rowGateway)); + self::assertInstanceOf(Sql::class, $sqlProp->getValue($rowGateway)); + } + + public function testConstructorWithTableIdentifier(): void + { + $tableIdentifier = new TableIdentifier('foo', 'schema'); + $rowGateway = new RowGateway('id', $tableIdentifier, $this->mockAdapter); + + $tableProp = new ReflectionProperty(RowGateway::class, 'table'); + self::assertSame($tableIdentifier, $tableProp->getValue($rowGateway)); + } + + public function testEmptyPrimaryKey(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('This row object does not have a primary key column set.'); + $this->rowGateway = new RowGateway('', 'foo', $this->mockAdapter); } public function testInitializeReturnsEarlyWhenAlreadyInitialized(): void @@ -136,35 +110,62 @@ public function testInitializeReturnsEarlyWhenAlreadyInitialized(): void self::assertTrue($isInitializedProp->getValue($rowGateway)); } - public function testInitializeThrowsWhenTableIsNull(): void + public function testInitializeThrowsWhenSqlIsNull(): void { $rowGateway = new RowGateway('id', 'foo', $this->mockAdapter); $isInitializedProp = new ReflectionProperty(RowGateway::class, 'isInitialized'); $isInitializedProp->setValue($rowGateway, false); - $tableProp = new ReflectionProperty(RowGateway::class, 'table'); - $tableProp->setValue($rowGateway, null); + $sqlProp = new ReflectionProperty(RowGateway::class, 'sql'); + $sqlProp->setValue($rowGateway, null); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This row object does not have a valid table set.'); + $this->expectExceptionMessage('This row object does not have a Sql object set.'); $rowGateway->initialize(); } - public function testInitializeThrowsWhenSqlIsNull(): void + public function testInitializeThrowsWhenTableIsNull(): void { $rowGateway = new RowGateway('id', 'foo', $this->mockAdapter); $isInitializedProp = new ReflectionProperty(RowGateway::class, 'isInitialized'); $isInitializedProp->setValue($rowGateway, false); - $sqlProp = new ReflectionProperty(RowGateway::class, 'sql'); - $sqlProp->setValue($rowGateway, null); + $tableProp = new ReflectionProperty(RowGateway::class, 'table'); + $tableProp->setValue($rowGateway, null); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This row object does not have a Sql object set.'); + $this->expectExceptionMessage('This row object does not have a valid table set.'); $rowGateway->initialize(); } + + #[Override] + protected function setUp(): void + { + $mockResult = $this->getMockBuilder(ResultInterface::class)->getMock(); + $mockResult->expects($this->any())->method('getAffectedRows')->willReturn(1); + $this->mockResult = $mockResult; + + $mockStatement = $this->getMockBuilder(StatementInterface::class)->getMock(); + $mockStatement->expects($this->any())->method('execute')->willReturn($mockResult); + + $mockConnection = $this->getMockBuilder(ConnectionInterface::class)->getMock(); + + $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + $mockDriver->expects($this->any())->method('createStatement')->willReturn($mockStatement); + $mockDriver->expects($this->any())->method('getConnection')->willReturn($mockConnection); + + $this->mockAdapter = $this->getMockBuilder(Adapter::class) + ->onlyMethods([]) + ->setConstructorArgs( + [ + $mockDriver, + $this->getMockBuilder(PlatformInterface::class)->getMock(), + ], + ) + ->getMock(); + } } diff --git a/test/unit/Sql/AbstractSqlFunctionalTestCase.php b/test/unit/Sql/AbstractSqlFunctionalTestCase.php index c93ddaae6..ea1fd69a7 100644 --- a/test/unit/Sql/AbstractSqlFunctionalTestCase.php +++ b/test/unit/Sql/AbstractSqlFunctionalTestCase.php @@ -25,7 +25,6 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use function array_merge; use function is_array; use function is_string; @@ -39,6 +38,41 @@ */ abstract class AbstractSqlFunctionalTestCase extends TestCase { + public static function dataProvider(): array + { + $data = [ + ...self::dataProviderCommonProcessMethods(), + ...self::dataProviderDecorators(), + ]; + + $res = []; + foreach ($data as $index => $test) { + self::assertIsArray($test); + $testExpected = $test['expected'] ?? []; + self::assertIsArray($testExpected); + /** @psalm-suppress MixedAssignment */ + foreach ($testExpected as $platform => $expected) { + $res["{$index}->{$platform}"] = [ + 'sqlObject' => $test['sqlObject'], + 'platform' => $platform, + 'expected' => $expected, + ]; + } + } + + return $res; + } + + protected static function createColumn(?string $sqlString): Sql\Ddl\Column\Column + { + return new Sql\Ddl\Column\Column($sqlString); + } + + protected static function createTable(string|TableIdentifier $sqlString): Sql\Ddl\CreateTable + { + return new Sql\Ddl\CreateTable($sqlString); + } + protected static function dataProviderCommonProcessMethods(): array { // phpcs:disable Generic.Files.LineLength.TooLong @@ -79,12 +113,12 @@ protected static function dataProviderCommonProcessMethods(): array ->join( 'joined_table2', 'my_table.id = joined_table2.id', - [] + [], ) ->join( 'joined_table3', 'my_table.id = joined_table3.id', - [Select::SQL_STAR] + [Select::SQL_STAR], ) ->columns([ 'my_table_column', @@ -98,23 +132,28 @@ protected static function dataProviderCommonProcessMethods(): array ], 'Select::processJoin()' => [ 'sqlObject' => self::select('a') - ->join(['b' => self::select('c')->where(['cc' => 10])], 'd=e')->where(['x' => 20]), + ->join(['b' => self::select('c')->where(['cc' => 10])], 'd=e') + ->where(['x' => 20]), 'expected' => [ 'sql92' => [ - 'string' => 'SELECT "a".*, "b".* FROM "a" INNER JOIN (SELECT "c".* FROM "c" WHERE "cc" = \'10\') AS "b" ON "d"="e" WHERE "x" = \'20\'', - 'prepare' => 'SELECT "a".*, "b".* FROM "a" INNER JOIN (SELECT "c".* FROM "c" WHERE "cc" = ?) AS "b" ON "d"="e" WHERE "x" = ?', + 'string' => 'SELECT "a".*, "b".* FROM "a" INNER JOIN (SELECT "c".* FROM "c" WHERE "cc" = \'10\') AS "b" ON "d"="e" WHERE "x" = \'20\'', + 'prepare' => 'SELECT "a".*, "b".* FROM "a" INNER JOIN (SELECT "c".* FROM "c" WHERE "cc" = ?) AS "b" ON "d"="e" WHERE "x" = ?', 'parameters' => ['subselect1where1' => 10, 'where1' => 20], ], ], ], 'Ddl::CreateTable::processColumns()' => [ 'sqlObject' => self::createTable('foo') - ->addColumn(self::createColumn('col1') - ->setOption('identity', true) - ->setOption('comment', 'Comment1')) - ->addColumn(self::createColumn('col2') - ->setOption('identity', true) - ->setOption('comment', 'Comment2')), + ->addColumn( + self::createColumn('col1') + ->setOption('identity', true) + ->setOption('comment', 'Comment1'), + ) + ->addColumn( + self::createColumn('col2') + ->setOption('identity', true) + ->setOption('comment', 'Comment2'), + ), 'expected' => [ 'sql92' => "CREATE TABLE \"foo\" ( \n \"col1\" INTEGER NOT NULL,\n \"col2\" INTEGER NOT NULL \n)", ], @@ -135,8 +174,8 @@ protected static function dataProviderCommonProcessMethods(): array ->where(['aa' => 'AA']), 'expected' => [ 'sql92' => [ - 'string' => 'SELECT "a".* FROM (SELECT "b".* FROM (SELECT "c".* FROM "c" WHERE "cc" = \'CC\') AS "b" WHERE "bb" = \'BB\') AS "a" WHERE "aa" = \'AA\'', - 'prepare' => 'SELECT "a".* FROM (SELECT "b".* FROM (SELECT "c".* FROM "c" WHERE "cc" = ?) AS "b" WHERE "bb" = ?) AS "a" WHERE "aa" = ?', + 'string' => 'SELECT "a".* FROM (SELECT "b".* FROM (SELECT "c".* FROM "c" WHERE "cc" = \'CC\') AS "b" WHERE "bb" = \'BB\') AS "a" WHERE "aa" = \'AA\'', + 'prepare' => 'SELECT "a".* FROM (SELECT "b".* FROM (SELECT "c".* FROM "c" WHERE "cc" = ?) AS "b" WHERE "bb" = ?) AS "a" WHERE "aa" = ?', 'parameters' => ['subselect2where1' => 'CC', 'subselect1where1' => 'BB', 'where1' => 'AA'], ], ], @@ -169,7 +208,7 @@ protected static function dataProviderCommonProcessMethods(): array ], 'Update::processExpression()' => [ 'sqlObject' => self::update('foo')->set( - ['x' => new Sql\Expression('?', [self::select('foo')->where(['x' => 'y'])])] + ['x' => new Sql\Expression('?', [self::select('foo')->where(['x' => 'y'])])], ), 'expected' => [ 'sql92' => [ @@ -180,10 +219,13 @@ protected static function dataProviderCommonProcessMethods(): array ], ], 'Update::processJoins()' => [ - 'sqlObject' => self::update('foo')->set(['x' => 'y'])->where(['xx' => 'yy'])->join( - 'bar', - 'bar.barId = foo.barId' - ), + 'sqlObject' => self::update('foo') + ->set(['x' => 'y']) + ->where(['xx' => 'yy']) + ->join( + 'bar', + 'bar.barId = foo.barId', + ), 'expected' => [ 'sql92' => [ 'string' => 'UPDATE "foo" INNER JOIN "bar" ON "bar"."barId" = "foo"."barId" SET "x" = \'y\' WHERE "xx" = \'yy\'', @@ -191,6 +233,7 @@ protected static function dataProviderCommonProcessMethods(): array ], ], ]; + // phpcs:enable Generic.Files.LineLength.TooLong } @@ -210,45 +253,40 @@ protected static function dataProviderDecorators(): array ], // phpcs:disable Generic.Files.LineLength.TooLong /* TODO - should be implemented - 'RootDecorators::Insert' => array( - 'sqlObject' => self::insert('foo')->select(self::select()), - 'expected' => array( - 'sql92' => array( - 'decorators' => array( - 'PhpDb\Sql\Insert' => new TestAsset\InsertDecorator, // Decorator for root sqlObject - 'PhpDb\Sql\Select' => array('PhpDb\Sql\Platform\Mysql\SelectDecorator', '{=SELECT_Sql92=}') - ), - 'string' => 'INSERT INTO "foo" {=SELECT_Sql92=}', - ), - ), - ),*/ + * 'RootDecorators::Insert' => array( + * 'sqlObject' => self::insert('foo')->select(self::select()), + * 'expected' => array( + * 'sql92' => array( + * 'decorators' => array( + * 'PhpDb\Sql\Insert' => new TestAsset\InsertDecorator, // Decorator for root sqlObject + * 'PhpDb\Sql\Select' => array('PhpDb\Sql\Platform\Mysql\SelectDecorator', '{=SELECT_Sql92=}') + * ), + * 'string' => 'INSERT INTO "foo" {=SELECT_Sql92=}', + * ), + * ), + * ),*/ // phpcs:enable Generic.Files.LineLength.TooLong ]; } - public static function dataProvider(): array + protected static function delete(string|TableIdentifier|null $sqlString): Sql\Delete { - $data = array_merge( - self::dataProviderCommonProcessMethods(), - self::dataProviderDecorators() - ); + return new Sql\Delete($sqlString); + } - $res = []; - foreach ($data as $index => $test) { - self::assertIsArray($test); - $testExpected = $test['expected'] ?? []; - self::assertIsArray($testExpected); - /** @psalm-suppress MixedAssignment */ - foreach ($testExpected as $platform => $expected) { - $res[$index . '->' . $platform] = [ - 'sqlObject' => $test['sqlObject'], - 'platform' => $platform, - 'expected' => $expected, - ]; - } - } + protected static function insert(string|TableIdentifier|null $sqlString): Sql\Insert + { + return new Sql\Insert($sqlString); + } - return $res; + protected static function select(string|array|null $sqlString): Sql\Select + { + return new Sql\Select($sqlString); + } + + protected static function update(string|TableIdentifier|null $sqlString): Sql\Update + { + return new Sql\Update($sqlString); } #[DataProvider('dataProvider')] @@ -271,7 +309,7 @@ public function test(PreparableSqlInterface|SqlInterface $sqlObject, string $pla } $expectedString = is_string($expected) ? $expected : (string) $expected['string']; - if ($expectedString !== '') { + if ('' !== $expectedString) { self::assertInstanceOf(SqlInterface::class, $sqlObject); $actual = $sql->buildSqlString($sqlObject); self::assertEquals($expectedString, $actual, 'getSqlString()'); @@ -290,22 +328,6 @@ public function test(PreparableSqlInterface|SqlInterface $sqlObject, string $pla } } - protected function resolveDecorator( - PlatformDecoratorInterface|array $decorator - ): PlatformDecoratorInterface|MockObject|null { - if (is_array($decorator)) { - /** @var class-string $classString */ - $classString = $decorator[0]; - $decoratorMock = $this->getMockBuilder($classString) - ->onlyMethods(['buildSqlString']) - ->setConstructorArgs([null]) - ->getMock(); - $decoratorMock->expects($this->any())->method('buildSqlString')->willReturn($decorator[1]); - return $decoratorMock; - } - return $decorator; - } - protected function resolveAdapter(string $platformName): Adapter\Adapter { // Only sql92 platform is supported after abstraction @@ -323,57 +345,43 @@ protected function resolveAdapter(string $platformName): Adapter\Adapter $mockStatement = $this->createMock(StatementInterface::class); $mockStatement->expects($this->any()) ->method('setSql') - ->willReturnCallback(function ($sql) use ($container, $mockStatement): MockObject { + ->willReturnCallback(static function ($sql) use ($container, $mockStatement): MockObject { $container->setSql($sql); return $mockStatement; }); $mockStatement->expects($this->any()) ->method('getSql') - ->willReturnCallback(fn(): ?string => $container->getSql()); + ->willReturnCallback(static fn(): ?string => $container->getSql()); $mockStatement->expects($this->any()) ->method('setParameterContainer') ->willReturnCallback( - function (ParameterContainer $params) use ($container, $mockStatement): MockObject { + static function (ParameterContainer $params) use ($container, $mockStatement): MockObject { $container->setParameterContainer($params); return $mockStatement; - } + }, ); $mockStatement->expects($this->any()) ->method('getParameterContainer') - ->willReturnCallback(fn(): ?ParameterContainer => $container->getParameterContainer()); + ->willReturnCallback(static fn(): ?ParameterContainer => $container->getParameterContainer()); return $mockStatement; }); return new Adapter\Adapter($mockDriver, $platform, new TestAsset\TemporaryResultSet()); } - protected static function select(string|array|null $sqlString): Sql\Select - { - return new Sql\Select($sqlString); - } - - protected static function delete(string|TableIdentifier|null $sqlString): Sql\Delete - { - return new Sql\Delete($sqlString); - } - - protected static function update(string|TableIdentifier|null $sqlString): Sql\Update - { - return new Sql\Update($sqlString); - } - - protected static function insert(string|TableIdentifier|null $sqlString): Sql\Insert - { - return new Sql\Insert($sqlString); - } - - protected static function createTable(string|TableIdentifier $sqlString): Sql\Ddl\CreateTable - { - return new Sql\Ddl\CreateTable($sqlString); - } - - protected static function createColumn(?string $sqlString): Sql\Ddl\Column\Column - { - return new Sql\Ddl\Column\Column($sqlString); + protected function resolveDecorator( + PlatformDecoratorInterface|array $decorator, + ): PlatformDecoratorInterface|MockObject|null { + if (is_array($decorator)) { + /** @var class-string $classString */ + $classString = $decorator[0]; + $decoratorMock = $this->getMockBuilder($classString) + ->onlyMethods(['buildSqlString']) + ->setConstructorArgs([null]) + ->getMock(); + $decoratorMock->expects($this->any())->method('buildSqlString')->willReturn($decorator[1]); + return $decoratorMock; + } + return $decorator; } } diff --git a/test/unit/Sql/AbstractSqlTest.php b/test/unit/Sql/AbstractSqlTest.php index ba288fe28..33eb427e3 100644 --- a/test/unit/Sql/AbstractSqlTest.php +++ b/test/unit/Sql/AbstractSqlTest.php @@ -65,22 +65,188 @@ final class AbstractSqlTest extends TestCase protected DriverInterface&MockObject $mockDriver; /** - * @throws Exception + * @throws ReflectionException */ - #[Override] - protected function setUp(): void + public function testCreateSqlFromSpecificationThrowsOnParameterCountMismatch(): void { - $this->abstractSql = $this->getMockBuilder(AbstractSql::class)->onlyMethods([])->getMock(); + $method = new ReflectionMethod($this->abstractSql, 'createSqlFromSpecificationAndParameters'); - $this->mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); - $this->mockDriver - ->expects($this->any()) - ->method('getPrepareType') - ->willReturn(DriverInterface::PARAMETERIZATION_NAMED); - $this->mockDriver - ->expects($this->any()) - ->method('formatParameterName') - ->willReturnCallback(fn($x): string => ':' . $x); + $specifications = [ + 'SELECT %1$s FROM %2$s' => [ + [1 => '%1$s', 'combinedby' => ', '], + null, + ], + ]; + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('A number of parameters was found that is not supported by this specification'); + $method->invoke($this->abstractSql, $specifications, ['col1', 'table', 'extra']); + } + + /** + * @throws ReflectionException + */ + public function testCreateSqlFromSpecNonCombinedByThrowsOnUnsupportedCount(): void + { + $method = new ReflectionMethod($this->abstractSql, 'createSqlFromSpecificationAndParameters'); + + $spec = [ + 'FROM %1$s' => [ + [1 => '%1$s'], + ], + ]; + $params = [['a', 'b']]; + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('A number of parameters (2)'); + $method->invoke($this->abstractSql, $spec, $params); + } + + /** + * @throws ReflectionException + */ + public function testCreateSqlFromSpecWithCombinedByScalarParam(): void + { + $method = new ReflectionMethod($this->abstractSql, 'createSqlFromSpecificationAndParameters'); + + $spec = [ + 'SELECT %1$s FROM %2$s' => [ + [1 => '%1$s', 'combinedby' => ', '], + null, + ], + ]; + $params = [['col1'], 'table1']; + + $result = $method->invoke($this->abstractSql, $spec, $params); + + self::assertSame('SELECT col1 FROM table1', $result); + } + + /** + * @throws ReflectionException + */ + public function testCreateSqlFromSpecWithCombinedByThrowsOnUnsupportedCount(): void + { + $method = new ReflectionMethod($this->abstractSql, 'createSqlFromSpecificationAndParameters'); + + $spec = [ + 'SELECT %1$s FROM %2$s' => [ + [1 => '%1$s', 'combinedby' => ', '], + null, + ], + ]; + $params = [[['a', 'b']], 'table1']; + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('A number of parameters (2)'); + $method->invoke($this->abstractSql, $spec, $params); + } + + /** + * @throws ReflectionException + */ + public function testCreateSqlFromSpecWithNonCombinedByParam(): void + { + $method = new ReflectionMethod($this->abstractSql, 'createSqlFromSpecificationAndParameters'); + + $spec = [ + 'FROM %1$s' => [ + [1 => '%1$s'], + ], + ]; + $params = [['my_table']]; + + $result = $method->invoke($this->abstractSql, $spec, $params); + + self::assertSame('FROM my_table', $result); + } + + public function testFlattenExpressionValuesViaInPredicate(): void + { + $select = new Select('users'); + $select->where(new Predicate\In('id', [1, 2, 3])); + + $sql = $select->getSqlString(new TrustingSql92Platform()); + + self::assertStringContainsString("\"id\" IN ('1', '2', '3')", $sql); + } + + public function testFlattenExpressionValuesViaInPredicateWithParameterContainer(): void + { + $select = new Select('users'); + $select->where(new Predicate\In('id', [1, 2, 3])); + + $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + $mockDriver->method('formatParameterName') + ->willReturnCallback(static fn(string $name): string => ":{$name}"); + + $parameterContainer = new ParameterContainer(); + $mockStatement = $this->createMock(StatementInterface::class); + $mockStatement->method('getParameterContainer')->willReturn($parameterContainer); + + $adapter = $this->getMockBuilder(Adapter::class) + ->setConstructorArgs([$mockDriver, new TrustingSql92Platform()]) + ->getMock(); + $adapter->method('getDriver')->willReturn($mockDriver); + $adapter->method('getPlatform')->willReturn(new TrustingSql92Platform()); + + $select->prepareStatement($adapter, $mockStatement); + + self::assertSame(3, $parameterContainer->count()); + } + + public function testLocalizeVariablesCopiesSubjectProperties(): void + { + $decorator = new SelectDecorator(); + $select = new Select('users'); + $select->columns(['id', 'name']); + $decorator->setSubject($select); + + $sql = $decorator->getSqlString(new TrustingSql92Platform()); + + self::assertStringContainsString('"users"', $sql); + self::assertStringContainsString('"id"', $sql); + } + + public function testProcessExpressionThrowsOnUnknownArgumentType(): void + { + $unknownArg = new class implements ArgumentInterface { + public function getSpecification(): string + { + return '%s'; + } + + public function getType(): ArgumentType + { + return ArgumentType::Value; + } + + public function getValue(): string + { + return 'test'; + } + }; + + $expression = new Expression('?', [$unknownArg]); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Unknown argument type'); + $this->invokeProcessExpressionMethod($expression); + } + + /** + * @throws ReflectionException + */ + public function testProcessExpressionWithIdentifiersArgument(): void + { + $expression = new Expression('? IN (SELECT col1, col2 FROM bar)', [ + Argument::identifiers(['col1', 'col2']), + ]); + + $sqlAndParams = $this->invokeProcessExpressionMethod($expression); + + self::assertStringContainsString('"col1"', $sqlAndParams); + self::assertStringContainsString('"col2"', $sqlAndParams); } /** @@ -133,14 +299,39 @@ public function testProcessExpressionWithParameterContainerAndParameterizationTy /** * @throws ReflectionException */ - public function testProcessExpressionWorksWithExpressionContainingStringParts(): void + public function testProcessExpressionWithValuesArgument(): void { - $expression = new Predicate\Expression('x = ?', 5); + $expression = new Expression( + '? IN (?, ?, ?)', + [ + new Argument\Identifier('id'), + new Argument\Value(1), + new Argument\Value(2), + new Argument\Value(3), + ], + ); - $predicateSet = new Predicate\PredicateSet([new Predicate\PredicateSet([$expression])]); - $sqlAndParams = $this->invokeProcessExpressionMethod($predicateSet); + $sqlAndParams = $this->invokeProcessExpressionMethod($expression); - self::assertEquals("(x = '5')", $sqlAndParams); + self::assertStringContainsString("'1'", $sqlAndParams); + self::assertStringContainsString("'2'", $sqlAndParams); + self::assertStringContainsString("'3'", $sqlAndParams); + self::assertStringContainsString('"id"', $sqlAndParams); + } + + /** + * @throws ReflectionException + */ + public function testProcessExpressionWorksWithExpressionContainingExpressionObject(): void + { + $expression = new Predicate\Operator( + 'release_date', + '=', + new Expression('FROM_UNIXTIME(?)', 100_000_000), + ); + + $sqlAndParams = $this->invokeProcessExpressionMethod($expression); + self::assertEquals('"release_date" = FROM_UNIXTIME(\'100000000\')', $sqlAndParams); } /** @@ -161,16 +352,14 @@ public function testProcessExpressionWorksWithExpressionContainingSelectObject() /** * @throws ReflectionException */ - public function testProcessExpressionWorksWithExpressionContainingExpressionObject(): void + public function testProcessExpressionWorksWithExpressionContainingStringParts(): void { - $expression = new Predicate\Operator( - 'release_date', - '=', - new Expression('FROM_UNIXTIME(?)', 100000000) - ); + $expression = new Predicate\Expression('x = ?', 5); - $sqlAndParams = $this->invokeProcessExpressionMethod($expression); - self::assertEquals('"release_date" = FROM_UNIXTIME(\'100000000\')', $sqlAndParams); + $predicateSet = new Predicate\PredicateSet([new Predicate\PredicateSet([$expression])]); + $sqlAndParams = $this->invokeProcessExpressionMethod($predicateSet); + + self::assertEquals("(x = '5')", $sqlAndParams); } /** @@ -193,10 +382,10 @@ public function testProcessExpressionWorksWithNamedParameterPrefix(): void { $parameterContainer = new ParameterContainer(); $namedParameterPrefix = uniqid(); - $expression = new Expression('FROM_UNIXTIME(?)', [10000000]); + $expression = new Expression('FROM_UNIXTIME(?)', [10_000_000]); $this->invokeProcessExpressionMethod($expression, $parameterContainer, $namedParameterPrefix); - self::assertSame($namedParameterPrefix . '1', (string) key($parameterContainer->getNamedArray())); + self::assertSame("{$namedParameterPrefix}1", (string) key($parameterContainer->getNamedArray())); } /** @@ -206,7 +395,7 @@ public function testProcessExpressionWorksWithNamedParameterPrefixContainingWhit { $parameterContainer = new ParameterContainer(); $namedParameterPrefix = "string\ncontaining white space"; - $expression = new Expression('FROM_UNIXTIME(?)', [10000000]); + $expression = new Expression('FROM_UNIXTIME(?)', [10_000_000]); $this->invokeProcessExpressionMethod($expression, $parameterContainer, $namedParameterPrefix); self::assertSame('string__containing__white__space1', key($parameterContainer->getNamedArray())); @@ -215,130 +404,138 @@ public function testProcessExpressionWorksWithNamedParameterPrefixContainingWhit /** * @throws ReflectionException */ - public function testResolveColumnValueWithNull(): void + public function testProcessJoinReturnsNullWhenEmpty(): void { - $method = new ReflectionMethod($this->abstractSql, 'resolveColumnValue'); - + $method = new ReflectionMethod($this->abstractSql, 'processJoin'); $result = $method->invoke( $this->abstractSql, null, new TrustingSql92Platform(), - $this->mockDriver, null, - null + null, ); - self::assertEquals('NULL', $result); + self::assertNull($result); } /** * @throws ReflectionException */ - public function testResolveColumnValueWithSelect(): void + public function testProcessJoinWithArrayAlias(): void { - $select = new Select('foo'); - $method = new ReflectionMethod($this->abstractSql, 'resolveColumnValue'); + $join = new Join(); + $join->join(['b' => 'bar'], 'foo.id = b.foo_id'); + $method = new ReflectionMethod($this->abstractSql, 'processJoin'); $result = $method->invoke( $this->abstractSql, - $select, + $join, new TrustingSql92Platform(), - $this->mockDriver, null, - null + null, ); - self::assertStringContainsString('SELECT', $result); - self::assertStringStartsWith('(', $result); - self::assertStringEndsWith(')', $result); + self::assertNotNull($result); + self::assertStringContainsString('AS', $result[0][0][1]); } /** * @throws ReflectionException */ - public function testResolveColumnValueWithArrayAndFromTable(): void + public function testProcessJoinWithExpressionNameViaArray(): void { - $method = new ReflectionMethod($this->abstractSql, 'resolveColumnValue'); + $join = new Join(); + $join->join(['x' => new Expression('LATERAL(SELECT 1)')], 'true'); + $method = new ReflectionMethod($this->abstractSql, 'processJoin'); $result = $method->invoke( $this->abstractSql, - [ - 'column' => 'id', - 'isIdentifier' => true, - 'fromTable' => 'table.', - ], + $join, new TrustingSql92Platform(), - $this->mockDriver, null, - null + null, ); - self::assertStringContainsString('table.', $result); - self::assertStringContainsString('id', $result); + self::assertStringContainsString('LATERAL(SELECT 1)', $result[0][0][1]); } /** * @throws ReflectionException */ - public function testResolveTableWithTableIdentifierAndSchema(): void + public function testProcessJoinWithPredicateExpressionOnClause(): void { - $table = new TableIdentifier('users', 'public'); - $method = new ReflectionMethod($this->abstractSql, 'resolveTable'); + $join = new Join(); + $join->join('bar', new Predicate\Expression('foo.id = bar.foo_id AND bar.active = 1')); + $method = new ReflectionMethod($this->abstractSql, 'processJoin'); $result = $method->invoke( $this->abstractSql, - $table, + $join, new TrustingSql92Platform(), - $this->mockDriver, - null + null, + null, ); - self::assertStringContainsString('public', $result); - self::assertStringContainsString('users', $result); + self::assertNotNull($result); + self::assertStringContainsString('foo.id = bar.foo_id', $result[0][0][2]); } /** * @throws ReflectionException */ - public function testResolveTableWithSelect(): void + public function testProcessJoinWithSelectSubqueryViaArray(): void { - $select = new Select('foo'); - $method = new ReflectionMethod($this->abstractSql, 'resolveTable'); + $subselect = new Select('bar'); + $join = new Join(); + $join->join(['b' => $subselect], 'foo.id = b.foo_id'); + $method = new ReflectionMethod($this->abstractSql, 'processJoin'); $result = $method->invoke( $this->abstractSql, - $select, + $join, new TrustingSql92Platform(), - $this->mockDriver, - null + null, + null, ); - self::assertStringStartsWith('(', $result); - self::assertStringEndsWith(')', $result); - self::assertStringContainsString('SELECT', $result); + self::assertStringContainsString('SELECT', $result[0][0][1]); + self::assertStringContainsString('AS', $result[0][0][1]); } /** * @throws ReflectionException */ - public function testProcessSubSelectWithParameterContainer(): void + public function testProcessJoinWithTableIdentifier(): void { - $select = new Select('foo'); - $select->where(['id' => 5]); - - $method = new ReflectionMethod($this->abstractSql, 'processSubSelect'); + $join = new Join(); + $join->join(new TableIdentifier('bar', 'myschema'), 'foo.id = bar.foo_id'); - $parameterContainer = new ParameterContainer(); - $result = $method->invoke( + $method = new ReflectionMethod($this->abstractSql, 'processJoin'); + $result = $method->invoke( $this->abstractSql, - $select, + $join, new TrustingSql92Platform(), - $this->mockDriver, - $parameterContainer + null, + null, ); - self::assertStringContainsString('SELECT', $result); - self::assertGreaterThan(0, count($parameterContainer->getNamedArray())); + self::assertNotNull($result); + self::assertStringContainsString('"myschema"', $result[0][0][1]); + self::assertStringContainsString('"bar"', $result[0][0][1]); + } + + public function testProcessSubSelectUsesDecoratorWhenPlatformDecorator(): void + { + $decorator = new SelectDecorator(); + $outer = new Select('foo'); + $outer->where(['x' => new Select('bar')]); + + $decorator->setSubject($outer); + + $sql = $decorator->getSqlString(new TrustingSql92Platform()); + + self::assertStringContainsString('SELECT "bar"', $sql); + self::assertStringContainsString('SELECT "foo"', $sql); } /** @@ -355,7 +552,7 @@ public function testProcessSubSelectWithoutParameterContainer(): void $select, new TrustingSql92Platform(), $this->mockDriver, - null + null, ); self::assertStringContainsString('SELECT', $result); @@ -364,401 +561,204 @@ public function testProcessSubSelectWithoutParameterContainer(): void /** * @throws ReflectionException */ - public function testProcessExpressionWithValuesArgument(): void - { - $expression = new Expression( - '? IN (?, ?, ?)', - [ - new Argument\Identifier('id'), - new Argument\Value(1), - new Argument\Value(2), - new Argument\Value(3), - ] - ); - - $sqlAndParams = $this->invokeProcessExpressionMethod($expression); - - self::assertStringContainsString("'1'", $sqlAndParams); - self::assertStringContainsString("'2'", $sqlAndParams); - self::assertStringContainsString("'3'", $sqlAndParams); - self::assertStringContainsString('"id"', $sqlAndParams); - } - - /** - * @throws ReflectionException - */ - public function testProcessExpressionWithIdentifiersArgument(): void - { - $expression = new Expression('? IN (SELECT col1, col2 FROM bar)', [ - Argument::identifiers(['col1', 'col2']), - ]); - - $sqlAndParams = $this->invokeProcessExpressionMethod($expression); - - self::assertStringContainsString('"col1"', $sqlAndParams); - self::assertStringContainsString('"col2"', $sqlAndParams); - } - - /** - * @throws ReflectionException - */ - public function testCreateSqlFromSpecificationThrowsOnParameterCountMismatch(): void + public function testProcessSubSelectWithParameterContainer(): void { - $method = new ReflectionMethod($this->abstractSql, 'createSqlFromSpecificationAndParameters'); - - $specifications = [ - 'SELECT %1$s FROM %2$s' => [ - [1 => '%1$s', 'combinedby' => ', '], - null, - ], - ]; + $select = new Select('foo'); + $select->where(['id' => 5]); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('A number of parameters was found that is not supported by this specification'); - $method->invoke($this->abstractSql, $specifications, ['col1', 'table', 'extra']); - } + $method = new ReflectionMethod($this->abstractSql, 'processSubSelect'); - /** - * @throws ReflectionException - */ - protected function invokeProcessExpressionMethod( - ExpressionInterface $expression, - ParameterContainer|null $parameterContainer = null, - string|null $namedParameterPrefix = null - ): string|StatementContainer { - $method = new ReflectionMethod($this->abstractSql, 'processExpression'); - return $method->invoke( + $parameterContainer = new ParameterContainer(); + $result = $method->invoke( $this->abstractSql, - $expression, + $select, new TrustingSql92Platform(), $this->mockDriver, $parameterContainer, - $namedParameterPrefix ); + + self::assertStringContainsString('SELECT', $result); + self::assertGreaterThan(0, count($parameterContainer->getNamedArray())); } /** * @throws ReflectionException */ - public function testProcessJoinWithArrayAlias(): void + public function testRenderTableWithAlias(): void { - $join = new Join(); - $join->join(['b' => 'bar'], 'foo.id = b.foo_id'); - - $method = new ReflectionMethod($this->abstractSql, 'processJoin'); - $result = $method->invoke( - $this->abstractSql, - $join, - new TrustingSql92Platform(), - null, - null - ); + $method = new ReflectionMethod($this->abstractSql, 'renderTable'); + $result = $method->invoke($this->abstractSql, '"foo"', '"f"'); - self::assertNotNull($result); - self::assertStringContainsString('AS', $result[0][0][1]); + self::assertSame('"foo" AS "f"', $result); } /** * @throws ReflectionException */ - public function testProcessJoinWithTableIdentifier(): void + public function testResolveColumnValueWithArrayAndFromTable(): void { - $join = new Join(); - $join->join(new TableIdentifier('bar', 'myschema'), 'foo.id = bar.foo_id'); + $method = new ReflectionMethod($this->abstractSql, 'resolveColumnValue'); - $method = new ReflectionMethod($this->abstractSql, 'processJoin'); $result = $method->invoke( $this->abstractSql, - $join, + [ + 'column' => 'id', + 'isIdentifier' => true, + 'fromTable' => 'table.', + ], new TrustingSql92Platform(), + $this->mockDriver, + null, null, - null ); - self::assertNotNull($result); - self::assertStringContainsString('"myschema"', $result[0][0][1]); - self::assertStringContainsString('"bar"', $result[0][0][1]); + self::assertStringContainsString('table.', $result); + self::assertStringContainsString('id', $result); } - /** - * @throws ReflectionException - */ - public function testProcessJoinWithPredicateExpressionOnClause(): void + public function testResolveColumnValueWithNamedParameterPrefix(): void { - $join = new Join(); - $join->join('bar', new Predicate\Expression('foo.id = bar.foo_id AND bar.active = 1')); + $select = new Select('users'); + $select->columns(['id']); + $select->where(new Predicate\In('status', [1, 2])); - $method = new ReflectionMethod($this->abstractSql, 'processJoin'); - $result = $method->invoke( - $this->abstractSql, - $join, - new TrustingSql92Platform(), - null, - null - ); + $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + $mockDriver->method('formatParameterName') + ->willReturnCallback(static fn(string $name): string => ":{$name}"); - self::assertNotNull($result); - self::assertStringContainsString('foo.id = bar.foo_id', $result[0][0][2]); + $parameterContainer = new ParameterContainer(); + $mockStatement = $this->createMock(StatementInterface::class); + $mockStatement->method('getParameterContainer')->willReturn($parameterContainer); + $mockStatement->method('setSql')->willReturnSelf(); + + $adapter = $this->getMockBuilder(Adapter::class) + ->setConstructorArgs([$mockDriver, new TrustingSql92Platform()]) + ->getMock(); + $adapter->method('getDriver')->willReturn($mockDriver); + $adapter->method('getPlatform')->willReturn(new TrustingSql92Platform()); + + $select->prepareStatement($adapter, $mockStatement); + + self::assertGreaterThanOrEqual(2, $parameterContainer->count()); } /** * @throws ReflectionException */ - public function testProcessJoinReturnsNullWhenEmpty(): void + public function testResolveColumnValueWithNull(): void { - $method = new ReflectionMethod($this->abstractSql, 'processJoin'); + $method = new ReflectionMethod($this->abstractSql, 'resolveColumnValue'); + $result = $method->invoke( $this->abstractSql, null, new TrustingSql92Platform(), + $this->mockDriver, + null, null, - null ); - self::assertNull($result); - } - - /** - * @throws ReflectionException - */ - public function testRenderTableWithAlias(): void - { - $method = new ReflectionMethod($this->abstractSql, 'renderTable'); - $result = $method->invoke($this->abstractSql, '"foo"', '"f"'); - - self::assertSame('"foo" AS "f"', $result); + self::assertEquals('NULL', $result); } /** * @throws ReflectionException */ - public function testProcessJoinWithExpressionNameViaArray(): void + public function testResolveColumnValueWithSelect(): void { - $join = new Join(); - $join->join(['x' => new Expression('LATERAL(SELECT 1)')], 'true'); + $select = new Select('foo'); + $method = new ReflectionMethod($this->abstractSql, 'resolveColumnValue'); - $method = new ReflectionMethod($this->abstractSql, 'processJoin'); $result = $method->invoke( $this->abstractSql, - $join, + $select, new TrustingSql92Platform(), + $this->mockDriver, + null, null, - null ); - self::assertStringContainsString('LATERAL(SELECT 1)', $result[0][0][1]); + self::assertStringContainsString('SELECT', $result); + self::assertStringStartsWith('(', $result); + self::assertStringEndsWith(')', $result); } /** * @throws ReflectionException */ - public function testProcessJoinWithSelectSubqueryViaArray(): void + public function testResolveTableWithSelect(): void { - $subselect = new Select('bar'); - $join = new Join(); - $join->join(['b' => $subselect], 'foo.id = b.foo_id'); + $select = new Select('foo'); + $method = new ReflectionMethod($this->abstractSql, 'resolveTable'); - $method = new ReflectionMethod($this->abstractSql, 'processJoin'); $result = $method->invoke( $this->abstractSql, - $join, + $select, new TrustingSql92Platform(), + $this->mockDriver, null, - null ); - self::assertStringContainsString('SELECT', $result[0][0][1]); - self::assertStringContainsString('AS', $result[0][0][1]); - } - - /** - * @throws ReflectionException - */ - public function testCreateSqlFromSpecWithCombinedByScalarParam(): void - { - $method = new ReflectionMethod($this->abstractSql, 'createSqlFromSpecificationAndParameters'); - - $spec = [ - 'SELECT %1$s FROM %2$s' => [ - [1 => '%1$s', 'combinedby' => ', '], - null, - ], - ]; - $params = [['col1'], 'table1']; - - $result = $method->invoke($this->abstractSql, $spec, $params); - - self::assertSame('SELECT col1 FROM table1', $result); + self::assertStringStartsWith('(', $result); + self::assertStringEndsWith(')', $result); + self::assertStringContainsString('SELECT', $result); } /** * @throws ReflectionException */ - public function testCreateSqlFromSpecWithCombinedByThrowsOnUnsupportedCount(): void + public function testResolveTableWithTableIdentifierAndSchema(): void { - $method = new ReflectionMethod($this->abstractSql, 'createSqlFromSpecificationAndParameters'); + $table = new TableIdentifier('users', 'public'); + $method = new ReflectionMethod($this->abstractSql, 'resolveTable'); - $spec = [ - 'SELECT %1$s FROM %2$s' => [ - [1 => '%1$s', 'combinedby' => ', '], - null, - ], - ]; - $params = [[['a', 'b']], 'table1']; + $result = $method->invoke( + $this->abstractSql, + $table, + new TrustingSql92Platform(), + $this->mockDriver, + null, + ); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('A number of parameters (2)'); - $method->invoke($this->abstractSql, $spec, $params); + self::assertStringContainsString('public', $result); + self::assertStringContainsString('users', $result); } /** * @throws ReflectionException */ - public function testCreateSqlFromSpecWithNonCombinedByParam(): void - { - $method = new ReflectionMethod($this->abstractSql, 'createSqlFromSpecificationAndParameters'); - - $spec = [ - 'FROM %1$s' => [ - [1 => '%1$s'], - ], - ]; - $params = [['my_table']]; - - $result = $method->invoke($this->abstractSql, $spec, $params); - - self::assertSame('FROM my_table', $result); + protected function invokeProcessExpressionMethod( + ExpressionInterface $expression, + ?ParameterContainer $parameterContainer = null, + ?string $namedParameterPrefix = null, + ): string|StatementContainer { + $method = new ReflectionMethod($this->abstractSql, 'processExpression'); + return $method->invoke( + $this->abstractSql, + $expression, + new TrustingSql92Platform(), + $this->mockDriver, + $parameterContainer, + $namedParameterPrefix, + ); } /** - * @throws ReflectionException + * @throws Exception */ - public function testCreateSqlFromSpecNonCombinedByThrowsOnUnsupportedCount(): void - { - $method = new ReflectionMethod($this->abstractSql, 'createSqlFromSpecificationAndParameters'); - - $spec = [ - 'FROM %1$s' => [ - [1 => '%1$s'], - ], - ]; - $params = [['a', 'b']]; - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('A number of parameters (2)'); - $method->invoke($this->abstractSql, $spec, $params); - } - - public function testProcessExpressionThrowsOnUnknownArgumentType(): void - { - $unknownArg = new class implements ArgumentInterface { - public function getType(): ArgumentType - { - return ArgumentType::Value; - } - - public function getValue(): string - { - return 'test'; - } - - public function getSpecification(): string - { - return '%s'; - } - }; - - $expression = new Expression('?', [$unknownArg]); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Unknown argument type'); - $this->invokeProcessExpressionMethod($expression); - } - - public function testResolveColumnValueWithNamedParameterPrefix(): void - { - $select = new Select('users'); - $select->columns(['id']); - $select->where(new Predicate\In('status', [1, 2])); - - $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); - $mockDriver->method('formatParameterName') - ->willReturnCallback(fn(string $name): string => ':' . $name); - - $parameterContainer = new ParameterContainer(); - $mockStatement = $this->createMock(StatementInterface::class); - $mockStatement->method('getParameterContainer')->willReturn($parameterContainer); - $mockStatement->method('setSql')->willReturnSelf(); - - $adapter = $this->getMockBuilder(Adapter::class) - ->setConstructorArgs([$mockDriver, new TrustingSql92Platform()]) - ->getMock(); - $adapter->method('getDriver')->willReturn($mockDriver); - $adapter->method('getPlatform')->willReturn(new TrustingSql92Platform()); - - $select->prepareStatement($adapter, $mockStatement); - - self::assertGreaterThanOrEqual(2, $parameterContainer->count()); - } - - public function testLocalizeVariablesCopiesSubjectProperties(): void - { - $decorator = new SelectDecorator(); - $select = new Select('users'); - $select->columns(['id', 'name']); - $decorator->setSubject($select); - - $sql = $decorator->getSqlString(new TrustingSql92Platform()); - - self::assertStringContainsString('"users"', $sql); - self::assertStringContainsString('"id"', $sql); - } - - public function testProcessSubSelectUsesDecoratorWhenPlatformDecorator(): void - { - $decorator = new SelectDecorator(); - $outer = new Select('foo'); - $outer->where(['x' => new Select('bar')]); - - $decorator->setSubject($outer); - - $sql = $decorator->getSqlString(new TrustingSql92Platform()); - - self::assertStringContainsString('SELECT "bar"', $sql); - self::assertStringContainsString('SELECT "foo"', $sql); - } - - public function testFlattenExpressionValuesViaInPredicate(): void - { - $select = new Select('users'); - $select->where(new Predicate\In('id', [1, 2, 3])); - - $sql = $select->getSqlString(new TrustingSql92Platform()); - - self::assertStringContainsString("\"id\" IN ('1', '2', '3')", $sql); - } - - public function testFlattenExpressionValuesViaInPredicateWithParameterContainer(): void + #[Override] + protected function setUp(): void { - $select = new Select('users'); - $select->where(new Predicate\In('id', [1, 2, 3])); - - $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); - $mockDriver->method('formatParameterName') - ->willReturnCallback(fn(string $name): string => ':' . $name); - - $parameterContainer = new ParameterContainer(); - $mockStatement = $this->createMock(StatementInterface::class); - $mockStatement->method('getParameterContainer')->willReturn($parameterContainer); - - $adapter = $this->getMockBuilder(Adapter::class) - ->setConstructorArgs([$mockDriver, new TrustingSql92Platform()]) - ->getMock(); - $adapter->method('getDriver')->willReturn($mockDriver); - $adapter->method('getPlatform')->willReturn(new TrustingSql92Platform()); - - $select->prepareStatement($adapter, $mockStatement); + $this->abstractSql = $this->getMockBuilder(AbstractSql::class)->onlyMethods([])->getMock(); - self::assertSame(3, $parameterContainer->count()); + $this->mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + $this->mockDriver + ->expects($this->any()) + ->method('getPrepareType') + ->willReturn(DriverInterface::PARAMETERIZATION_NAMED); + $this->mockDriver + ->expects($this->any()) + ->method('formatParameterName') + ->willReturnCallback(static fn($x): string => ":{$x}"); } } diff --git a/test/unit/Sql/Argument/LiteralTest.php b/test/unit/Sql/Argument/LiteralTest.php index 784918cbe..bd142104e 100644 --- a/test/unit/Sql/Argument/LiteralTest.php +++ b/test/unit/Sql/Argument/LiteralTest.php @@ -17,6 +17,13 @@ #[CoversMethod(Literal::class, 'getSpecification')] final class LiteralTest extends TestCase { + public function testGetSpecificationReturnsPlaceholder(): void + { + $literal = new Literal('test'); + + self::assertSame('%s', $literal->getSpecification()); + } + public function testGetTypeReturnsLiteral(): void { $literal = new Literal('test'); @@ -30,11 +37,4 @@ public function testGetValueReturnsLiteralString(): void self::assertSame('NOW()', $literal->getValue()); } - - public function testGetSpecificationReturnsPlaceholder(): void - { - $literal = new Literal('test'); - - self::assertSame('%s', $literal->getSpecification()); - } } diff --git a/test/unit/Sql/ArgumentTest.php b/test/unit/Sql/ArgumentTest.php index 1d94c9f2e..59d51363e 100644 --- a/test/unit/Sql/ArgumentTest.php +++ b/test/unit/Sql/ArgumentTest.php @@ -22,10 +22,26 @@ #[CoversMethod(Argument::class, 'select')] final class ArgumentTest extends TestCase { - public function testConstructorWithSimpleValue(): void + public function testConstructorThrowsExceptionForInvalidSelectType(): void { - $argument = new Value('test'); - self::assertEquals('test', $argument->getValue()); + $this->expectException(TypeError::class); + /** @noinspection PhpParamsInspection */ + /** @noinspection PhpExpressionResultUnusedInspection */ + new ArgumentSelect('simple_value'); /** @phpstan-ignore-line */ + } + + public function testConstructorWithArrayContainingArgumentType(): void + { + $argument = new Identifier('column'); + + self::assertEquals('column', $argument->getValue()); + self::assertEquals(ArgumentType::Identifier, $argument->getType()); + } + + public function testConstructorWithBooleanValue(): void + { + $argument = new Value(true); + self::assertTrue($argument->getValue()); self::assertEquals(ArgumentType::Value, $argument->getType()); } @@ -45,29 +61,18 @@ public function testConstructorWithExpressionInterface(): void self::assertEquals(ArgumentType::Select, $argument->getType()); } - public function testConstructorWithSqlInterface(): void - { - $select = new Select(); - $argument = new ArgumentSelect($select); - - self::assertSame($select, $argument->getValue()); - self::assertEquals(ArgumentType::Select, $argument->getType()); - } - - public function testConstructorThrowsExceptionForInvalidSelectType(): void + public function testConstructorWithFloatValue(): void { - $this->expectException(TypeError::class); - /** @noinspection PhpParamsInspection */ - /** @noinspection PhpExpressionResultUnusedInspection */ - new ArgumentSelect('simple_value'); /** @phpstan-ignore-line */ + $argument = new Value(3.14); + self::assertEquals(3.14, $argument->getValue()); + self::assertEquals(ArgumentType::Value, $argument->getType()); } - public function testConstructorWithArrayContainingArgumentType(): void + public function testConstructorWithNullValue(): void { - $argument = new Identifier('column'); - - self::assertEquals('column', $argument->getValue()); - self::assertEquals(ArgumentType::Identifier, $argument->getType()); + $argument = new Value(null); + self::assertNull($argument->getValue()); + self::assertEquals(ArgumentType::Value, $argument->getType()); } public function testConstructorWithSimpleArray(): void @@ -78,14 +83,22 @@ public function testConstructorWithSimpleArray(): void self::assertEquals(ArgumentType::Values, $argument->getType()); } - public function testStaticValueMethod(): void + public function testConstructorWithSimpleValue(): void { - $argument = Argument::value('test_value'); - - self::assertEquals('test_value', $argument->getValue()); + $argument = new Value('test'); + self::assertEquals('test', $argument->getValue()); self::assertEquals(ArgumentType::Value, $argument->getType()); } + public function testConstructorWithSqlInterface(): void + { + $select = new Select(); + $argument = new ArgumentSelect($select); + + self::assertSame($select, $argument->getValue()); + self::assertEquals(ArgumentType::Select, $argument->getType()); + } + public function testStaticIdentifierMethod(): void { $argument = Argument::identifier('column_name'); @@ -102,27 +115,6 @@ public function testStaticLiteralMethod(): void self::assertEquals(ArgumentType::Literal, $argument->getType()); } - public function testConstructorWithBooleanValue(): void - { - $argument = new Value(true); - self::assertTrue($argument->getValue()); - self::assertEquals(ArgumentType::Value, $argument->getType()); - } - - public function testConstructorWithNullValue(): void - { - $argument = new Value(null); - self::assertNull($argument->getValue()); - self::assertEquals(ArgumentType::Value, $argument->getType()); - } - - public function testConstructorWithFloatValue(): void - { - $argument = new Value(3.14); - self::assertEquals(3.14, $argument->getValue()); - self::assertEquals(ArgumentType::Value, $argument->getType()); - } - public function testStaticSelectMethodCreatesSelectArgument(): void { $select = new Select(); @@ -131,4 +123,12 @@ public function testStaticSelectMethodCreatesSelectArgument(): void self::assertSame($select, $argument->getValue()); self::assertEquals(ArgumentType::Select, $argument->getType()); } + + public function testStaticValueMethod(): void + { + $argument = Argument::value('test_value'); + + self::assertEquals('test_value', $argument->getValue()); + self::assertEquals(ArgumentType::Value, $argument->getType()); + } } diff --git a/test/unit/Sql/CombineTest.php b/test/unit/Sql/CombineTest.php index bbe77ff72..409dba0ac 100644 --- a/test/unit/Sql/CombineTest.php +++ b/test/unit/Sql/CombineTest.php @@ -34,106 +34,6 @@ final class CombineTest extends TestCase protected Combine $combine; - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - #[Override] - protected function setUp(): void - { - $this->combine = new Combine(); - } - - public function testRejectsInvalidStatement(): void - { - $this->expectException(TypeError::class); - - /** @noinspection PhpParamsInspection */ - $this->combine->combine('foo'); - } - - public function testGetSqlString(): void - { - $this->combine - ->union(new Select('t1')) - ->intersect(new Select('t2')) - ->except(new Select('t3')) - ->union(new Select('t4')); - - self::assertEquals( - // @codingStandardsIgnoreStart - '(SELECT "t1".* FROM "t1") INTERSECT (SELECT "t2".* FROM "t2") EXCEPT (SELECT "t3".* FROM "t3") UNION (SELECT "t4".* FROM "t4")', - // @codingStandardsIgnoreEnd - $this->combine->getSqlString() - ); - } - - public function testGetSqlStringWithModifier(): void - { - $this->combine - ->union(new Select('t1')) - ->union(new Select('t2'), 'ALL'); - - self::assertEquals( - '(SELECT "t1".* FROM "t1") UNION ALL (SELECT "t2".* FROM "t2")', - $this->combine->getSqlString() - ); - } - - public function testGetSqlStringFromArray(): void - { - $this->combine->combine([ - [new Select('t1')], - [new Select('t2'), Combine::COMBINE_INTERSECT, 'ALL'], - [new Select('t3'), Combine::COMBINE_EXCEPT], - ]); - - self::assertEquals( - '(SELECT "t1".* FROM "t1") INTERSECT ALL (SELECT "t2".* FROM "t2") EXCEPT (SELECT "t3".* FROM "t3")', - $this->combine->getSqlString() - ); - - $this->combine = new Combine(); - $this->combine->combine([ - new Select('t1'), - new Select('t2'), - new Select('t3'), - ]); - - self::assertEquals( - '(SELECT "t1".* FROM "t1") UNION (SELECT "t2".* FROM "t2") UNION (SELECT "t3".* FROM "t3")', - $this->combine->getSqlString() - ); - } - - public function testGetSqlStringEmpty(): void - { - self::assertEmpty($this->combine->getSqlString()); - } - - public function testPrepareStatementWithModifier(): void - { - $select1 = new Select('t1'); - $select1->where(['x1' => 10]); - - $select2 = new Select('t2'); - $select2->where(['x2' => 20]); - - $this->combine->combine([ - $select1, - $select2, - ]); - - $adapter = $this->getMockAdapter(); - - $statement = $this->combine->prepareStatement($adapter, new StatementContainer()); - self::assertInstanceOf(StatementContainerInterface::class, $statement); - self::assertEquals( - '(SELECT "t1".* FROM "t1" WHERE "x1" = ?) UNION (SELECT "t2".* FROM "t2" WHERE "x2" = ?)', - $statement->getSql() - ); - } - public function testAlignColumns(): void { $select1 = new Select('t1'); @@ -148,8 +48,8 @@ public function testAlignColumns(): void ]); $this->combine - ->union([$select1, $select2]) - ->alignColumns(); + ->union([$select1, $select2]) + ->alignColumns(); // Verify first select has NULL for missing c2 self::assertEquals( @@ -158,7 +58,7 @@ public function testAlignColumns(): void 'c1' => 'c1', 'c2' => new Expression('NULL'), ], - $select1->getRawState('columns') + $select1->getRawState('columns'), ); // Verify second select has NULL for missing c0 @@ -168,10 +68,57 @@ public function testAlignColumns(): void 'c1' => 'c1', 'c2' => 'c2', ], - $select2->getRawState('columns') + $select2->getRawState('columns'), + ); + } + + public function testAlignColumnsAppendsNullExpressionsForMissingColumns(): void + { + $select1 = new Select('t1'); + $select1->columns(['a' => 'a']); + + $select2 = new Select('t2'); + $select2->columns(['a' => 'a', 'b' => 'b']); + + $this->combine->union([$select1, $select2])->alignColumns(); + + $columns1 = $select1->getRawState('columns'); + self::assertArrayHasKey('b', $columns1); + self::assertInstanceOf(Expression::class, $columns1['b']); + } + + public function testAlignColumnsReturnsEarlyWhenEmpty(): void + { + $combine = new Combine(); + $result = $combine->alignColumns(); + + self::assertSame($combine, $result); + } + + public function testCombineWithArrayOfSelectAndModifier(): void + { + $this->combine->combine([ + [new Select('t1'), 'UNION', 'ALL'], + [new Select('t2'), 'INTERSECT'], + ]); + + self::assertEquals( + '(SELECT "t1".* FROM "t1") INTERSECT (SELECT "t2".* FROM "t2")', + $this->combine->getSqlString(), ); } + public function testConstructorWithSelectDelegatesToCombine(): void + { + $select = new Select('foo'); + $combine = new Combine($select, Combine::COMBINE_EXCEPT, 'ALL'); + + $rawState = $combine->getRawState(); + self::assertCount(1, $rawState['combine']); + self::assertSame('except', $rawState['combine'][0]['type']); + self::assertSame('ALL', $rawState['combine'][0]['modifier']); + } + public function testGetRawState(): void { $select = new Select('t1'); @@ -189,55 +136,98 @@ public function testGetRawState(): void '0' => '*', ], ], - $this->combine->getRawState() + $this->combine->getRawState(), ); } - public function testCombineWithArrayOfSelectAndModifier(): void + public function testGetSqlString(): void + { + $this->combine + ->union(new Select('t1')) + ->intersect(new Select('t2')) + ->except(new Select('t3')) + ->union(new Select('t4')); + + self::assertEquals( + // @codingStandardsIgnoreStart + '(SELECT "t1".* FROM "t1") INTERSECT (SELECT "t2".* FROM "t2") EXCEPT (SELECT "t3".* FROM "t3") UNION (SELECT "t4".* FROM "t4")', + // @codingStandardsIgnoreEnd + $this->combine->getSqlString(), + ); + } + + public function testGetSqlStringEmpty(): void + { + self::assertEmpty($this->combine->getSqlString()); + } + + public function testGetSqlStringFromArray(): void { $this->combine->combine([ - [new Select('t1'), 'UNION', 'ALL'], - [new Select('t2'), 'INTERSECT'], + [new Select('t1')], + [new Select('t2'), Combine::COMBINE_INTERSECT, 'ALL'], + [new Select('t3'), Combine::COMBINE_EXCEPT], ]); self::assertEquals( - '(SELECT "t1".* FROM "t1") INTERSECT (SELECT "t2".* FROM "t2")', - $this->combine->getSqlString() + '(SELECT "t1".* FROM "t1") INTERSECT ALL (SELECT "t2".* FROM "t2") EXCEPT (SELECT "t3".* FROM "t3")', + $this->combine->getSqlString(), + ); + + $this->combine = new Combine(); + $this->combine->combine([ + new Select('t1'), + new Select('t2'), + new Select('t3'), + ]); + + self::assertEquals( + '(SELECT "t1".* FROM "t1") UNION (SELECT "t2".* FROM "t2") UNION (SELECT "t3".* FROM "t3")', + $this->combine->getSqlString(), ); } - public function testAlignColumnsAppendsNullExpressionsForMissingColumns(): void + public function testGetSqlStringWithModifier(): void + { + $this->combine + ->union(new Select('t1')) + ->union(new Select('t2'), 'ALL'); + + self::assertEquals( + '(SELECT "t1".* FROM "t1") UNION ALL (SELECT "t2".* FROM "t2")', + $this->combine->getSqlString(), + ); + } + + public function testPrepareStatementWithModifier(): void { $select1 = new Select('t1'); - $select1->columns(['a' => 'a']); + $select1->where(['x1' => 10]); $select2 = new Select('t2'); - $select2->columns(['a' => 'a', 'b' => 'b']); - - $this->combine->union([$select1, $select2])->alignColumns(); + $select2->where(['x2' => 20]); - $columns1 = $select1->getRawState('columns'); - self::assertArrayHasKey('b', $columns1); - self::assertInstanceOf(Expression::class, $columns1['b']); - } + $this->combine->combine([ + $select1, + $select2, + ]); - public function testConstructorWithSelectDelegatesToCombine(): void - { - $select = new Select('foo'); - $combine = new Combine($select, Combine::COMBINE_EXCEPT, 'ALL'); + $adapter = $this->getMockAdapter(); - $rawState = $combine->getRawState(); - self::assertCount(1, $rawState['combine']); - self::assertSame('except', $rawState['combine'][0]['type']); - self::assertSame('ALL', $rawState['combine'][0]['modifier']); + $statement = $this->combine->prepareStatement($adapter, new StatementContainer()); + self::assertInstanceOf(StatementContainerInterface::class, $statement); + self::assertEquals( + '(SELECT "t1".* FROM "t1" WHERE "x1" = ?) UNION (SELECT "t2".* FROM "t2" WHERE "x2" = ?)', + $statement->getSql(), + ); } - public function testAlignColumnsReturnsEarlyWhenEmpty(): void + public function testRejectsInvalidStatement(): void { - $combine = new Combine(); - $result = $combine->alignColumns(); + $this->expectException(TypeError::class); - self::assertSame($combine, $result); + /** @noinspection PhpParamsInspection */ + $this->combine->combine('foo'); } protected function getMockAdapter(): Adapter|MockObject @@ -245,10 +235,9 @@ protected function getMockAdapter(): Adapter|MockObject $parameterContainer = new ParameterContainer(); $mockStatement = $this->getMockBuilder(StatementInterface::class)->getMock(); - $mockStatement->expects($this->any())->method('getParameterContainer') - ->willReturn($parameterContainer); + $mockStatement->expects($this->any())->method('getParameterContainer')->willReturn($parameterContainer); - $setGetSqlFunction = function ($sql = null) use ($mockStatement) { + $setGetSqlFunction = static function ($sql = null) use ($mockStatement) { static $sqlValue; if ($sql) { $sqlValue = $sql; @@ -266,4 +255,14 @@ protected function getMockAdapter(): Adapter|MockObject return $this->createMockAdapter($mockDriver); } + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + #[Override] + protected function setUp(): void + { + $this->combine = new Combine(); + } } diff --git a/test/unit/Sql/Ddl/AlterTableTest.php b/test/unit/Sql/Ddl/AlterTableTest.php index a5bf50a46..c1bb71f5a 100644 --- a/test/unit/Sql/Ddl/AlterTableTest.php +++ b/test/unit/Sql/Ddl/AlterTableTest.php @@ -38,14 +38,6 @@ #[CoversMethod(AlterTable::class, 'processTableOptions')] class AlterTableTest extends TestCase { - public function testSetTable(): void - { - $at = new AlterTable(); - self::assertEquals('', $at->getRawState('table')); - self::assertSame($at, $at->setTable('test')); - self::assertEquals('test', $at->getRawState('table')); - } - public function testAddColumn(): void { $at = new AlterTable(); @@ -55,6 +47,44 @@ public function testAddColumn(): void self::assertEquals([$colMock], $at->getRawState(AlterTable::ADD_COLUMNS)); } + public function testAddConstraint(): void + { + $at = new AlterTable(); + /** @var ConstraintInterface $conMock */ + $conMock = $this->getMockBuilder(ConstraintInterface::class)->getMock(); + self::assertSame($at, $at->addConstraint($conMock)); + self::assertEquals([$conMock], $at->getRawState(AlterTable::ADD_CONSTRAINTS)); + } + + public function testAddConstraintGeneratesCorrectSql(): void + { + $at = new AlterTable('orders'); + $fk = new Constraint\ForeignKey('fk_user', 'user_id', 'users', 'id'); + $at->addConstraint($fk); + + $sql = $at->getSqlString(); + self::assertStringContainsString('ADD CONSTRAINT', $sql); + self::assertStringContainsString('"fk_user"', $sql); + self::assertStringContainsString('FOREIGN KEY', $sql); + } + + public function testChainedOperations(): void + { + $at = new AlterTable(); + $col = $this->getMockBuilder(ColumnInterface::class)->getMock(); + $con = $this->getMockBuilder(ConstraintInterface::class)->getMock(); + + $result = $at->setTable('test') + ->addColumn($col) + ->dropColumn('old') + ->addConstraint($con) + ->dropConstraint('old_fk') + ->dropIndex('old_idx'); + + self::assertSame($at, $result); + self::assertEquals('test', $at->getRawState(AlterTable::TABLE)); + } + public function testChangeColumn(): void { $at = new AlterTable(); @@ -64,6 +94,40 @@ public function testChangeColumn(): void self::assertEquals(['newname' => $colMock], $at->getRawState(AlterTable::CHANGE_COLUMNS)); } + public function testChangeColumnGeneratesCorrectSql(): void + { + $at = new AlterTable('users'); + $at->changeColumn('old_name', new Column\Varchar('new_name', 100)); + + $sql = $at->getSqlString(); + self::assertStringContainsString('CHANGE COLUMN', $sql); + self::assertStringContainsString('"old_name"', $sql); + self::assertStringContainsString('"new_name"', $sql); + self::assertStringContainsString('VARCHAR(100)', $sql); + } + + public function testConstructorWithEmptyTable(): void + { + $at = new AlterTable(); + self::assertEquals('', $at->getRawState('table')); + } + + public function testConstructorWithTable(): void + { + $at = new AlterTable('test_table'); + self::assertEquals('test_table', $at->getRawState('table')); + } + + public function testConstructorWithTableIdentifier(): void + { + $tableId = new TableIdentifier('bar', 'foo'); + $at = new AlterTable($tableId); + + // Get full raw state to avoid type issue with getRawState('table') + $rawState = $at->getRawState(); + self::assertSame($tableId, $rawState['table']); + } + public function testDropColumn(): void { $at = new AlterTable(); @@ -78,15 +142,6 @@ public function testDropConstraint(): void self::assertEquals(['foo'], $at->getRawState(AlterTable::DROP_CONSTRAINTS)); } - public function testAddConstraint(): void - { - $at = new AlterTable(); - /** @var ConstraintInterface $conMock */ - $conMock = $this->getMockBuilder(ConstraintInterface::class)->getMock(); - self::assertSame($at, $at->addConstraint($conMock)); - self::assertEquals([$conMock], $at->getRawState(AlterTable::ADD_CONSTRAINTS)); - } - public function testDropIndex(): void { $at = new AlterTable(); @@ -94,64 +149,30 @@ public function testDropIndex(): void self::assertEquals(['foo'], $at->getRawState(AlterTable::DROP_INDEXES)); } - /** - * @todo Implement testGetSqlString(). - */ - public function testGetSqlString(): void + public function testEmptyAlterTableGeneratesMinimalSql(): void { - $at = new AlterTable('foo'); - $at->addColumn(new Column\Varchar('another', 255)); - $at->changeColumn('name', new Column\Varchar('new_name', 50)); - $at->dropColumn('foo'); - $at->addConstraint(new Constraint\ForeignKey('my_fk', 'other_id', 'other_table', 'id', 'CASCADE', 'CASCADE')); - $at->dropConstraint('my_constraint'); - $at->dropIndex('my_index'); - - $expected = <<getSqlString(); - self::assertEquals( - str_replace(["\r", "\n"], '', $expected), - str_replace(["\r", "\n"], '', $actual) - ); - - $at = new AlterTable(new TableIdentifier('foo')); - $at->addColumn(new Column\Column('bar')); - $this->assertEquals("ALTER TABLE \"foo\"\n ADD COLUMN \"bar\" INTEGER NOT NULL", $at->getSqlString()); + $at = new AlterTable('test_table'); + $sql = $at->getSqlString(); - $at = new AlterTable(new TableIdentifier('bar', 'foo')); - $at->addColumn(new Column\Column('baz')); - $this->assertEquals("ALTER TABLE \"foo\".\"bar\"\n ADD COLUMN \"baz\" INTEGER NOT NULL", $at->getSqlString()); + // Should have ALTER TABLE but no operations + self::assertStringContainsString('ALTER TABLE "test_table"', $sql); } - public function testConstructorWithTable(): void + public function testGetOptionsReturnsEmpty(): void { - $at = new AlterTable('test_table'); - self::assertEquals('test_table', $at->getRawState('table')); + $at = new AlterTable('foo'); + self::assertEquals([], $at->getOptions()); } - public function testConstructorWithTableIdentifier(): void + public function testGetRawStateIncludesTableOptions(): void { - $tableId = new TableIdentifier('bar', 'foo'); - $at = new AlterTable($tableId); + $at = new AlterTable('foo'); + $at->setOption('engine', new Literal('InnoDB')); - // Get full raw state to avoid type issue with getRawState('table') $rawState = $at->getRawState(); - self::assertSame($tableId, $rawState['table']); - } - public function testConstructorWithEmptyTable(): void - { - $at = new AlterTable(); - self::assertEquals('', $at->getRawState('table')); + self::assertArrayHasKey(AlterTable::TABLE_OPTIONS, $rawState); + self::assertEquals(['engine' => new Literal('InnoDB')], $rawState[AlterTable::TABLE_OPTIONS]); } public function testGetRawStateReturnsAllState(): void @@ -187,6 +208,16 @@ public function testGetRawStateReturnsAllState(): void self::assertEquals(['drop_idx'], $rawState[AlterTable::DROP_INDEXES]); } + public function testGetRawStateWithInvalidKey(): void + { + $at = new AlterTable('test'); + $result = $at->getRawState('invalid_key'); + + // Should return full array when key doesn't exist + self::assertIsArray($result); + self::assertArrayHasKey(AlterTable::TABLE, $result); + } + public function testGetRawStateWithSpecificKey(): void { $at = new AlterTable('my_table'); @@ -198,115 +229,101 @@ public function testGetRawStateWithSpecificKey(): void self::assertEquals([], $at->getRawState(AlterTable::ADD_COLUMNS)); } - public function testMultipleColumnsAndConstraints(): void + /** + * @todo Implement testGetSqlString(). + */ + public function testGetSqlString(): void { - $at = new AlterTable('users'); + $at = new AlterTable('foo'); + $at->addColumn(new Column\Varchar('another', 255)); + $at->changeColumn('name', new Column\Varchar('new_name', 50)); + $at->dropColumn('foo'); + $at->addConstraint(new Constraint\ForeignKey('my_fk', 'other_id', 'other_table', 'id', 'CASCADE', 'CASCADE')); + $at->dropConstraint('my_constraint'); + $at->dropIndex('my_index'); - $col1 = new Column\Varchar('email', 255); - $col2 = new Column\Integer('age'); - $col3 = new Column\Text('bio'); + $expected = <<addColumn($col1); - $at->addColumn($col2); - $at->addColumn($col3); + $actual = $at->getSqlString(); + self::assertEquals( + str_replace(["\r", "\n"], '', $expected), + str_replace(["\r", "\n"], '', $actual), + ); - self::assertCount(3, $at->getRawState(AlterTable::ADD_COLUMNS)); + $at = new AlterTable(new TableIdentifier('foo')); + $at->addColumn(new Column\Column('bar')); + $this->assertEquals("ALTER TABLE \"foo\"\n ADD COLUMN \"bar\" INTEGER NOT NULL", $at->getSqlString()); - $sql = $at->getSqlString(); - self::assertStringContainsString('ADD COLUMN "email"', $sql); - self::assertStringContainsString('ADD COLUMN "age"', $sql); - self::assertStringContainsString('ADD COLUMN "bio"', $sql); + $at = new AlterTable(new TableIdentifier('bar', 'foo')); + $at->addColumn(new Column\Column('baz')); + $this->assertEquals("ALTER TABLE \"foo\".\"bar\"\n ADD COLUMN \"baz\" INTEGER NOT NULL", $at->getSqlString()); } - public function testMultipleDropOperations(): void + public function testGetSqlStringWithBoolOption(): void { - $at = new AlterTable('products'); - - $at->dropColumn('old_col1'); - $at->dropColumn('old_col2'); - $at->dropConstraint('old_fk'); - $at->dropIndex('old_idx'); + $at = new AlterTable('foo'); + $at->setOption('pack_keys', true); $sql = $at->getSqlString(); - self::assertStringContainsString('DROP COLUMN "old_col1"', $sql); - self::assertStringContainsString('DROP COLUMN "old_col2"', $sql); - self::assertStringContainsString('DROP CONSTRAINT "old_fk"', $sql); - self::assertStringContainsString('DROP INDEX "old_idx"', $sql); - } - - public function testChainedOperations(): void - { - $at = new AlterTable(); - $col = $this->getMockBuilder(ColumnInterface::class)->getMock(); - $con = $this->getMockBuilder(ConstraintInterface::class)->getMock(); - - $result = $at->setTable('test') - ->addColumn($col) - ->dropColumn('old') - ->addConstraint($con) - ->dropConstraint('old_fk') - ->dropIndex('old_idx'); - - self::assertSame($at, $result); - self::assertEquals('test', $at->getRawState(AlterTable::TABLE)); + self::assertStringContainsString('PACK_KEYS = 1', $sql); } - public function testChangeColumnGeneratesCorrectSql(): void + public function testGetSqlStringWithColumnAndEngineOption(): void { - $at = new AlterTable('users'); - $at->changeColumn('old_name', new Column\Varchar('new_name', 100)); + $at = new AlterTable('foo'); + $at->addColumn(new Column\Column('bar')); + $at->setOption('engine', new Literal('InnoDB')); $sql = $at->getSqlString(); - self::assertStringContainsString('CHANGE COLUMN', $sql); - self::assertStringContainsString('"old_name"', $sql); - self::assertStringContainsString('"new_name"', $sql); - self::assertStringContainsString('VARCHAR(100)', $sql); + self::assertStringContainsString('ADD COLUMN "bar" INTEGER NOT NULL', $sql); + self::assertStringContainsString('ENGINE = InnoDB', $sql); } - public function testMultipleChangeColumns(): void + public function testGetSqlStringWithEngineOption(): void { - $at = new AlterTable('products'); - $at->changeColumn('price', new Column\Decimal('cost', 10, 2)); - $at->changeColumn('name', new Column\Varchar('title', 200)); + $at = new AlterTable('foo'); + $at->setOption('engine', new Literal('InnoDB')); $sql = $at->getSqlString(); - self::assertStringContainsString('CHANGE COLUMN "price" "cost"', $sql); - self::assertStringContainsString('CHANGE COLUMN "name" "title"', $sql); + self::assertStringContainsString('ALTER TABLE "foo"', $sql); + self::assertStringContainsString('ENGINE = InnoDB', $sql); } - public function testAddConstraintGeneratesCorrectSql(): void + public function testGetSqlStringWithIntOption(): void { - $at = new AlterTable('orders'); - $fk = new Constraint\ForeignKey('fk_user', 'user_id', 'users', 'id'); - $at->addConstraint($fk); + $at = new AlterTable('foo'); + $at->setOption('auto_increment', 100); $sql = $at->getSqlString(); - self::assertStringContainsString('ADD CONSTRAINT', $sql); - self::assertStringContainsString('"fk_user"', $sql); - self::assertStringContainsString('FOREIGN KEY', $sql); + self::assertStringContainsString('AUTO_INCREMENT = 100', $sql); } - public function testMultipleConstraints(): void + public function testGetSqlStringWithMultipleOptions(): void { - $at = new AlterTable('orders'); - $fk1 = new Constraint\ForeignKey('fk_user', 'user_id', 'users', 'id'); - $fk2 = new Constraint\ForeignKey('fk_product', 'product_id', 'products', 'id'); - - $at->addConstraint($fk1); - $at->addConstraint($fk2); + $at = new AlterTable('foo'); + $at->setOption('engine', new Literal('InnoDB')); + $at->setOption('auto_increment', 100); $sql = $at->getSqlString(); - self::assertStringContainsString('"fk_user"', $sql); - self::assertStringContainsString('"fk_product"', $sql); + self::assertStringContainsString('ENGINE = InnoDB', $sql); + self::assertStringContainsString('AUTO_INCREMENT = 100', $sql); } - public function testEmptyAlterTableGeneratesMinimalSql(): void + public function testGetSqlStringWithStringOption(): void { - $at = new AlterTable('test_table'); - $sql = $at->getSqlString(); + $at = new AlterTable('foo'); + $at->setOption('comment', 'My table'); - // Should have ALTER TABLE but no operations - self::assertStringContainsString('ALTER TABLE "test_table"', $sql); + $sql = $at->getSqlString(); + self::assertStringContainsString('COMMENT = \'My table\'', $sql); } public function testMixedOperationsInCorrectOrder(): void @@ -332,118 +349,101 @@ public function testMixedOperationsInCorrectOrder(): void self::assertStringContainsString('DROP INDEX "old_index"', $sql); } - public function testGetRawStateWithInvalidKey(): void - { - $at = new AlterTable('test'); - $result = $at->getRawState('invalid_key'); - - // Should return full array when key doesn't exist - self::assertIsArray($result); - self::assertArrayHasKey(AlterTable::TABLE, $result); - } - - public function testTableIdentifierInChangeColumn(): void + public function testMultipleChangeColumns(): void { - $at = new AlterTable(new TableIdentifier('table', 'schema')); - $at->changeColumn('col1', new Column\Integer('col1_new')); + $at = new AlterTable('products'); + $at->changeColumn('price', new Column\Decimal('cost', 10, 2)); + $at->changeColumn('name', new Column\Varchar('title', 200)); $sql = $at->getSqlString(); - self::assertStringContainsString('"schema"."table"', $sql); - self::assertStringContainsString('CHANGE COLUMN "col1" "col1_new"', $sql); + self::assertStringContainsString('CHANGE COLUMN "price" "cost"', $sql); + self::assertStringContainsString('CHANGE COLUMN "name" "title"', $sql); } - public function testSetOptionFluentInterface(): void + public function testMultipleColumnsAndConstraints(): void { - $at = new AlterTable('foo'); - $result = $at->setOption('engine', new Literal('InnoDB')); - - self::assertSame($at, $result); - self::assertEquals(['engine' => new Literal('InnoDB')], $at->getOptions()); - } + $at = new AlterTable('users'); - public function testSetOptionsReplacesAll(): void - { - $at = new AlterTable('foo'); - $at->setOption('engine', new Literal('InnoDB')); + $col1 = new Column\Varchar('email', 255); + $col2 = new Column\Integer('age'); + $col3 = new Column\Text('bio'); - $at->setOptions(['charset' => new Literal('utf8mb4')]); + $at->addColumn($col1); + $at->addColumn($col2); + $at->addColumn($col3); - self::assertEquals(['charset' => new Literal('utf8mb4')], $at->getOptions()); - } + self::assertCount(3, $at->getRawState(AlterTable::ADD_COLUMNS)); - public function testGetOptionsReturnsEmpty(): void - { - $at = new AlterTable('foo'); - self::assertEquals([], $at->getOptions()); + $sql = $at->getSqlString(); + self::assertStringContainsString('ADD COLUMN "email"', $sql); + self::assertStringContainsString('ADD COLUMN "age"', $sql); + self::assertStringContainsString('ADD COLUMN "bio"', $sql); } - public function testGetRawStateIncludesTableOptions(): void + public function testMultipleConstraints(): void { - $at = new AlterTable('foo'); - $at->setOption('engine', new Literal('InnoDB')); - - $rawState = $at->getRawState(); - - self::assertArrayHasKey(AlterTable::TABLE_OPTIONS, $rawState); - self::assertEquals(['engine' => new Literal('InnoDB')], $rawState[AlterTable::TABLE_OPTIONS]); - } + $at = new AlterTable('orders'); + $fk1 = new Constraint\ForeignKey('fk_user', 'user_id', 'users', 'id'); + $fk2 = new Constraint\ForeignKey('fk_product', 'product_id', 'products', 'id'); - public function testGetSqlStringWithEngineOption(): void - { - $at = new AlterTable('foo'); - $at->setOption('engine', new Literal('InnoDB')); + $at->addConstraint($fk1); + $at->addConstraint($fk2); $sql = $at->getSqlString(); - self::assertStringContainsString('ALTER TABLE "foo"', $sql); - self::assertStringContainsString('ENGINE = InnoDB', $sql); + self::assertStringContainsString('"fk_user"', $sql); + self::assertStringContainsString('"fk_product"', $sql); } - public function testGetSqlStringWithColumnAndEngineOption(): void + public function testMultipleDropOperations(): void { - $at = new AlterTable('foo'); - $at->addColumn(new Column\Column('bar')); - $at->setOption('engine', new Literal('InnoDB')); + $at = new AlterTable('products'); + + $at->dropColumn('old_col1'); + $at->dropColumn('old_col2'); + $at->dropConstraint('old_fk'); + $at->dropIndex('old_idx'); $sql = $at->getSqlString(); - self::assertStringContainsString('ADD COLUMN "bar" INTEGER NOT NULL', $sql); - self::assertStringContainsString('ENGINE = InnoDB', $sql); + self::assertStringContainsString('DROP COLUMN "old_col1"', $sql); + self::assertStringContainsString('DROP COLUMN "old_col2"', $sql); + self::assertStringContainsString('DROP CONSTRAINT "old_fk"', $sql); + self::assertStringContainsString('DROP INDEX "old_idx"', $sql); } - public function testGetSqlStringWithStringOption(): void + public function testSetOptionFluentInterface(): void { - $at = new AlterTable('foo'); - $at->setOption('comment', 'My table'); + $at = new AlterTable('foo'); + $result = $at->setOption('engine', new Literal('InnoDB')); - $sql = $at->getSqlString(); - self::assertStringContainsString('COMMENT = \'My table\'', $sql); + self::assertSame($at, $result); + self::assertEquals(['engine' => new Literal('InnoDB')], $at->getOptions()); } - public function testGetSqlStringWithIntOption(): void + public function testSetOptionsReplacesAll(): void { $at = new AlterTable('foo'); - $at->setOption('auto_increment', 100); + $at->setOption('engine', new Literal('InnoDB')); - $sql = $at->getSqlString(); - self::assertStringContainsString('AUTO_INCREMENT = 100', $sql); + $at->setOptions(['charset' => new Literal('utf8mb4')]); + + self::assertEquals(['charset' => new Literal('utf8mb4')], $at->getOptions()); } - public function testGetSqlStringWithBoolOption(): void + public function testSetTable(): void { - $at = new AlterTable('foo'); - $at->setOption('pack_keys', true); - - $sql = $at->getSqlString(); - self::assertStringContainsString('PACK_KEYS = 1', $sql); + $at = new AlterTable(); + self::assertEquals('', $at->getRawState('table')); + self::assertSame($at, $at->setTable('test')); + self::assertEquals('test', $at->getRawState('table')); } - public function testGetSqlStringWithMultipleOptions(): void + public function testTableIdentifierInChangeColumn(): void { - $at = new AlterTable('foo'); - $at->setOption('engine', new Literal('InnoDB')); - $at->setOption('auto_increment', 100); + $at = new AlterTable(new TableIdentifier('table', 'schema')); + $at->changeColumn('col1', new Column\Integer('col1_new')); $sql = $at->getSqlString(); - self::assertStringContainsString('ENGINE = InnoDB', $sql); - self::assertStringContainsString('AUTO_INCREMENT = 100', $sql); + self::assertStringContainsString('"schema"."table"', $sql); + self::assertStringContainsString('CHANGE COLUMN "col1" "col1_new"', $sql); } } diff --git a/test/unit/Sql/Ddl/Column/AbstractLengthColumnTest.php b/test/unit/Sql/Ddl/Column/AbstractLengthColumnTest.php index c0d6d5e24..5fe37436a 100644 --- a/test/unit/Sql/Ddl/Column/AbstractLengthColumnTest.php +++ b/test/unit/Sql/Ddl/Column/AbstractLengthColumnTest.php @@ -19,15 +19,24 @@ final class AbstractLengthColumnTest extends TestCase /** * @throws Exception */ - public function testSetLength(): void + public function testGetExpressionData(): void { $column = $this->getMockBuilder(AbstractLengthColumn::class) - ->setConstructorArgs(['foo', 55]) + ->setConstructorArgs(['foo', 4]) ->onlyMethods([]) ->getMock(); - self::assertEquals(55, $column->getLength()); - self::assertSame($column, $column->setLength(20)); - self::assertEquals(20, $column->getLength()); + + $expressionData = $column->getExpressionData(); + + self::assertEquals('%s %s(%s) NOT NULL', $expressionData['spec']); + self::assertEquals( + [ + new Identifier('foo'), + new Literal('INTEGER'), + new Literal('4'), + ], + $expressionData['values'], + ); } /** @@ -45,20 +54,14 @@ public function testGetLength(): void /** * @throws Exception */ - public function testGetExpressionData(): void + public function testSetLength(): void { $column = $this->getMockBuilder(AbstractLengthColumn::class) - ->setConstructorArgs(['foo', 4]) + ->setConstructorArgs(['foo', 55]) ->onlyMethods([]) ->getMock(); - - $expressionData = $column->getExpressionData(); - - self::assertEquals('%s %s(%s) NOT NULL', $expressionData['spec']); - self::assertEquals([ - new Identifier('foo'), - new Literal('INTEGER'), - new Literal('4'), - ], $expressionData['values']); + self::assertEquals(55, $column->getLength()); + self::assertSame($column, $column->setLength(20)); + self::assertEquals(20, $column->getLength()); } } diff --git a/test/unit/Sql/Ddl/Column/AbstractPrecisionColumnTest.php b/test/unit/Sql/Ddl/Column/AbstractPrecisionColumnTest.php index f5a8f719a..459d7748c 100644 --- a/test/unit/Sql/Ddl/Column/AbstractPrecisionColumnTest.php +++ b/test/unit/Sql/Ddl/Column/AbstractPrecisionColumnTest.php @@ -20,15 +20,13 @@ final class AbstractPrecisionColumnTest extends TestCase /** * @throws Exception */ - public function testSetDigits(): void + public function testGetDecimal(): void { $column = $this->getMockBuilder(AbstractPrecisionColumn::class) - ->setConstructorArgs(['foo', 10]) + ->setConstructorArgs(['foo', 10, 5]) ->onlyMethods([]) ->getMock(); - self::assertEquals(10, $column->getDigits()); - self::assertSame($column, $column->setDigits(12)); - self::assertEquals(12, $column->getDigits()); + self::assertEquals(5, $column->getDecimal()); } /** @@ -46,46 +44,51 @@ public function testGetDigits(): void /** * @throws Exception */ - public function testSetDecimal(): void + public function testGetExpressionData(): void { $column = $this->getMockBuilder(AbstractPrecisionColumn::class) ->setConstructorArgs(['foo', 10, 5]) ->onlyMethods([]) ->getMock(); - self::assertEquals(5, $column->getDecimal()); - self::assertSame($column, $column->setDecimal(2)); - self::assertEquals(2, $column->getDecimal()); + + $expressionData = $column->getExpressionData(); + + self::assertEquals('%s %s(%s) NOT NULL', $expressionData['spec']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('INTEGER'), + Argument::literal('10,5'), + ], + $expressionData['values'], + ); } /** * @throws Exception */ - public function testGetDecimal(): void + public function testSetDecimal(): void { $column = $this->getMockBuilder(AbstractPrecisionColumn::class) ->setConstructorArgs(['foo', 10, 5]) ->onlyMethods([]) ->getMock(); self::assertEquals(5, $column->getDecimal()); + self::assertSame($column, $column->setDecimal(2)); + self::assertEquals(2, $column->getDecimal()); } /** * @throws Exception */ - public function testGetExpressionData(): void + public function testSetDigits(): void { $column = $this->getMockBuilder(AbstractPrecisionColumn::class) - ->setConstructorArgs(['foo', 10, 5]) + ->setConstructorArgs(['foo', 10]) ->onlyMethods([]) ->getMock(); - - $expressionData = $column->getExpressionData(); - - self::assertEquals('%s %s(%s) NOT NULL', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('INTEGER'), - Argument::literal('10,5'), - ], $expressionData['values']); + self::assertEquals(10, $column->getDigits()); + self::assertSame($column, $column->setDigits(12)); + self::assertEquals(12, $column->getDigits()); } } diff --git a/test/unit/Sql/Ddl/Column/BigIntegerTest.php b/test/unit/Sql/Ddl/Column/BigIntegerTest.php index b5c6dd547..55b330e18 100644 --- a/test/unit/Sql/Ddl/Column/BigIntegerTest.php +++ b/test/unit/Sql/Ddl/Column/BigIntegerTest.php @@ -14,12 +14,6 @@ #[CoversMethod(Column::class, 'getExpressionData')] final class BigIntegerTest extends TestCase { - public function testObjectConstruction(): void - { - $integer = new BigInteger('foo'); - self::assertEquals('foo', $integer->getName()); - } - public function testGetExpressionData(): void { $column = new BigInteger('foo'); @@ -27,7 +21,7 @@ public function testGetExpressionData(): void self::assertEquals( '%s %s NOT NULL', - $expressionData['spec'] + $expressionData['spec'], ); self::assertEquals( @@ -35,7 +29,13 @@ public function testGetExpressionData(): void Argument::Identifier('foo'), Argument::Literal('BIGINT'), ], - $expressionData['values'] + $expressionData['values'], ); } + + public function testObjectConstruction(): void + { + $integer = new BigInteger('foo'); + self::assertEquals('foo', $integer->getName()); + } } diff --git a/test/unit/Sql/Ddl/Column/BinaryTest.php b/test/unit/Sql/Ddl/Column/BinaryTest.php index fc39a4ec7..26e29fa5c 100644 --- a/test/unit/Sql/Ddl/Column/BinaryTest.php +++ b/test/unit/Sql/Ddl/Column/BinaryTest.php @@ -14,15 +14,18 @@ final class BinaryTest extends TestCase { public function testGetExpressionData(): void { - $column = new Binary('foo', 10000000); + $column = new Binary('foo', 10_000_000); $expressionData = $column->getExpressionData(); self::assertEquals('%s %s(%s) NOT NULL', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('BINARY'), - Argument::literal('10000000'), - ], $expressionData['values']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('BINARY'), + Argument::literal('10000000'), + ], + $expressionData['values'], + ); } } diff --git a/test/unit/Sql/Ddl/Column/BlobTest.php b/test/unit/Sql/Ddl/Column/BlobTest.php index 4052bd4dd..0cf51bc20 100644 --- a/test/unit/Sql/Ddl/Column/BlobTest.php +++ b/test/unit/Sql/Ddl/Column/BlobTest.php @@ -20,9 +20,12 @@ public function testGetExpressionData(): void $expressionData = $column->getExpressionData(); self::assertEquals('%s %s NOT NULL', $expressionData['spec']); - self::assertEquals([ - new Identifier('foo'), - new Literal('BLOB'), - ], $expressionData['values']); + self::assertEquals( + [ + new Identifier('foo'), + new Literal('BLOB'), + ], + $expressionData['values'], + ); } } diff --git a/test/unit/Sql/Ddl/Column/BooleanTest.php b/test/unit/Sql/Ddl/Column/BooleanTest.php index 1d53435ca..417bd959f 100644 --- a/test/unit/Sql/Ddl/Column/BooleanTest.php +++ b/test/unit/Sql/Ddl/Column/BooleanTest.php @@ -22,10 +22,13 @@ public function testGetExpressionData(): void $expressionData = $column->getExpressionData(); self::assertEquals('%s %s NOT NULL', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('BOOLEAN'), - ], $expressionData['values']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('BOOLEAN'), + ], + $expressionData['values'], + ); } #[Group('6257')] diff --git a/test/unit/Sql/Ddl/Column/CharTest.php b/test/unit/Sql/Ddl/Column/CharTest.php index 2773ab1c0..cfcc17908 100644 --- a/test/unit/Sql/Ddl/Column/CharTest.php +++ b/test/unit/Sql/Ddl/Column/CharTest.php @@ -20,10 +20,13 @@ public function testGetExpressionData(): void $expressionData = $column->getExpressionData(); self::assertEquals('%s %s(%s) NOT NULL', $expressionData['spec']); - self::assertEquals([ - new Identifier('foo'), - new Literal('CHAR'), - new Literal('20'), - ], $expressionData['values']); + self::assertEquals( + [ + new Identifier('foo'), + new Literal('CHAR'), + new Literal('20'), + ], + $expressionData['values'], + ); } } diff --git a/test/unit/Sql/Ddl/Column/ColumnTest.php b/test/unit/Sql/Ddl/Column/ColumnTest.php index d17c31192..2db3fa68f 100644 --- a/test/unit/Sql/Ddl/Column/ColumnTest.php +++ b/test/unit/Sql/Ddl/Column/ColumnTest.php @@ -28,6 +28,15 @@ #[Group('unit')] final class ColumnTest extends TestCase { + public function testAddConstraintAppendsConstraintToColumn(): void + { + $column = new Column('id'); + + $result = $column->addConstraint(new PrimaryKey()); + + self::assertSame($column, $result); + } + public function testConstructor(): void { $column = new Column('test_col', true, 'default_val', ['option1' => 'value1']); @@ -37,139 +46,186 @@ public function testConstructor(): void self::assertEquals(['option1' => 'value1'], $column->getOptions()); } - public function testSetName(): void + public function testGetExpressionData(): void { $column = new Column(); + $column->setName('foo'); - // First mutation - $result = $column->setName('foo'); + $expressionData = $column->getExpressionData(); - // Verify fluent interface - self::assertSame($column, $result); + self::assertEquals('%s %s NOT NULL', $expressionData['spec']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('INTEGER'), + ], + $expressionData['values'], + ); - // Verify the first mutation occurred - self::assertEquals('foo', $column->getName()); + $column->setNullable(true); - // Second mutation to verify mutability - $column->setName('bar'); + $expressionData = $column->getExpressionData(); - // Verify the instance was actually mutated - self::assertEquals('bar', $column->getName()); + self::assertEquals('%s %s NULL DEFAULT NULL', $expressionData['spec']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('INTEGER'), + ], + $expressionData['values'], + ); + + $column->setDefault('bar'); + + $expressionData = $column->getExpressionData(); + + self::assertEquals('%s %s NULL DEFAULT %s', $expressionData['spec']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('INTEGER'), + Argument::value('bar'), + ], + $expressionData['values'], + ); } - public function testSetNullable(): void + public function testGetExpressionDataIncludesConstraints(): void { - $column = new Column(); + $column = new Column('id'); + $column->addConstraint(new PrimaryKey()); - // First mutation - $result = $column->setNullable(true); + $expressionData = $column->getExpressionData(); - // Verify fluent interface - self::assertSame($column, $result); + self::assertStringContainsString('PRIMARY KEY', $expressionData['spec']); + } - // Verify the first mutation occurred - self::assertTrue($column->isNullable()); + public function testGetExpressionDataIncludesConstraintValues(): void + { + $column = new Column('id'); + $column->addConstraint(new PrimaryKey('id', 'pk_id')); - // Second mutation to verify mutability - $column->setNullable(false); + $expressionData = $column->getExpressionData(); - // Verify the instance was actually mutated - self::assertFalse($column->isNullable()); + self::assertNotEmpty($expressionData['values']); } - public function testSetDefault(): void + public function testGetExpressionDataWithBoolDefault(): void { $column = new Column(); + $column->setName('is_active'); + $column->setDefault(false); - // First mutation - $result = $column->setDefault('foo bar'); + $expressionData = $column->getExpressionData(); - // Verify fluent interface - self::assertSame($column, $result); + self::assertEquals('%s %s NOT NULL DEFAULT %s', $expressionData['spec']); + self::assertEquals( + [ + Argument::identifier('is_active'), + Argument::literal('INTEGER'), + Argument::value(false), + ], + $expressionData['values'], + ); + } - // Verify the first mutation occurred - self::assertEquals('foo bar', $column->getDefault()); + public function testGetExpressionDataWithFloatDefault(): void + { + $column = new Column(); + $column->setName('rate'); + $column->setDefault(9.99); - // Second mutation to verify mutability - $column->setDefault('baz qux'); + $expressionData = $column->getExpressionData(); - // Verify the instance was actually mutated - self::assertEquals('baz qux', $column->getDefault()); + self::assertEquals('%s %s NOT NULL DEFAULT %s', $expressionData['spec']); + self::assertEquals( + [ + Argument::identifier('rate'), + Argument::literal('INTEGER'), + Argument::value(9.99), + ], + $expressionData['values'], + ); } - public function testSetOptions(): void + public function testGetExpressionDataWithLiteralDefault(): void { $column = new Column(); + $column->setName('created_at'); + $column->setDefault(new Literal('CURRENT_TIMESTAMP')); - // First mutation - $result = $column->setOptions(['autoincrement' => true]); + $expressionData = $column->getExpressionData(); - // Verify fluent interface - self::assertSame($column, $result); + self::assertEquals('%s %s NOT NULL DEFAULT %s', $expressionData['spec']); + self::assertEquals( + [ + Argument::identifier('created_at'), + Argument::literal('INTEGER'), + Argument::literal('CURRENT_TIMESTAMP'), + ], + $expressionData['values'], + ); + } - // Verify the first mutation occurred - self::assertEquals(['autoincrement' => true], $column->getOptions()); + public function testGetExpressionDataWithValueDefault(): void + { + $column = new Column(); + $column->setName('score'); + $column->setDefault(new Value(42)); - // Second mutation to verify mutability - $column->setOptions(['primary' => true, 'unsigned' => true]); + $expressionData = $column->getExpressionData(); - // Verify the instance was actually mutated - self::assertEquals(['primary' => true, 'unsigned' => true], $column->getOptions()); + self::assertEquals('%s %s NOT NULL DEFAULT %s', $expressionData['spec']); + self::assertEquals( + [ + Argument::identifier('score'), + Argument::literal('INTEGER'), + Argument::value(42), + ], + $expressionData['values'], + ); } - public function testSetOption(): void + public function testSetDefault(): void { $column = new Column(); // First mutation - $result = $column->setOption('primary', true); + $result = $column->setDefault('foo bar'); // Verify fluent interface self::assertSame($column, $result); // Verify the first mutation occurred - self::assertEquals(['primary' => true], $column->getOptions()); + self::assertEquals('foo bar', $column->getDefault()); // Second mutation to verify mutability - $column->setOption('unsigned', true); + $column->setDefault('baz qux'); // Verify the instance was actually mutated - self::assertEquals(['primary' => true, 'unsigned' => true], $column->getOptions()); + self::assertEquals('baz qux', $column->getDefault()); } - public function testGetExpressionData(): void + public function testSetDefaultWithBool(): void { $column = new Column(); - $column->setName('foo'); - - $expressionData = $column->getExpressionData(); - - self::assertEquals('%s %s NOT NULL', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('INTEGER'), - ], $expressionData['values']); - - $column->setNullable(true); + $column->setName('is_active'); - $expressionData = $column->getExpressionData(); + $result = $column->setDefault(true); - self::assertEquals('%s %s NULL DEFAULT NULL', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('INTEGER'), - ], $expressionData['values']); + self::assertSame($column, $result); + self::assertTrue($column->getDefault()); + } - $column->setDefault('bar'); + public function testSetDefaultWithFloat(): void + { + $column = new Column(); + $column->setName('rate'); - $expressionData = $column->getExpressionData(); + $result = $column->setDefault(3.14); - self::assertEquals('%s %s NULL DEFAULT %s', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('INTEGER'), - Argument::value('bar'), - ], $expressionData['values']); + self::assertSame($column, $result); + self::assertSame(3.14, $column->getDefault()); } public function testSetDefaultWithLiteral(): void @@ -184,22 +240,6 @@ public function testSetDefaultWithLiteral(): void self::assertSame($literal, $column->getDefault()); } - public function testGetExpressionDataWithLiteralDefault(): void - { - $column = new Column(); - $column->setName('created_at'); - $column->setDefault(new Literal('CURRENT_TIMESTAMP')); - - $expressionData = $column->getExpressionData(); - - self::assertEquals('%s %s NOT NULL DEFAULT %s', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('created_at'), - Argument::literal('INTEGER'), - Argument::literal('CURRENT_TIMESTAMP'), - ], $expressionData['values']); - } - public function testSetDefaultWithValue(): void { $column = new Column(); @@ -212,102 +252,83 @@ public function testSetDefaultWithValue(): void self::assertSame($value, $column->getDefault()); } - public function testGetExpressionDataWithValueDefault(): void + public function testSetName(): void { $column = new Column(); - $column->setName('score'); - $column->setDefault(new Value(42)); - $expressionData = $column->getExpressionData(); + // First mutation + $result = $column->setName('foo'); - self::assertEquals('%s %s NOT NULL DEFAULT %s', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('score'), - Argument::literal('INTEGER'), - Argument::value(42), - ], $expressionData['values']); - } + // Verify fluent interface + self::assertSame($column, $result); - public function testSetDefaultWithFloat(): void - { - $column = new Column(); - $column->setName('rate'); + // Verify the first mutation occurred + self::assertEquals('foo', $column->getName()); - $result = $column->setDefault(3.14); + // Second mutation to verify mutability + $column->setName('bar'); - self::assertSame($column, $result); - self::assertSame(3.14, $column->getDefault()); + // Verify the instance was actually mutated + self::assertEquals('bar', $column->getName()); } - public function testGetExpressionDataWithFloatDefault(): void + public function testSetNullable(): void { $column = new Column(); - $column->setName('rate'); - $column->setDefault(9.99); - $expressionData = $column->getExpressionData(); + // First mutation + $result = $column->setNullable(true); - self::assertEquals('%s %s NOT NULL DEFAULT %s', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('rate'), - Argument::literal('INTEGER'), - Argument::value(9.99), - ], $expressionData['values']); - } + // Verify fluent interface + self::assertSame($column, $result); - public function testSetDefaultWithBool(): void - { - $column = new Column(); - $column->setName('is_active'); + // Verify the first mutation occurred + self::assertTrue($column->isNullable()); - $result = $column->setDefault(true); + // Second mutation to verify mutability + $column->setNullable(false); - self::assertSame($column, $result); - self::assertTrue($column->getDefault()); + // Verify the instance was actually mutated + self::assertFalse($column->isNullable()); } - public function testGetExpressionDataWithBoolDefault(): void + public function testSetOption(): void { $column = new Column(); - $column->setName('is_active'); - $column->setDefault(false); - $expressionData = $column->getExpressionData(); + // First mutation + $result = $column->setOption('primary', true); - self::assertEquals('%s %s NOT NULL DEFAULT %s', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('is_active'), - Argument::literal('INTEGER'), - Argument::value(false), - ], $expressionData['values']); - } + // Verify fluent interface + self::assertSame($column, $result); - public function testAddConstraintAppendsConstraintToColumn(): void - { - $column = new Column('id'); + // Verify the first mutation occurred + self::assertEquals(['primary' => true], $column->getOptions()); - $result = $column->addConstraint(new PrimaryKey()); + // Second mutation to verify mutability + $column->setOption('unsigned', true); - self::assertSame($column, $result); + // Verify the instance was actually mutated + self::assertEquals(['primary' => true, 'unsigned' => true], $column->getOptions()); } - public function testGetExpressionDataIncludesConstraints(): void + public function testSetOptions(): void { - $column = new Column('id'); - $column->addConstraint(new PrimaryKey()); + $column = new Column(); - $expressionData = $column->getExpressionData(); + // First mutation + $result = $column->setOptions(['autoincrement' => true]); - self::assertStringContainsString('PRIMARY KEY', $expressionData['spec']); - } + // Verify fluent interface + self::assertSame($column, $result); - public function testGetExpressionDataIncludesConstraintValues(): void - { - $column = new Column('id'); - $column->addConstraint(new PrimaryKey('id', 'pk_id')); + // Verify the first mutation occurred + self::assertEquals(['autoincrement' => true], $column->getOptions()); - $expressionData = $column->getExpressionData(); + // Second mutation to verify mutability + $column->setOptions(['primary' => true, 'unsigned' => true]); - self::assertNotEmpty($expressionData['values']); + // Verify the instance was actually mutated + self::assertEquals(['primary' => true, 'unsigned' => true], $column->getOptions()); } } diff --git a/test/unit/Sql/Ddl/Column/DateTest.php b/test/unit/Sql/Ddl/Column/DateTest.php index 894ad4579..9050935c8 100644 --- a/test/unit/Sql/Ddl/Column/DateTest.php +++ b/test/unit/Sql/Ddl/Column/DateTest.php @@ -19,9 +19,12 @@ public function testGetExpressionData(): void $expressionData = $column->getExpressionData(); self::assertEquals('%s %s NOT NULL', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('DATE'), - ], $expressionData['values']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('DATE'), + ], + $expressionData['values'], + ); } } diff --git a/test/unit/Sql/Ddl/Column/DatetimeTest.php b/test/unit/Sql/Ddl/Column/DatetimeTest.php index f68d073b8..89aa5300f 100644 --- a/test/unit/Sql/Ddl/Column/DatetimeTest.php +++ b/test/unit/Sql/Ddl/Column/DatetimeTest.php @@ -19,9 +19,12 @@ public function testGetExpressionData(): void $expressionData = $column->getExpressionData(); self::assertEquals('%s %s NOT NULL', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('DATETIME'), - ], $expressionData['values']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('DATETIME'), + ], + $expressionData['values'], + ); } } diff --git a/test/unit/Sql/Ddl/Column/DecimalTest.php b/test/unit/Sql/Ddl/Column/DecimalTest.php index 27ee78458..bf89a47bc 100644 --- a/test/unit/Sql/Ddl/Column/DecimalTest.php +++ b/test/unit/Sql/Ddl/Column/DecimalTest.php @@ -19,20 +19,6 @@ #[CoversMethod(AbstractPrecisionColumn::class, 'getLengthExpression')] final class DecimalTest extends TestCase { - public function testGetExpressionData(): void - { - $column = new Decimal('foo', 10, 5); - - $expressionData = $column->getExpressionData(); - - self::assertEquals('%s %s(%s) NOT NULL', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('DECIMAL'), - Argument::literal('10,5'), - ], $expressionData['values']); - } - public function testConstructorSetsDigitsAndDecimal(): void { $column = new Decimal('price', 10, 2); @@ -41,22 +27,21 @@ public function testConstructorSetsDigitsAndDecimal(): void self::assertEquals(2, $column->getDecimal()); } - public function testSetDigitsAndGetDigits(): void + public function testGetExpressionData(): void { - $column = new Decimal('amount'); - $result = $column->setDigits(15); - - self::assertSame($column, $result); // Fluent interface - self::assertEquals(15, $column->getDigits()); - } + $column = new Decimal('foo', 10, 5); - public function testSetDecimalAndGetDecimal(): void - { - $column = new Decimal('value'); - $result = $column->setDecimal(4); + $expressionData = $column->getExpressionData(); - self::assertSame($column, $result); // Fluent interface - self::assertEquals(4, $column->getDecimal()); + self::assertEquals('%s %s(%s) NOT NULL', $expressionData['spec']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('DECIMAL'), + Argument::literal('10,5'), + ], + $expressionData['values'], + ); } public function testGetExpressionDataWithNullDecimal(): void @@ -80,4 +65,22 @@ public function testInheritanceFromAbstractPrecisionColumn(): void $column = new Decimal('test'); self::assertInstanceOf(AbstractPrecisionColumn::class, $column); } + + public function testSetDecimalAndGetDecimal(): void + { + $column = new Decimal('value'); + $result = $column->setDecimal(4); + + self::assertSame($column, $result); // Fluent interface + self::assertEquals(4, $column->getDecimal()); + } + + public function testSetDigitsAndGetDigits(): void + { + $column = new Decimal('amount'); + $result = $column->setDigits(15); + + self::assertSame($column, $result); // Fluent interface + self::assertEquals(15, $column->getDigits()); + } } diff --git a/test/unit/Sql/Ddl/Column/DoubleTest.php b/test/unit/Sql/Ddl/Column/DoubleTest.php index 1616d16f6..7901a81a4 100644 --- a/test/unit/Sql/Ddl/Column/DoubleTest.php +++ b/test/unit/Sql/Ddl/Column/DoubleTest.php @@ -19,10 +19,13 @@ public function testGetExpressionData(): void $expressionData = $column->getExpressionData(); self::assertEquals('%s %s(%s) NOT NULL', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('DOUBLE'), - Argument::literal('10,5'), - ], $expressionData['values']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('DOUBLE'), + Argument::literal('10,5'), + ], + $expressionData['values'], + ); } } diff --git a/test/unit/Sql/Ddl/Column/FloatingTest.php b/test/unit/Sql/Ddl/Column/FloatingTest.php index 6956eafb6..14f439d24 100644 --- a/test/unit/Sql/Ddl/Column/FloatingTest.php +++ b/test/unit/Sql/Ddl/Column/FloatingTest.php @@ -19,10 +19,13 @@ public function testGetExpressionData(): void $expressionData = $column->getExpressionData(); self::assertEquals('%s %s(%s) NOT NULL', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('FLOAT'), - Argument::literal('10,5'), - ], $expressionData['values']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('FLOAT'), + Argument::literal('10,5'), + ], + $expressionData['values'], + ); } } diff --git a/test/unit/Sql/Ddl/Column/IntegerTest.php b/test/unit/Sql/Ddl/Column/IntegerTest.php index b8f69a777..0404d88b1 100644 --- a/test/unit/Sql/Ddl/Column/IntegerTest.php +++ b/test/unit/Sql/Ddl/Column/IntegerTest.php @@ -18,12 +18,6 @@ #[Group('unit')] final class IntegerTest extends TestCase { - public function testObjectConstruction(): void - { - $integer = new Integer('foo'); - self::assertEquals('foo', $integer->getName()); - } - public function testGetExpressionData(): void { $column = new Integer('foo'); @@ -31,10 +25,13 @@ public function testGetExpressionData(): void $expressionData = $column->getExpressionData(); self::assertEquals('%s %s NOT NULL', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('INTEGER'), - ], $expressionData['values']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('INTEGER'), + ], + $expressionData['values'], + ); $column = new Integer('foo'); $column->addConstraint(new PrimaryKey()); @@ -42,28 +39,37 @@ public function testGetExpressionData(): void $expressionData = $column->getExpressionData(); self::assertEquals('%s %s NOT NULL PRIMARY KEY', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('INTEGER'), - ], $expressionData['values']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('INTEGER'), + ], + $expressionData['values'], + ); } - public function testGetExpressionDataIncludesLengthWhenOptionSet(): void + public function testGetExpressionDataExcludesLengthWhenNotSet(): void { $column = new Integer('id'); - $column->setOption('length', '11'); $expressionData = $column->getExpressionData(); - self::assertStringContainsString('(11)', $expressionData['spec']); + self::assertStringNotContainsString('(', $expressionData['spec']); } - public function testGetExpressionDataExcludesLengthWhenNotSet(): void + public function testGetExpressionDataIncludesLengthWhenOptionSet(): void { $column = new Integer('id'); + $column->setOption('length', '11'); $expressionData = $column->getExpressionData(); - self::assertStringNotContainsString('(', $expressionData['spec']); + self::assertStringContainsString('(11)', $expressionData['spec']); + } + + public function testObjectConstruction(): void + { + $integer = new Integer('foo'); + self::assertEquals('foo', $integer->getName()); } } diff --git a/test/unit/Sql/Ddl/Column/JsonTest.php b/test/unit/Sql/Ddl/Column/JsonTest.php index afd4e1f63..0af7d7ee4 100644 --- a/test/unit/Sql/Ddl/Column/JsonTest.php +++ b/test/unit/Sql/Ddl/Column/JsonTest.php @@ -19,9 +19,12 @@ public function testGetExpressionData(): void $expressionData = $column->getExpressionData(); self::assertEquals('%s %s NOT NULL', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('JSON'), - ], $expressionData['values']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('JSON'), + ], + $expressionData['values'], + ); } } diff --git a/test/unit/Sql/Ddl/Column/SmallIntegerTest.php b/test/unit/Sql/Ddl/Column/SmallIntegerTest.php index 384aea534..62593edbc 100644 --- a/test/unit/Sql/Ddl/Column/SmallIntegerTest.php +++ b/test/unit/Sql/Ddl/Column/SmallIntegerTest.php @@ -14,12 +14,6 @@ #[CoversMethod(Column::class, 'getExpressionData')] final class SmallIntegerTest extends TestCase { - public function testObjectConstruction(): void - { - $integer = new SmallInteger('foo'); - self::assertEquals('foo', $integer->getName()); - } - public function testGetExpressionData(): void { $column = new SmallInteger('foo'); @@ -27,7 +21,7 @@ public function testGetExpressionData(): void self::assertEquals( '%s %s NOT NULL', - $expressionData['spec'] + $expressionData['spec'], ); self::assertEquals( @@ -35,7 +29,13 @@ public function testGetExpressionData(): void Argument::Identifier('foo'), Argument::Literal('SMALLINT'), ], - $expressionData['values'] + $expressionData['values'], ); } + + public function testObjectConstruction(): void + { + $integer = new SmallInteger('foo'); + self::assertEquals('foo', $integer->getName()); + } } diff --git a/test/unit/Sql/Ddl/Column/TextTest.php b/test/unit/Sql/Ddl/Column/TextTest.php index 1be7899ba..548a71002 100644 --- a/test/unit/Sql/Ddl/Column/TextTest.php +++ b/test/unit/Sql/Ddl/Column/TextTest.php @@ -19,9 +19,12 @@ public function testGetExpressionData(): void $expressionData = $column->getExpressionData(); self::assertEquals('%s %s NOT NULL', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('TEXT'), - ], $expressionData['values']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('TEXT'), + ], + $expressionData['values'], + ); } } diff --git a/test/unit/Sql/Ddl/Column/TimeTest.php b/test/unit/Sql/Ddl/Column/TimeTest.php index 1f764cfe7..3c0a95205 100644 --- a/test/unit/Sql/Ddl/Column/TimeTest.php +++ b/test/unit/Sql/Ddl/Column/TimeTest.php @@ -20,9 +20,12 @@ public function testGetExpressionData(): void $expressionData = $column->getExpressionData(); self::assertEquals('%s %s NOT NULL', $expressionData['spec']); - self::assertEquals([ - new Identifier('foo'), - new Literal('TIME'), - ], $expressionData['values']); + self::assertEquals( + [ + new Identifier('foo'), + new Literal('TIME'), + ], + $expressionData['values'], + ); } } diff --git a/test/unit/Sql/Ddl/Column/TimestampTest.php b/test/unit/Sql/Ddl/Column/TimestampTest.php index 7712588f6..94dcd506d 100644 --- a/test/unit/Sql/Ddl/Column/TimestampTest.php +++ b/test/unit/Sql/Ddl/Column/TimestampTest.php @@ -24,10 +24,51 @@ public function testGetExpressionData(): void $expressionData = $column->getExpressionData(); self::assertEquals('%s %s NOT NULL', $expressionData['spec']); - self::assertEquals([ - new Identifier('foo'), - new Literal('TIMESTAMP'), - ], $expressionData['values']); + self::assertEquals( + [ + new Identifier('foo'), + new Literal('TIMESTAMP'), + ], + $expressionData['values'], + ); + } + + public function testGetExpressionDataWithCurrentTimestampDefault(): void + { + $column = new Timestamp('created_at'); + $column->setDefault(new Literal('CURRENT_TIMESTAMP')); + + $expressionData = $column->getExpressionData(); + + self::assertEquals('%s %s NOT NULL DEFAULT %s', $expressionData['spec']); + self::assertEquals( + [ + new Identifier('created_at'), + new Literal('TIMESTAMP'), + new Literal('CURRENT_TIMESTAMP'), + ], + $expressionData['values'], + ); + } + + public function testGetExpressionDataWithCurrentTimestampDefaultAndOnUpdate(): void + { + $column = new Timestamp('updated_at'); + $column->setDefault(new Literal('CURRENT_TIMESTAMP')); + $column->setOption('on_update', true); + + $expressionData = $column->getExpressionData(); + + self::assertEquals('%s %s NOT NULL DEFAULT %s %s', $expressionData['spec']); + self::assertEquals( + [ + new Identifier('updated_at'), + new Literal('TIMESTAMP'), + new Literal('CURRENT_TIMESTAMP'), + new Literal('ON UPDATE CURRENT_TIMESTAMP'), + ], + $expressionData['values'], + ); } public function testGetExpressionDataWithOnUpdateOption(): void @@ -67,38 +108,6 @@ public function testGetExpressionDataWithoutOnUpdateOption(): void self::assertEquals(Argument::literal('TIMESTAMP'), $values[1]); } - public function testGetExpressionDataWithCurrentTimestampDefault(): void - { - $column = new Timestamp('created_at'); - $column->setDefault(new Literal('CURRENT_TIMESTAMP')); - - $expressionData = $column->getExpressionData(); - - self::assertEquals('%s %s NOT NULL DEFAULT %s', $expressionData['spec']); - self::assertEquals([ - new Identifier('created_at'), - new Literal('TIMESTAMP'), - new Literal('CURRENT_TIMESTAMP'), - ], $expressionData['values']); - } - - public function testGetExpressionDataWithCurrentTimestampDefaultAndOnUpdate(): void - { - $column = new Timestamp('updated_at'); - $column->setDefault(new Literal('CURRENT_TIMESTAMP')); - $column->setOption('on_update', true); - - $expressionData = $column->getExpressionData(); - - self::assertEquals('%s %s NOT NULL DEFAULT %s %s', $expressionData['spec']); - self::assertEquals([ - new Identifier('updated_at'), - new Literal('TIMESTAMP'), - new Literal('CURRENT_TIMESTAMP'), - new Literal('ON UPDATE CURRENT_TIMESTAMP'), - ], $expressionData['values']); - } - public function testInheritanceFromAbstractTimestampColumn(): void { $column = new Timestamp('test'); diff --git a/test/unit/Sql/Ddl/Column/VarbinaryTest.php b/test/unit/Sql/Ddl/Column/VarbinaryTest.php index a65d154ba..33a283364 100644 --- a/test/unit/Sql/Ddl/Column/VarbinaryTest.php +++ b/test/unit/Sql/Ddl/Column/VarbinaryTest.php @@ -19,10 +19,13 @@ public function testGetExpressionData(): void $expressionData = $column->getExpressionData(); self::assertEquals('%s %s(%s) NOT NULL', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('VARBINARY'), - Argument::literal('20'), - ], $expressionData['values']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('VARBINARY'), + Argument::literal('20'), + ], + $expressionData['values'], + ); } } diff --git a/test/unit/Sql/Ddl/Column/VarcharTest.php b/test/unit/Sql/Ddl/Column/VarcharTest.php index 004b870fa..869a0d3a8 100644 --- a/test/unit/Sql/Ddl/Column/VarcharTest.php +++ b/test/unit/Sql/Ddl/Column/VarcharTest.php @@ -25,32 +25,29 @@ public function testGetExpressionData(): void $expressionData = $column->getExpressionData(); self::assertEquals('%s %s(%s) NOT NULL', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('VARCHAR'), - Argument::literal('20'), - ], $expressionData['values']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('VARCHAR'), + Argument::literal('20'), + ], + $expressionData['values'], + ); $column->setDefault('bar'); $expressionData = $column->getExpressionData(); self::assertEquals('%s %s(%s) NOT NULL DEFAULT %s', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('VARCHAR'), - Argument::literal('20'), - Argument::value('bar'), - ], $expressionData['values']); - } - - public function testSetLengthAndGetLength(): void - { - $column = new Varchar('name'); - - $result = $column->setLength(100); - self::assertSame($column, $result); // Fluent interface - self::assertEquals(100, $column->getLength()); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('VARCHAR'), + Argument::literal('20'), + Argument::value('bar'), + ], + $expressionData['values'], + ); } public function testGetExpressionDataWithNullLength(): void @@ -69,10 +66,13 @@ public function testGetExpressionDataWithNullLength(): void // The specification format is defined in AbstractLengthColumn as '%s %s(%s)' // But when length value is not added, we need to check if placeholder remains self::assertEquals('%s %s(%s) NOT NULL', $spec); - self::assertEquals([ - Argument::identifier('name'), - Argument::literal('VARCHAR'), - ], $values); + self::assertEquals( + [ + Argument::identifier('name'), + Argument::literal('VARCHAR'), + ], + $values, + ); } public function testInheritanceFromAbstractLengthColumn(): void @@ -80,4 +80,13 @@ public function testInheritanceFromAbstractLengthColumn(): void $column = new Varchar('test'); self::assertInstanceOf(AbstractLengthColumn::class, $column); } + + public function testSetLengthAndGetLength(): void + { + $column = new Varchar('name'); + + $result = $column->setLength(100); + self::assertSame($column, $result); // Fluent interface + self::assertEquals(100, $column->getLength()); + } } diff --git a/test/unit/Sql/Ddl/Constraint/AbstractConstraintTest.php b/test/unit/Sql/Ddl/Constraint/AbstractConstraintTest.php index 4dd670d11..464148cf6 100644 --- a/test/unit/Sql/Ddl/Constraint/AbstractConstraintTest.php +++ b/test/unit/Sql/Ddl/Constraint/AbstractConstraintTest.php @@ -18,30 +18,30 @@ final class AbstractConstraintTest extends TestCase { protected MockObject $ac; - /** - * @throws Exception - */ - #[Override] - protected function setUp(): void + public function testAddColumn(): void { - $this->ac = $this->getMockBuilder(AbstractConstraint::class)->onlyMethods([])->getMock(); + self::assertSame($this->ac, $this->ac->addColumn('foo')); + self::assertEquals(['foo'], $this->ac->getColumns()); } - public function testSetColumns(): void + public function testGetColumns(): void { - self::assertSame($this->ac, $this->ac->setColumns(['foo', 'bar'])); + $this->ac->setColumns(['foo', 'bar']); self::assertEquals(['foo', 'bar'], $this->ac->getColumns()); } - public function testAddColumn(): void + public function testSetColumns(): void { - self::assertSame($this->ac, $this->ac->addColumn('foo')); - self::assertEquals(['foo'], $this->ac->getColumns()); + self::assertSame($this->ac, $this->ac->setColumns(['foo', 'bar'])); + self::assertEquals(['foo', 'bar'], $this->ac->getColumns()); } - public function testGetColumns(): void + /** + * @throws Exception + */ + #[Override] + protected function setUp(): void { - $this->ac->setColumns(['foo', 'bar']); - self::assertEquals(['foo', 'bar'], $this->ac->getColumns()); + $this->ac = $this->getMockBuilder(AbstractConstraint::class)->onlyMethods([])->getMock(); } } diff --git a/test/unit/Sql/Ddl/Constraint/CheckTest.php b/test/unit/Sql/Ddl/Constraint/CheckTest.php index 48adbebba..921b8da06 100644 --- a/test/unit/Sql/Ddl/Constraint/CheckTest.php +++ b/test/unit/Sql/Ddl/Constraint/CheckTest.php @@ -20,9 +20,12 @@ public function testGetExpressionData(): void $expressionData = $check->getExpressionData(); self::assertEquals('CONSTRAINT %s CHECK (%s)', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - Argument::literal('id>0'), - ], $expressionData['values']); + self::assertEquals( + [ + Argument::identifier('foo'), + Argument::literal('id>0'), + ], + $expressionData['values'], + ); } } diff --git a/test/unit/Sql/Ddl/Constraint/ForeignKeyTest.php b/test/unit/Sql/Ddl/Constraint/ForeignKeyTest.php index 60b52066e..57260c188 100644 --- a/test/unit/Sql/Ddl/Constraint/ForeignKeyTest.php +++ b/test/unit/Sql/Ddl/Constraint/ForeignKeyTest.php @@ -32,6 +32,53 @@ #[CoversMethod(ForeignKey::class, 'getExpressionData')] final class ForeignKeyTest extends TestCase { + public function testGetExpressionData(): void + { + $fk = new ForeignKey('foo', 'bar', 'baz', 'bam', 'CASCADE', 'SET NULL'); + + $expressionData = $fk->getExpressionData(); + + // Verify specification + self::assertEquals( + 'CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s) ON DELETE %s ON UPDATE %s', + $expressionData['spec'], + ); + + // Verify expression values + $values = $expressionData['values']; + self::assertCount(6, $values); + + // Verify constraint name + self::assertInstanceOf(ArgumentInterface::class, $values[0]); + self::assertEquals('foo', $values[0]->getValue()); + self::assertEquals(ArgumentType::Identifier, $values[0]->getType()); + + // Verify column name + self::assertInstanceOf(ArgumentInterface::class, $values[1]); + self::assertEquals('bar', $values[1]->getValue()); + self::assertEquals(ArgumentType::Identifier, $values[1]->getType()); + + // Verify reference table + self::assertInstanceOf(ArgumentInterface::class, $values[2]); + self::assertEquals('baz', $values[2]->getValue()); + self::assertEquals(ArgumentType::Identifier, $values[2]->getType()); + + // Verify reference column + self::assertInstanceOf(ArgumentInterface::class, $values[3]); + self::assertEquals('bam', $values[3]->getValue()); + self::assertEquals(ArgumentType::Identifier, $values[3]->getType()); + + // Verify on delete rule + self::assertInstanceOf(ArgumentInterface::class, $values[4]); + self::assertEquals('CASCADE', $values[4]->getValue()); + self::assertEquals(ArgumentType::Literal, $values[4]->getType()); + + // Verify on update rule + self::assertInstanceOf(ArgumentInterface::class, $values[5]); + self::assertEquals('SET NULL', $values[5]->getValue()); + self::assertEquals(ArgumentType::Literal, $values[5]->getType()); + } + public function testSetName(): void { $fk = new ForeignKey('foo', 'bar', 'baz', 'bam'); @@ -52,130 +99,83 @@ public function testSetName(): void self::assertEquals('yyyy', $fk->getName()); } - public function testSetReferenceTable(): void + public function testSetOnDeleteRule(): void { $fk = new ForeignKey('foo', 'bar', 'baz', 'bam'); // First mutation - $result = $fk->setReferenceTable('xxxx'); + $result = $fk->setOnDeleteRule('CASCADE'); // Verify fluent interface self::assertSame($fk, $result); // Verify the first mutation occurred - self::assertEquals('xxxx', $fk->getReferenceTable()); + self::assertEquals('CASCADE', $fk->getOnDeleteRule()); // Second mutation to verify mutability - $fk->setReferenceTable('yyyy'); + $fk->setOnDeleteRule('SET NULL'); // Verify the instance was actually mutated - self::assertEquals('yyyy', $fk->getReferenceTable()); + self::assertEquals('SET NULL', $fk->getOnDeleteRule()); } - public function testSetReferenceColumn(): void + public function testSetOnUpdateRule(): void { $fk = new ForeignKey('foo', 'bar', 'baz', 'bam'); // First mutation - $result = $fk->setReferenceColumn('xxxx'); + $result = $fk->setOnUpdateRule('CASCADE'); // Verify fluent interface self::assertSame($fk, $result); // Verify the first mutation occurred - self::assertEquals(['xxxx'], $fk->getReferenceColumn()); + self::assertEquals('CASCADE', $fk->getOnUpdateRule()); // Second mutation to verify mutability - $fk->setReferenceColumn('yyyy'); + $fk->setOnUpdateRule('RESTRICT'); // Verify the instance was actually mutated - self::assertEquals(['yyyy'], $fk->getReferenceColumn()); + self::assertEquals('RESTRICT', $fk->getOnUpdateRule()); } - public function testSetOnDeleteRule(): void + public function testSetReferenceColumn(): void { $fk = new ForeignKey('foo', 'bar', 'baz', 'bam'); // First mutation - $result = $fk->setOnDeleteRule('CASCADE'); + $result = $fk->setReferenceColumn('xxxx'); // Verify fluent interface self::assertSame($fk, $result); // Verify the first mutation occurred - self::assertEquals('CASCADE', $fk->getOnDeleteRule()); + self::assertEquals(['xxxx'], $fk->getReferenceColumn()); // Second mutation to verify mutability - $fk->setOnDeleteRule('SET NULL'); + $fk->setReferenceColumn('yyyy'); // Verify the instance was actually mutated - self::assertEquals('SET NULL', $fk->getOnDeleteRule()); + self::assertEquals(['yyyy'], $fk->getReferenceColumn()); } - public function testSetOnUpdateRule(): void + public function testSetReferenceTable(): void { $fk = new ForeignKey('foo', 'bar', 'baz', 'bam'); // First mutation - $result = $fk->setOnUpdateRule('CASCADE'); + $result = $fk->setReferenceTable('xxxx'); // Verify fluent interface self::assertSame($fk, $result); // Verify the first mutation occurred - self::assertEquals('CASCADE', $fk->getOnUpdateRule()); + self::assertEquals('xxxx', $fk->getReferenceTable()); // Second mutation to verify mutability - $fk->setOnUpdateRule('RESTRICT'); + $fk->setReferenceTable('yyyy'); // Verify the instance was actually mutated - self::assertEquals('RESTRICT', $fk->getOnUpdateRule()); - } - - public function testGetExpressionData(): void - { - $fk = new ForeignKey('foo', 'bar', 'baz', 'bam', 'CASCADE', 'SET NULL'); - - $expressionData = $fk->getExpressionData(); - - // Verify specification - self::assertEquals( - 'CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s) ON DELETE %s ON UPDATE %s', - $expressionData['spec'] - ); - - // Verify expression values - $values = $expressionData['values']; - self::assertCount(6, $values); - - // Verify constraint name - self::assertInstanceOf(ArgumentInterface::class, $values[0]); - self::assertEquals('foo', $values[0]->getValue()); - self::assertEquals(ArgumentType::Identifier, $values[0]->getType()); - - // Verify column name - self::assertInstanceOf(ArgumentInterface::class, $values[1]); - self::assertEquals('bar', $values[1]->getValue()); - self::assertEquals(ArgumentType::Identifier, $values[1]->getType()); - - // Verify reference table - self::assertInstanceOf(ArgumentInterface::class, $values[2]); - self::assertEquals('baz', $values[2]->getValue()); - self::assertEquals(ArgumentType::Identifier, $values[2]->getType()); - - // Verify reference column - self::assertInstanceOf(ArgumentInterface::class, $values[3]); - self::assertEquals('bam', $values[3]->getValue()); - self::assertEquals(ArgumentType::Identifier, $values[3]->getType()); - - // Verify on delete rule - self::assertInstanceOf(ArgumentInterface::class, $values[4]); - self::assertEquals('CASCADE', $values[4]->getValue()); - self::assertEquals(ArgumentType::Literal, $values[4]->getType()); - - // Verify on update rule - self::assertInstanceOf(ArgumentInterface::class, $values[5]); - self::assertEquals('SET NULL', $values[5]->getValue()); - self::assertEquals(ArgumentType::Literal, $values[5]->getType()); + self::assertEquals('yyyy', $fk->getReferenceTable()); } } diff --git a/test/unit/Sql/Ddl/Constraint/PrimaryKeyTest.php b/test/unit/Sql/Ddl/Constraint/PrimaryKeyTest.php index c4c98a6aa..51669b9a2 100644 --- a/test/unit/Sql/Ddl/Constraint/PrimaryKeyTest.php +++ b/test/unit/Sql/Ddl/Constraint/PrimaryKeyTest.php @@ -19,8 +19,11 @@ public function testGetExpressionData(): void $expressionData = $pk->getExpressionData(); self::assertEquals('PRIMARY KEY (%s)', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('foo'), - ], $expressionData['values']); + self::assertEquals( + [ + Argument::identifier('foo'), + ], + $expressionData['values'], + ); } } diff --git a/test/unit/Sql/Ddl/Constraint/UniqueKeyTest.php b/test/unit/Sql/Ddl/Constraint/UniqueKeyTest.php index 5669b281c..59e30fbec 100644 --- a/test/unit/Sql/Ddl/Constraint/UniqueKeyTest.php +++ b/test/unit/Sql/Ddl/Constraint/UniqueKeyTest.php @@ -19,9 +19,12 @@ public function testGetExpressionData(): void $expressionData = $uk->getExpressionData(); self::assertEquals('CONSTRAINT %s UNIQUE (%s)', $expressionData['spec']); - self::assertEquals([ - Argument::identifier('my_uk'), - Argument::identifier('foo'), - ], $expressionData['values']); + self::assertEquals( + [ + Argument::identifier('my_uk'), + Argument::identifier('foo'), + ], + $expressionData['values'], + ); } } diff --git a/test/unit/Sql/Ddl/CreateTableTest.php b/test/unit/Sql/Ddl/CreateTableTest.php index 34c5a75ae..cd3bdb0e2 100644 --- a/test/unit/Sql/Ddl/CreateTableTest.php +++ b/test/unit/Sql/Ddl/CreateTableTest.php @@ -35,60 +35,6 @@ #[CoversMethod(CreateTable::class, 'processTableOptions')] class CreateTableTest extends TestCase { - /** - * test object construction - */ - public function testObjectConstruction(): void - { - $ct = new CreateTable('foo', true); - self::assertEquals('foo', $ct->getRawState(CreateTable::TABLE)); - self::assertTrue($ct->isTemporary()); - } - - public function testSetTemporary(): void - { - $ct = new CreateTable(); - self::assertSame($ct, $ct->setTemporary(false)); - self::assertFalse($ct->isTemporary()); - $ct->setTemporary(true); - self::assertTrue($ct->isTemporary()); - $ct->setTemporary('yes'); - self::assertTrue($ct->isTemporary()); - - self::assertStringStartsWith('CREATE TEMPORARY TABLE', $ct->getSqlString()); - } - - public function testIsTemporary(): void - { - $ct = new CreateTable(); - self::assertFalse($ct->isTemporary()); - $ct->setTemporary(true); - self::assertTrue($ct->isTemporary()); - } - - public function testSetTable(): void - { - $ct = new CreateTable(); - - // Verify initial state - self::assertEquals('', $ct->getRawState('table')); - - // First mutation - $result = $ct->setTable('test'); - - // Verify fluent interface - self::assertSame($ct, $result); - - // Verify the first mutation occurred - self::assertEquals('test', $ct->getRawState('table')); - - // Second mutation to verify mutability - $ct->setTable('another_table'); - - // Verify the instance was actually mutated - self::assertEquals('another_table', $ct->getRawState('table')); - } - public function testAddColumn(): void { $column = $this->getMockBuilder(ColumnInterface::class)->getMock(); @@ -143,53 +89,24 @@ public function testAddConstraint(): void self::assertInstanceOf(ConstraintInterface::class, $state2[1]); } - public function testGetSqlString(): void + public function testChainedOperations(): void { - $ct = new CreateTable('foo'); - self::assertEquals("CREATE TABLE \"foo\" ( \n)", $ct->getSqlString()); - - $ct = new CreateTable('foo', true); - self::assertEquals("CREATE TEMPORARY TABLE \"foo\" ( \n)", $ct->getSqlString()); - - $ct = new CreateTable('foo'); - $ct->addColumn(new Column('bar')); - self::assertEquals("CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n)", $ct->getSqlString()); - - $ct = new CreateTable('foo', true); - $ct->addColumn(new Column('bar')); - self::assertEquals("CREATE TEMPORARY TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n)", $ct->getSqlString()); - - $ct = new CreateTable('foo', true); - $ct->addColumn(new Column('bar')); - $ct->addColumn(new Column('baz')); - self::assertEquals( - "CREATE TEMPORARY TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL,\n \"baz\" INTEGER NOT NULL \n)", - $ct->getSqlString() - ); - - $ct = new CreateTable('foo'); - $ct->addColumn(new Column('bar')); - $ct->addConstraint(new Constraint\PrimaryKey('bat')); - self::assertEquals( - "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL , \n PRIMARY KEY (\"bat\") \n)", - $ct->getSqlString() - ); - - $ct = new CreateTable('foo'); - $ct->addConstraint(new Constraint\PrimaryKey('bar')); - $ct->addConstraint(new Constraint\PrimaryKey('bat')); - self::assertEquals( - "CREATE TABLE \"foo\" ( \n PRIMARY KEY (\"bar\"),\n PRIMARY KEY (\"bat\") \n)", - $ct->getSqlString() - ); + $ct = new CreateTable(); + $col1 = $this->getMockBuilder(ColumnInterface::class)->getMock(); + $col2 = $this->getMockBuilder(ColumnInterface::class)->getMock(); + $con = $this->getMockBuilder(ConstraintInterface::class)->getMock(); - $ct = new CreateTable(new TableIdentifier('foo')); - $ct->addColumn(new Column('bar')); - self::assertEquals("CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n)", $ct->getSqlString()); + $result = $ct->setTable('products') + ->setTemporary(true) + ->addColumn($col1) + ->addColumn($col2) + ->addConstraint($con); - $ct = new CreateTable(new TableIdentifier('bar', 'foo')); - $ct->addColumn(new Column('baz')); - self::assertEquals("CREATE TABLE \"foo\".\"bar\" ( \n \"baz\" INTEGER NOT NULL \n)", $ct->getSqlString()); + self::assertSame($ct, $result); + self::assertEquals('products', $ct->getRawState(CreateTable::TABLE)); + self::assertTrue($ct->isTemporary()); + self::assertCount(2, $ct->getRawState(CreateTable::COLUMNS)); + self::assertCount(1, $ct->getRawState(CreateTable::CONSTRAINTS)); } public function testConstructorWithTableIdentifier(): void @@ -211,6 +128,32 @@ public function testConstructorWithTemporaryFlag(): void self::assertFalse($ct2->isTemporary()); } + public function testEmptyTableConstruction(): void + { + $ct = new CreateTable(); + self::assertEquals('', $ct->getRawState(CreateTable::TABLE)); + self::assertFalse($ct->isTemporary()); + self::assertEmpty($ct->getRawState(CreateTable::COLUMNS)); + self::assertEmpty($ct->getRawState(CreateTable::CONSTRAINTS)); + } + + public function testGetOptionsReturnsEmpty(): void + { + $ct = new CreateTable('foo'); + self::assertEquals([], $ct->getOptions()); + } + + public function testGetRawStateIncludesTableOptions(): void + { + $ct = new CreateTable('foo'); + $ct->setOption('engine', new Literal('InnoDB')); + + $rawState = $ct->getRawState(); + + self::assertArrayHasKey(CreateTable::TABLE_OPTIONS, $rawState); + self::assertEquals(['engine' => new Literal('InnoDB')], $rawState[CreateTable::TABLE_OPTIONS]); + } + public function testGetRawStateReturnsAllState(): void { $ct = new CreateTable('users'); @@ -243,136 +186,64 @@ public function testGetRawStateWithInvalidKey(): void self::assertArrayHasKey(CreateTable::TABLE, $rawState); } - public function testChainedOperations(): void - { - $ct = new CreateTable(); - $col1 = $this->getMockBuilder(ColumnInterface::class)->getMock(); - $col2 = $this->getMockBuilder(ColumnInterface::class)->getMock(); - $con = $this->getMockBuilder(ConstraintInterface::class)->getMock(); - - $result = $ct->setTable('products') - ->setTemporary(true) - ->addColumn($col1) - ->addColumn($col2) - ->addConstraint($con); - - self::assertSame($ct, $result); - self::assertEquals('products', $ct->getRawState(CreateTable::TABLE)); - self::assertTrue($ct->isTemporary()); - self::assertCount(2, $ct->getRawState(CreateTable::COLUMNS)); - self::assertCount(1, $ct->getRawState(CreateTable::CONSTRAINTS)); - } - - public function testMultipleColumns(): void - { - $ct = new CreateTable('users'); - $ct->addColumn(new Column('id')); - $ct->addColumn(new Column('name')); - $ct->addColumn(new Column('email')); - - $columns = $ct->getRawState(CreateTable::COLUMNS); - self::assertCount(3, $columns); - - $sql = $ct->getSqlString(); - self::assertStringContainsString('"id"', $sql); - self::assertStringContainsString('"name"', $sql); - self::assertStringContainsString('"email"', $sql); - } - - public function testMultipleConstraints(): void - { - $ct = new CreateTable('orders'); - $ct->addConstraint(new Constraint\PrimaryKey('id')); - $ct->addConstraint(new Constraint\UniqueKey('order_number')); - - $constraints = $ct->getRawState(CreateTable::CONSTRAINTS); - self::assertCount(2, $constraints); - - $sql = $ct->getSqlString(); - self::assertStringContainsString('PRIMARY KEY', $sql); - self::assertStringContainsString('UNIQUE', $sql); - } - - public function testEmptyTableConstruction(): void - { - $ct = new CreateTable(); - self::assertEquals('', $ct->getRawState(CreateTable::TABLE)); - self::assertFalse($ct->isTemporary()); - self::assertEmpty($ct->getRawState(CreateTable::COLUMNS)); - self::assertEmpty($ct->getRawState(CreateTable::CONSTRAINTS)); - } - - public function testSetTableAfterConstruction(): void - { - $ct = new CreateTable(); - self::assertEquals('', $ct->getRawState(CreateTable::TABLE)); - - $ct->setTable('new_table'); - self::assertEquals('new_table', $ct->getRawState(CreateTable::TABLE)); - - // Test that setTable is chainable - $result = $ct->setTable('another_table'); - self::assertSame($ct, $result); - self::assertEquals('another_table', $ct->getRawState(CreateTable::TABLE)); - } - - public function testSetOptionFluentInterface(): void - { - $ct = new CreateTable('foo'); - $result = $ct->setOption('engine', new Literal('InnoDB')); - - self::assertSame($ct, $result); - self::assertEquals(['engine' => new Literal('InnoDB')], $ct->getOptions()); - } - - public function testSetOptionsReplacesAll(): void + public function testGetSqlString(): void { $ct = new CreateTable('foo'); - $ct->setOption('engine', new Literal('InnoDB')); - - $ct->setOptions(['charset' => new Literal('utf8mb4')]); - - self::assertEquals(['charset' => new Literal('utf8mb4')], $ct->getOptions()); - } + self::assertEquals("CREATE TABLE \"foo\" ( \n)", $ct->getSqlString()); - public function testGetOptionsReturnsEmpty(): void - { - $ct = new CreateTable('foo'); - self::assertEquals([], $ct->getOptions()); - } + $ct = new CreateTable('foo', true); + self::assertEquals("CREATE TEMPORARY TABLE \"foo\" ( \n)", $ct->getSqlString()); - public function testGetRawStateIncludesTableOptions(): void - { $ct = new CreateTable('foo'); - $ct->setOption('engine', new Literal('InnoDB')); + $ct->addColumn(new Column('bar')); + self::assertEquals("CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n)", $ct->getSqlString()); - $rawState = $ct->getRawState(); + $ct = new CreateTable('foo', true); + $ct->addColumn(new Column('bar')); + self::assertEquals("CREATE TEMPORARY TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n)", $ct->getSqlString()); - self::assertArrayHasKey(CreateTable::TABLE_OPTIONS, $rawState); - self::assertEquals(['engine' => new Literal('InnoDB')], $rawState[CreateTable::TABLE_OPTIONS]); - } + $ct = new CreateTable('foo', true); + $ct->addColumn(new Column('bar')); + $ct->addColumn(new Column('baz')); + self::assertEquals( + "CREATE TEMPORARY TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL,\n \"baz\" INTEGER NOT NULL \n)", + $ct->getSqlString(), + ); - public function testGetSqlStringWithLiteralOption(): void - { $ct = new CreateTable('foo'); $ct->addColumn(new Column('bar')); - $ct->setOption('engine', new Literal('InnoDB')); + $ct->addConstraint(new Constraint\PrimaryKey('bat')); + self::assertEquals( + "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL , \n PRIMARY KEY (\"bat\") \n)", + $ct->getSqlString(), + ); + $ct = new CreateTable('foo'); + $ct->addConstraint(new Constraint\PrimaryKey('bar')); + $ct->addConstraint(new Constraint\PrimaryKey('bat')); self::assertEquals( - "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n) ENGINE = InnoDB", - $ct->getSqlString() + "CREATE TABLE \"foo\" ( \n PRIMARY KEY (\"bar\"),\n PRIMARY KEY (\"bat\") \n)", + $ct->getSqlString(), ); + + $ct = new CreateTable(new TableIdentifier('foo')); + $ct->addColumn(new Column('bar')); + self::assertEquals("CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n)", $ct->getSqlString()); + + $ct = new CreateTable(new TableIdentifier('bar', 'foo')); + $ct->addColumn(new Column('baz')); + self::assertEquals("CREATE TABLE \"foo\".\"bar\" ( \n \"baz\" INTEGER NOT NULL \n)", $ct->getSqlString()); } - public function testGetSqlStringWithStringOption(): void + public function testGetSqlStringWithBoolOption(): void { $ct = new CreateTable('foo'); $ct->addColumn(new Column('bar')); - $ct->setOption('comment', 'My table'); + $ct->setOption('pack_keys', true); self::assertEquals( - "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n) COMMENT = 'My table'", - $ct->getSqlString() + "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n) PACK_KEYS = 1", + $ct->getSqlString(), ); } @@ -384,19 +255,19 @@ public function testGetSqlStringWithIntOption(): void self::assertEquals( "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n) AUTO_INCREMENT = 100", - $ct->getSqlString() + $ct->getSqlString(), ); } - public function testGetSqlStringWithBoolOption(): void + public function testGetSqlStringWithLiteralOption(): void { $ct = new CreateTable('foo'); $ct->addColumn(new Column('bar')); - $ct->setOption('pack_keys', true); + $ct->setOption('engine', new Literal('InnoDB')); self::assertEquals( - "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n) PACK_KEYS = 1", - $ct->getSqlString() + "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n) ENGINE = InnoDB", + $ct->getSqlString(), ); } @@ -409,7 +280,7 @@ public function testGetSqlStringWithMultipleOptions(): void self::assertEquals( "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n) ENGINE = InnoDB AUTO_INCREMENT = 100", - $ct->getSqlString() + $ct->getSqlString(), ); } @@ -420,7 +291,19 @@ public function testGetSqlStringWithNoOptionsUnchanged(): void self::assertEquals( "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n)", - $ct->getSqlString() + $ct->getSqlString(), + ); + } + + public function testGetSqlStringWithStringOption(): void + { + $ct = new CreateTable('foo'); + $ct->addColumn(new Column('bar')); + $ct->setOption('comment', 'My table'); + + self::assertEquals( + "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n) COMMENT = 'My table'", + $ct->getSqlString(), ); } @@ -436,6 +319,14 @@ public function testIfNotExists(): void self::assertEquals("CREATE TABLE IF NOT EXISTS \"foo\" ( \n)", $ct->getSqlString()); } + public function testIfNotExistsCombinedWithTemporary(): void + { + $ct = new CreateTable('foo', true); + $ct->ifNotExists(); + + self::assertEquals("CREATE TEMPORARY TABLE IF NOT EXISTS \"foo\" ( \n)", $ct->getSqlString()); + } + public function testIfNotExistsDisable(): void { $ct = new CreateTable('foo'); @@ -448,11 +339,120 @@ public function testIfNotExistsDisable(): void self::assertEquals("CREATE TABLE \"foo\" ( \n)", $ct->getSqlString()); } - public function testIfNotExistsCombinedWithTemporary(): void + public function testIsTemporary(): void + { + $ct = new CreateTable(); + self::assertFalse($ct->isTemporary()); + $ct->setTemporary(true); + self::assertTrue($ct->isTemporary()); + } + + public function testMultipleColumns(): void + { + $ct = new CreateTable('users'); + $ct->addColumn(new Column('id')); + $ct->addColumn(new Column('name')); + $ct->addColumn(new Column('email')); + + $columns = $ct->getRawState(CreateTable::COLUMNS); + self::assertCount(3, $columns); + + $sql = $ct->getSqlString(); + self::assertStringContainsString('"id"', $sql); + self::assertStringContainsString('"name"', $sql); + self::assertStringContainsString('"email"', $sql); + } + + public function testMultipleConstraints(): void + { + $ct = new CreateTable('orders'); + $ct->addConstraint(new Constraint\PrimaryKey('id')); + $ct->addConstraint(new Constraint\UniqueKey('order_number')); + + $constraints = $ct->getRawState(CreateTable::CONSTRAINTS); + self::assertCount(2, $constraints); + + $sql = $ct->getSqlString(); + self::assertStringContainsString('PRIMARY KEY', $sql); + self::assertStringContainsString('UNIQUE', $sql); + } + + /** + * test object construction + */ + public function testObjectConstruction(): void { $ct = new CreateTable('foo', true); - $ct->ifNotExists(); + self::assertEquals('foo', $ct->getRawState(CreateTable::TABLE)); + self::assertTrue($ct->isTemporary()); + } - self::assertEquals("CREATE TEMPORARY TABLE IF NOT EXISTS \"foo\" ( \n)", $ct->getSqlString()); + public function testSetOptionFluentInterface(): void + { + $ct = new CreateTable('foo'); + $result = $ct->setOption('engine', new Literal('InnoDB')); + + self::assertSame($ct, $result); + self::assertEquals(['engine' => new Literal('InnoDB')], $ct->getOptions()); + } + + public function testSetOptionsReplacesAll(): void + { + $ct = new CreateTable('foo'); + $ct->setOption('engine', new Literal('InnoDB')); + + $ct->setOptions(['charset' => new Literal('utf8mb4')]); + + self::assertEquals(['charset' => new Literal('utf8mb4')], $ct->getOptions()); + } + + public function testSetTable(): void + { + $ct = new CreateTable(); + + // Verify initial state + self::assertEquals('', $ct->getRawState('table')); + + // First mutation + $result = $ct->setTable('test'); + + // Verify fluent interface + self::assertSame($ct, $result); + + // Verify the first mutation occurred + self::assertEquals('test', $ct->getRawState('table')); + + // Second mutation to verify mutability + $ct->setTable('another_table'); + + // Verify the instance was actually mutated + self::assertEquals('another_table', $ct->getRawState('table')); + } + + public function testSetTableAfterConstruction(): void + { + $ct = new CreateTable(); + self::assertEquals('', $ct->getRawState(CreateTable::TABLE)); + + $ct->setTable('new_table'); + self::assertEquals('new_table', $ct->getRawState(CreateTable::TABLE)); + + // Test that setTable is chainable + $result = $ct->setTable('another_table'); + self::assertSame($ct, $result); + self::assertEquals('another_table', $ct->getRawState(CreateTable::TABLE)); + } + + public function testSetTemporary(): void + { + $ct = new CreateTable(); + self::assertSame($ct, $ct->setTemporary(false)); + self::assertFalse($ct->isTemporary()); + $ct->setTemporary(true); + self::assertTrue($ct->isTemporary()); + $ct->setTemporary('yes'); + self::assertTrue($ct->isTemporary()); + + self::assertStringStartsWith('CREATE TEMPORARY TABLE', $ct->getSqlString()); } } diff --git a/test/unit/Sql/Ddl/Index/IndexTest.php b/test/unit/Sql/Ddl/Index/IndexTest.php index 9bf9b7844..1e5fa73f5 100644 --- a/test/unit/Sql/Ddl/Index/IndexTest.php +++ b/test/unit/Sql/Ddl/Index/IndexTest.php @@ -23,48 +23,13 @@ public function testGetExpressionData(): void $expressionData = $uk->getExpressionData(); self::assertEquals('INDEX %s(%s)', $expressionData['spec']); - self::assertEquals([ - new Identifier('my_uk'), - new Identifier('foo'), - ], $expressionData['values']); - } - - public function testGetExpressionDataWithLength(): void - { - $key = new Index(['foo', 'bar'], 'my_uk', [10, 5]); - - $expressionData = $key->getExpressionData(); - - self::assertEquals('INDEX %s(%s(10), %s(5))', $expressionData['spec']); - self::assertEquals([ - new Identifier('my_uk'), - new Identifier('foo'), - new Identifier('bar'), - ], $expressionData['values']); - } - - public function testGetExpressionDataWithLengthUnmatched(): void - { - $key = new Index(['foo', 'bar'], 'my_uk', [10]); - - $expressionData = $key->getExpressionData(); - - self::assertEquals('INDEX %s(%s(10), %s)', $expressionData['spec']); - self::assertEquals([ - new Identifier('my_uk'), - new Identifier('foo'), - new Identifier('bar'), - ], $expressionData['values']); - } - - public function testSetTypeAndGetType(): void - { - $index = new Index('foo', 'my_idx'); - self::assertNull($index->getType()); - - $result = $index->setType('BTREE'); - self::assertSame($index, $result); - self::assertEquals('BTREE', $index->getType()); + self::assertEquals( + [ + new Identifier('my_uk'), + new Identifier('foo'), + ], + $expressionData['values'], + ); } public function testGetExpressionDataWithBtreeType(): void @@ -75,11 +40,14 @@ public function testGetExpressionDataWithBtreeType(): void $expressionData = $index->getExpressionData(); self::assertEquals('INDEX %s(%s) USING %s', $expressionData['spec']); - self::assertEquals([ - new Identifier('my_idx'), - new Identifier('foo'), - new Literal('BTREE'), - ], $expressionData['values']); + self::assertEquals( + [ + new Identifier('my_idx'), + new Identifier('foo'), + new Literal('BTREE'), + ], + $expressionData['values'], + ); } public function testGetExpressionDataWithHashType(): void @@ -90,11 +58,48 @@ public function testGetExpressionDataWithHashType(): void $expressionData = $index->getExpressionData(); self::assertEquals('INDEX %s(%s) USING %s', $expressionData['spec']); - self::assertEquals([ - new Identifier('my_idx'), - new Identifier('foo'), - new Literal('HASH'), - ], $expressionData['values']); + self::assertEquals( + [ + new Identifier('my_idx'), + new Identifier('foo'), + new Literal('HASH'), + ], + $expressionData['values'], + ); + } + + public function testGetExpressionDataWithLength(): void + { + $key = new Index(['foo', 'bar'], 'my_uk', [10, 5]); + + $expressionData = $key->getExpressionData(); + + self::assertEquals('INDEX %s(%s(10), %s(5))', $expressionData['spec']); + self::assertEquals( + [ + new Identifier('my_uk'), + new Identifier('foo'), + new Identifier('bar'), + ], + $expressionData['values'], + ); + } + + public function testGetExpressionDataWithLengthUnmatched(): void + { + $key = new Index(['foo', 'bar'], 'my_uk', [10]); + + $expressionData = $key->getExpressionData(); + + self::assertEquals('INDEX %s(%s(10), %s)', $expressionData['spec']); + self::assertEquals( + [ + new Identifier('my_uk'), + new Identifier('foo'), + new Identifier('bar'), + ], + $expressionData['values'], + ); } public function testGetExpressionDataWithTypeAndLengths(): void @@ -105,11 +110,24 @@ public function testGetExpressionDataWithTypeAndLengths(): void $expressionData = $index->getExpressionData(); self::assertEquals('INDEX %s(%s(10), %s(5)) USING %s', $expressionData['spec']); - self::assertEquals([ - new Identifier('my_idx'), - new Identifier('foo'), - new Identifier('bar'), - new Literal('BTREE'), - ], $expressionData['values']); + self::assertEquals( + [ + new Identifier('my_idx'), + new Identifier('foo'), + new Identifier('bar'), + new Literal('BTREE'), + ], + $expressionData['values'], + ); + } + + public function testSetTypeAndGetType(): void + { + $index = new Index('foo', 'my_idx'); + self::assertNull($index->getType()); + + $result = $index->setType('BTREE'); + self::assertSame($index, $result); + self::assertEquals('BTREE', $index->getType()); } } diff --git a/test/unit/Sql/DeleteTest.php b/test/unit/Sql/DeleteTest.php index b11e428de..487e40b14 100644 --- a/test/unit/Sql/DeleteTest.php +++ b/test/unit/Sql/DeleteTest.php @@ -47,22 +47,17 @@ final class DeleteTest extends TestCase protected Delete $delete; - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - #[Override] - protected function setUp(): void + public function testConstructorWithTable(): void { - $this->delete = new Delete(); + $delete = new Delete('foo'); + self::assertEquals('foo', $delete->getRawState('table')); } - /** - * Tears down the fixture, for example, closes a network connection. - * This method is called after a test is executed. - */ - protected function tearDown(): void + public function testConstructorWithTableIdentifier(): void { + $tableIdentifier = new TableIdentifier('foo', 'bar'); + $delete = new Delete($tableIdentifier); + self::assertEquals($tableIdentifier, $delete->getRawState('table')); } /** @@ -80,58 +75,60 @@ public function testFrom(): void self::assertEquals($tableIdentifier, $this->readAttribute($this->delete, 'table')); } - /** - * @throws ReflectionException - * @todo REMOVE THIS IN 3.x - */ - public function testWhere(): void + public function testGetRawState(): void { - $this->delete->where('x = y'); - $this->delete->where(['foo > ?' => 5]); - $this->delete->where(['id' => 2]); - $this->delete->where(['a = b'], PredicateSet::OP_OR); - $this->delete->where(['c1' => null]); - $this->delete->where(['c2' => [1, 2, 3]]); - $this->delete->where([new IsNotNull('c3')]); - $this->delete->where(['one' => 1, 'two' => 2]); - - $where = $this->delete->where; - - $predicates = $this->readAttribute($where, 'predicates'); - self::assertEquals('AND', $predicates[0][0]); - self::assertInstanceOf(Literal::class, $predicates[0][1]); + $this->delete->from('foo')->where('x = y'); - self::assertEquals('AND', $predicates[1][0]); - self::assertInstanceOf(Expression::class, $predicates[1][1]); + $rawState = $this->delete->getRawState(); - self::assertEquals('AND', $predicates[2][0]); - self::assertInstanceOf(Operator::class, $predicates[2][1]); + self::assertIsArray($rawState); + self::assertArrayHasKey('table', $rawState); + self::assertArrayHasKey('where', $rawState); + self::assertArrayHasKey('emptyWhereProtection', $rawState); - self::assertEquals('OR', $predicates[3][0]); - self::assertInstanceOf(Literal::class, $predicates[3][1]); + self::assertEquals('foo', $rawState['table']); + self::assertInstanceOf(Where::class, $rawState['where']); + self::assertTrue($rawState['emptyWhereProtection']); + } - self::assertEquals('AND', $predicates[4][0]); - self::assertInstanceOf(IsNull::class, $predicates[4][1]); + public function testGetRawStateWithKey(): void + { + $this->delete->from('foo'); - self::assertEquals('AND', $predicates[5][0]); - self::assertInstanceOf(In::class, $predicates[5][1]); + self::assertEquals('foo', $this->delete->getRawState('table')); + self::assertInstanceOf(Where::class, $this->delete->getRawState('where')); + self::assertTrue($this->delete->getRawState('emptyWhereProtection')); + } - self::assertEquals('AND', $predicates[6][0]); - self::assertInstanceOf(IsNotNull::class, $predicates[6][1]); + public function testGetSqlString(): void + { + $this->delete->from('foo')->where('x = y'); + self::assertEquals('DELETE FROM "foo" WHERE x = y', $this->delete->getSqlString()); - self::assertEquals('AND', $predicates[7][0]); - self::assertInstanceOf(Operator::class, $predicates[7][1]); + // Test with TableIdentifier + $this->delete = new Delete(); + $this->delete->from(new TableIdentifier('foo', 'sch'))->where('x = y'); + self::assertEquals('DELETE FROM "sch"."foo" WHERE x = y', $this->delete->getSqlString()); + } - self::assertEquals('AND', $predicates[8][0]); - self::assertInstanceOf(Operator::class, $predicates[8][1]); + public function testGetSqlStringWithEmptyWhere(): void + { + $this->delete->from('foo'); + // Empty where should not add WHERE clause + self::assertEquals('DELETE FROM "foo"', $this->delete->getSqlString()); + } - $where = new Where(); - $this->delete->where($where); - self::assertSame($where, $this->delete->where); + public function testMagicGetReturnsNullForUnknownProperty(): void + { + /** @noinspection PhpUndefinedFieldInspection */ + self::assertNull($this->delete->unknown); // @phpstan-ignore-line + self::assertNull($this->delete->table); // @phpstan-ignore-line + } - $this->delete->where(function ($what) use ($where): void { - self::assertSame($where, $what); - }); + public function testMagicGetReturnsWhereClause(): void + { + $where = $this->delete->where; + self::assertInstanceOf(Where::class, $where); } public function testPrepareStatement(): void @@ -144,8 +141,7 @@ public function testPrepareStatement(): void ->method('setSql') ->with($this->equalTo('DELETE FROM "foo" WHERE x = y')); - $this->delete->from('foo') - ->where('x = y'); + $this->delete->from('foo')->where('x = y'); $this->delete->prepareStatement($mockAdapter, $mockStatement); @@ -160,23 +156,25 @@ public function testPrepareStatement(): void ->method('setSql') ->with($this->equalTo('DELETE FROM "sch"."foo" WHERE x = y')); - $this->delete->from(new TableIdentifier('foo', 'sch')) - ->where('x = y'); + $this->delete->from(new TableIdentifier('foo', 'sch'))->where('x = y'); $this->delete->prepareStatement($mockAdapter, $mockStatement); } - public function testGetSqlString(): void + #[CoversNothing] + public function testSpecificationconstantsCouldBeOverridedByExtensionInGetSqlString(): void { - $this->delete->from('foo') + $deleteIgnore = new DeleteIgnore(); + + $deleteIgnore->from('foo') ->where('x = y'); - self::assertEquals('DELETE FROM "foo" WHERE x = y', $this->delete->getSqlString()); + self::assertEquals('DELETE IGNORE FROM "foo" WHERE x = y', $deleteIgnore->getSqlString()); - // Test with TableIdentifier - $this->delete = new Delete(); - $this->delete->from(new TableIdentifier('foo', 'sch')) + // with TableIdentifier + $deleteIgnore = new DeleteIgnore(); + $deleteIgnore->from(new TableIdentifier('foo', 'sch')) ->where('x = y'); - self::assertEquals('DELETE FROM "sch"."foo" WHERE x = y', $this->delete->getSqlString()); + self::assertEquals('DELETE IGNORE FROM "sch"."foo" WHERE x = y', $deleteIgnore->getSqlString()); } #[CoversNothing] @@ -214,85 +212,65 @@ public function testSpecificationconstantsCouldBeOverridedByExtensionInPrepareSt $deleteIgnore->prepareStatement($mockAdapter, $mockStatement); } - #[CoversNothing] - public function testSpecificationconstantsCouldBeOverridedByExtensionInGetSqlString(): void + /** + * @throws ReflectionException + * @todo REMOVE THIS IN 3.x + */ + public function testWhere(): void { - $deleteIgnore = new DeleteIgnore(); - - $deleteIgnore->from('foo') - ->where('x = y'); - self::assertEquals('DELETE IGNORE FROM "foo" WHERE x = y', $deleteIgnore->getSqlString()); + $this->delete->where('x = y'); + $this->delete->where(['foo > ?' => 5]); + $this->delete->where(['id' => 2]); + $this->delete->where(['a = b'], PredicateSet::OP_OR); + $this->delete->where(['c1' => null]); + $this->delete->where(['c2' => [1, 2, 3]]); + $this->delete->where([new IsNotNull('c3')]); + $this->delete->where(['one' => 1, 'two' => 2]); - // with TableIdentifier - $deleteIgnore = new DeleteIgnore(); - $deleteIgnore->from(new TableIdentifier('foo', 'sch')) - ->where('x = y'); - self::assertEquals('DELETE IGNORE FROM "sch"."foo" WHERE x = y', $deleteIgnore->getSqlString()); - } + $where = $this->delete->where; - public function testGetRawState(): void - { - $this->delete->from('foo') - ->where('x = y'); + $predicates = $this->readAttribute($where, 'predicates'); + self::assertEquals('AND', $predicates[0][0]); + self::assertInstanceOf(Literal::class, $predicates[0][1]); - $rawState = $this->delete->getRawState(); + self::assertEquals('AND', $predicates[1][0]); + self::assertInstanceOf(Expression::class, $predicates[1][1]); - self::assertIsArray($rawState); - self::assertArrayHasKey('table', $rawState); - self::assertArrayHasKey('where', $rawState); - self::assertArrayHasKey('emptyWhereProtection', $rawState); + self::assertEquals('AND', $predicates[2][0]); + self::assertInstanceOf(Operator::class, $predicates[2][1]); - self::assertEquals('foo', $rawState['table']); - self::assertInstanceOf(Where::class, $rawState['where']); - self::assertTrue($rawState['emptyWhereProtection']); - } + self::assertEquals('OR', $predicates[3][0]); + self::assertInstanceOf(Literal::class, $predicates[3][1]); - public function testGetRawStateWithKey(): void - { - $this->delete->from('foo'); + self::assertEquals('AND', $predicates[4][0]); + self::assertInstanceOf(IsNull::class, $predicates[4][1]); - self::assertEquals('foo', $this->delete->getRawState('table')); - self::assertInstanceOf(Where::class, $this->delete->getRawState('where')); - self::assertTrue($this->delete->getRawState('emptyWhereProtection')); - } + self::assertEquals('AND', $predicates[5][0]); + self::assertInstanceOf(In::class, $predicates[5][1]); - public function testMagicGetReturnsWhereClause(): void - { - $where = $this->delete->where; - self::assertInstanceOf(Where::class, $where); - } + self::assertEquals('AND', $predicates[6][0]); + self::assertInstanceOf(IsNotNull::class, $predicates[6][1]); - public function testMagicGetReturnsNullForUnknownProperty(): void - { - /** @noinspection PhpUndefinedFieldInspection */ - self::assertNull($this->delete->unknown); // @phpstan-ignore-line - self::assertNull($this->delete->table); // @phpstan-ignore-line - } + self::assertEquals('AND', $predicates[7][0]); + self::assertInstanceOf(Operator::class, $predicates[7][1]); - public function testConstructorWithTable(): void - { - $delete = new Delete('foo'); - self::assertEquals('foo', $delete->getRawState('table')); - } + self::assertEquals('AND', $predicates[8][0]); + self::assertInstanceOf(Operator::class, $predicates[8][1]); - public function testConstructorWithTableIdentifier(): void - { - $tableIdentifier = new TableIdentifier('foo', 'bar'); - $delete = new Delete($tableIdentifier); - self::assertEquals($tableIdentifier, $delete->getRawState('table')); - } + $where = new Where(); + $this->delete->where($where); + self::assertSame($where, $this->delete->where); - public function testGetSqlStringWithEmptyWhere(): void - { - $this->delete->from('foo'); - // Empty where should not add WHERE clause - self::assertEquals('DELETE FROM "foo"', $this->delete->getSqlString()); + $this->delete->where(static function ($what) use ($where): void { + self::assertSame($where, $what); + }); } #[TestDox('unit test: Test where() accepts Expression (ExpressionInterface) in array')] public function testWhereAcceptsExpressionInterface(): void { - $this->delete->from('foo') + $this->delete + ->from('foo') ->where([ new SqlExpression('COUNT(?) > ?', [new Identifier('id'), new Value(5)]), ]); @@ -301,4 +279,20 @@ public function testWhereAcceptsExpressionInterface(): void self::assertInstanceOf(Where::class, $where); self::assertEquals(1, $where->count()); } + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + #[Override] + protected function setUp(): void + { + $this->delete = new Delete(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown(): void {} } diff --git a/test/unit/Sql/ExpressionTest.php b/test/unit/Sql/ExpressionTest.php index 2ee05788d..ee2f37c5a 100644 --- a/test/unit/Sql/ExpressionTest.php +++ b/test/unit/Sql/ExpressionTest.php @@ -31,57 +31,56 @@ #[CoversMethod(Expression::class, 'getExpressionData')] final class ExpressionTest extends TestCase { - public function testSetExpression(): void + /** @psalm-return array */ + public static function falsyExpressionParametersProvider(): array { - $expression = new Expression(); - - // First mutation - $result = $expression->setExpression('Foo Bar'); - - // Verify fluent interface - self::assertSame($expression, $result); + return [ + [''], + ['0'], + [0], + [0.0], + [false], + ]; + } - // Verify the first mutation occurred - self::assertEquals('Foo Bar', $expression->getExpression()); + #[DataProvider('falsyExpressionParametersProvider')] + public function testConstructorWithFalsyValidParameters(mixed $falsyParameter): void + { + $expression = new Expression('?', $falsyParameter); + $falsyValue = Argument::value($falsyParameter); - // Second mutation to verify mutability - $expression->setExpression('Baz Qux'); + $expressionData = $expression->getExpressionData(); - // Verify the instance was actually mutated - self::assertEquals('Baz Qux', $expression->getExpression()); + self::assertEquals([$falsyValue], $expressionData['values']); } - public function testSetExpressionException(): void + public function testConstructorWithInvalidParameter(): void { - $expression = new Expression(); $this->expectException(TypeError::class); - /** @noinspection PhpStrictTypeCheckingInspection */ - $expression->setExpression(null); - - $expression = new Expression(); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Supplied expression must not be an empty string.'); - $expression->setExpression(''); + new Expression('?', (object) []); } - public function testSetParameters(): void + public function testConstructorWithLiteralZero(): void { - $expression = new Expression(); - - // First mutation - $result = $expression->setParameters('foo'); - - // Verify fluent interface - self::assertSame($expression, $result); + $expression = new Expression('0'); + self::assertSame('0', $expression->getExpression()); + } - // Verify the first mutation occurred - self::assertEquals([new Value('foo')], $expression->getParameters()); + public function testConstructorWithMultipleArguments(): void + { + $expression = new Expression('? + ? - ?', 1, 2, 3); - // Second mutation to verify mutability (setParameters appends) - $expression->setParameters('bar'); + $expressionData = $expression->getExpressionData(); - // Verify the instance was actually mutated (now has both parameters) - self::assertEquals([new Value('foo'), new Value('bar')], $expression->getParameters()); + self::assertEquals('%s + %s - %s', $expressionData['spec']); + self::assertEquals( + [ + Argument::value(1), + Argument::value(2), + Argument::value(3), + ], + $expressionData['values'], + ); } public function testGetExpressionData(): void @@ -92,32 +91,53 @@ public function testGetExpressionData(): void new Argument\Identifier('foo'), new Argument\Value(5), new Argument\Literal('FUNC(FF%X)'), - ] + ], ); $expressionData = $expression->getExpressionData(); self::assertEquals('X SAME AS %s AND Y = %s BUT LITERALLY %s', $expressionData['spec']); - self::assertEquals([ - new Identifier('foo'), - new Value(5), - new Literal('FUNC(FF%X)'), - ], $expressionData['values']); + self::assertEquals( + [ + new Identifier('foo'), + new Value(5), + new Literal('FUNC(FF%X)'), + ], + $expressionData['values'], + ); } - public function testGetExpressionDataWillEscapePercent(): void + public function testGetExpressionDataThrowsExceptionWhenParameterCountMismatch(): void { - $expression = new Expression('X LIKE "foo%"'); + $expression = new Expression('? AND ?', [1]); // Two placeholders but only one parameter + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'The number of replacements in the expression does not match the number of parameters', + ); + $expression->getExpressionData(); + } + + public function testGetExpressionDataUsesRegexWhenPlaceholderCountMismatches(): void + { + $expression = new Expression('uf.user_id = :user_id OR uf.friend_id = :user_id', ['user_id' => 1]); $expressionData = $expression->getExpressionData(); - self::assertEquals('X LIKE "foo%%"', $expressionData['spec']); + self::assertEquals( + 'uf.user_id = :user_id OR uf.friend_id = :user_id', + $expressionData['spec'], + ); + self::assertCount(1, $expressionData['values']); } - public function testConstructorWithLiteralZero(): void + public function testGetExpressionDataWillEscapePercent(): void { - $expression = new Expression('0'); - self::assertSame('0', $expression->getExpression()); + $expression = new Expression('X LIKE "foo%"'); + + $expressionData = $expression->getExpressionData(); + + self::assertEquals('X LIKE "foo%%"', $expressionData['spec']); } #[Group('7407')] @@ -138,40 +158,11 @@ public function testNumberOfReplacementsConsidersWhenSameVariableIsUsedManyTimes self::assertEquals( 'uf.user_id = :user_id OR uf.friend_id = :user_id', - $expressionData['spec'] + $expressionData['spec'], ); self::assertEquals([$value], $expressionData['values']); } - #[DataProvider('falsyExpressionParametersProvider')] - public function testConstructorWithFalsyValidParameters(mixed $falsyParameter): void - { - $expression = new Expression('?', $falsyParameter); - $falsyValue = Argument::value($falsyParameter); - - $expressionData = $expression->getExpressionData(); - - self::assertEquals([$falsyValue], $expressionData['values']); - } - - public function testConstructorWithInvalidParameter(): void - { - $this->expectException(TypeError::class); - new Expression('?', (object) []); - } - - /** @psalm-return array */ - public static function falsyExpressionParametersProvider(): array - { - return [ - [''], - ['0'], - [0], - [0.0], - [false], - ]; - } - public function testNumberOfReplacementsForExpressionWithParameters(): void { $expression = new Expression(':a + :b', ['a' => 1, 'b' => 2]); @@ -184,29 +175,37 @@ public function testNumberOfReplacementsForExpressionWithParameters(): void self::assertEquals([$value1, $value2], $expressionData['values']); } - public function testGetExpressionDataThrowsExceptionWhenParameterCountMismatch(): void + public function testSetExpression(): void { - $expression = new Expression('? AND ?', [1]); // Two placeholders but only one parameter + $expression = new Expression(); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage( - 'The number of replacements in the expression does not match the number of parameters' - ); - $expression->getExpressionData(); + // First mutation + $result = $expression->setExpression('Foo Bar'); + + // Verify fluent interface + self::assertSame($expression, $result); + + // Verify the first mutation occurred + self::assertEquals('Foo Bar', $expression->getExpression()); + + // Second mutation to verify mutability + $expression->setExpression('Baz Qux'); + + // Verify the instance was actually mutated + self::assertEquals('Baz Qux', $expression->getExpression()); } - public function testConstructorWithMultipleArguments(): void + public function testSetExpressionException(): void { - $expression = new Expression('? + ? - ?', 1, 2, 3); - - $expressionData = $expression->getExpressionData(); + $expression = new Expression(); + $this->expectException(TypeError::class); + /** @noinspection PhpStrictTypeCheckingInspection */ + $expression->setExpression(null); - self::assertEquals('%s + %s - %s', $expressionData['spec']); - self::assertEquals([ - Argument::value(1), - Argument::value(2), - Argument::value(3), - ], $expressionData['values']); + $expression = new Expression(); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Supplied expression must not be an empty string.'); + $expression->setExpression(''); } public function testSetExpressionThrowsOnEmptyString(): void @@ -217,26 +216,33 @@ public function testSetExpressionThrowsOnEmptyString(): void $expression->setExpression(''); } - public function testSetParametersWrapsArrayInValuesArgument(): void + public function testSetParameters(): void { - $expression = new Expression('? IN (?)', [Argument::identifier('id'), [1, 2, 3]]); + $expression = new Expression(); - $data = $expression->getExpressionData(); + // First mutation + $result = $expression->setParameters('foo'); - self::assertCount(2, $data['values']); - self::assertInstanceOf(Argument\Values::class, $data['values'][1]); + // Verify fluent interface + self::assertSame($expression, $result); + + // Verify the first mutation occurred + self::assertEquals([new Value('foo')], $expression->getParameters()); + + // Second mutation to verify mutability (setParameters appends) + $expression->setParameters('bar'); + + // Verify the instance was actually mutated (now has both parameters) + self::assertEquals([new Value('foo'), new Value('bar')], $expression->getParameters()); } - public function testGetExpressionDataUsesRegexWhenPlaceholderCountMismatches(): void + public function testSetParametersWrapsArrayInValuesArgument(): void { - $expression = new Expression('uf.user_id = :user_id OR uf.friend_id = :user_id', ['user_id' => 1]); + $expression = new Expression('? IN (?)', [Argument::identifier('id'), [1, 2, 3]]); - $expressionData = $expression->getExpressionData(); + $data = $expression->getExpressionData(); - self::assertEquals( - 'uf.user_id = :user_id OR uf.friend_id = :user_id', - $expressionData['spec'] - ); - self::assertCount(1, $expressionData['values']); + self::assertCount(2, $data['values']); + self::assertInstanceOf(Argument\Values::class, $data['values'][1]); } } diff --git a/test/unit/Sql/InsertIgnoreTest.php b/test/unit/Sql/InsertIgnoreTest.php index e1c851ed1..d73efb277 100644 --- a/test/unit/Sql/InsertIgnoreTest.php +++ b/test/unit/Sql/InsertIgnoreTest.php @@ -35,75 +35,62 @@ final class InsertIgnoreTest extends TestCase protected InsertIgnore $insert; - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - #[Override] - protected function setUp(): void + // @codingStandardsIgnoreStart + public function test__get(): void { - $this->insert = new InsertIgnore(); + // @codingStandardsIgnoreEnd + $this->insert->foo = 'bar'; + self::assertEquals('bar', $this->insert->foo); + + $this->insert->foo = null; + self::assertNull($this->insert->foo); } - public function testInto(): void + // @codingStandardsIgnoreStart + public function test__isset(): void { - $this->insert->into('table'); - self::assertEquals('table', $this->insert->getRawState('table')); + // @codingStandardsIgnoreEnd + $this->insert->foo = 'bar'; + self::assertTrue(isset($this->insert->foo)); - $tableIdentifier = new TableIdentifier('table', 'schema'); - $this->insert->into($tableIdentifier); - self::assertEquals($tableIdentifier, $this->insert->getRawState('table')); + $this->insert->foo = null; + self::assertTrue(isset($this->insert->foo)); } - public function testColumns(): void + // @codingStandardsIgnoreStart + public function test__set(): void { - $columns = ['foo', 'bar']; - $this->insert->columns($columns); - self::assertEquals($columns, $this->insert->getRawState('columns')); + // @codingStandardsIgnoreEnd + $this->insert->foo = 'bar'; + self::assertEquals(['foo'], $this->insert->getRawState('columns')); + self::assertEquals(['bar'], $this->insert->getRawState('values')); } - public function testValues(): void + // @codingStandardsIgnoreStart + public function test__unset(): void { - $this->insert->values(['foo' => 'bar']); + // @codingStandardsIgnoreEnd + $this->insert->foo = 'bar'; self::assertEquals(['foo'], $this->insert->getRawState('columns')); self::assertEquals(['bar'], $this->insert->getRawState('values')); + unset($this->insert->foo); + self::assertEquals([], $this->insert->getRawState('columns')); + self::assertEquals([], $this->insert->getRawState('values')); - // test will merge cols and values of previously set stuff - $this->insert->values(['foo' => 'bax'], Insert::VALUES_MERGE); - $this->insert->values(['boom' => 'bam'], Insert::VALUES_MERGE); - self::assertEquals(['foo', 'boom'], $this->insert->getRawState('columns')); - self::assertEquals(['bax', 'bam'], $this->insert->getRawState('values')); - - $this->insert->values(['foo' => 'bax']); + $this->insert->foo = null; self::assertEquals(['foo'], $this->insert->getRawState('columns')); - self::assertEquals(['bax'], $this->insert->getRawState('values')); - } - - public function testValuesThrowsExceptionWhenNotArrayOrSelect(): void - { - $this->expectException(TypeError::class); - $this->insert->values(5); - } - - public function testValuesThrowsExceptionWhenSelectMergeOverArray(): void - { - $this->insert->values(['foo' => 'bar']); + self::assertEquals([null], $this->insert->getRawState('values')); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('A PhpDb\Sql\Select instance cannot be provided with the merge flag'); - $this->insert->values(new Select(), Insert::VALUES_MERGE); + unset($this->insert->foo); + self::assertEquals([], $this->insert->getRawState('columns')); + self::assertEquals([], $this->insert->getRawState('values')); } - public function testValuesThrowsExceptionWhenArrayMergeOverSelect(): void + public function testColumns(): void { - $this->insert->values(new Select()); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage( - 'An array of values cannot be provided with the merge flag when a PhpDb\Sql\Select instance already ' - . 'exists as the value source' - ); - $this->insert->values(['foo' => 'bar'], Insert::VALUES_MERGE); + $columns = ['foo', 'bar']; + $this->insert->columns($columns); + self::assertEquals($columns, $this->insert->getRawState('columns')); } /** @@ -116,6 +103,67 @@ public function testEmptyArrayValues(): void self::assertEquals([], $this->readAttribute($this->insert, 'columns')); } + public function testGetSqlString(): void + { + $this->insert->into('foo')->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); + + self::assertEquals( + 'INSERT IGNORE INTO "foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', + $this->insert->getSqlString(new TrustingSql92Platform()), + ); + + // with TableIdentifier + $this->insert = new InsertIgnore(); + $this->insert + ->into(new TableIdentifier('foo', 'sch')) + ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); + + self::assertEquals( + 'INSERT IGNORE INTO "sch"."foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', + $this->insert->getSqlString(new TrustingSql92Platform()), + ); + + // with Select + $this->insert = new InsertIgnore(); + $select = new Select(); + $this->insert->into('foo')->select($select->from('bar')); + + self::assertEquals( + 'INSERT IGNORE INTO "foo" SELECT "bar".* FROM "bar"', + $this->insert->getSqlString(new TrustingSql92Platform()), + ); + + // with Select and columns + $this->insert->columns(['col1', 'col2']); + self::assertEquals( + 'INSERT IGNORE INTO "foo" ("col1", "col2") SELECT "bar".* FROM "bar"', + $this->insert->getSqlString(new TrustingSql92Platform()), + ); + } + + public function testGetSqlStringUsingColumnsAndValuesMethods(): void + { + // With columns() and values() + $this->insert + ->into('foo') + ->columns(['col1', 'col2', 'col3']) + ->values(['val1', 'val2', 'val3']); + self::assertEquals( + 'INSERT IGNORE INTO "foo" ("col1", "col2", "col3") VALUES (\'val1\', \'val2\', \'val3\')', + $this->insert->getSqlString(new TrustingSql92Platform()), + ); + } + + public function testInto(): void + { + $this->insert->into('table'); + self::assertEquals('table', $this->insert->getRawState('table')); + + $tableIdentifier = new TableIdentifier('table', 'schema'); + $this->insert->into($tableIdentifier); + self::assertEquals($tableIdentifier, $this->insert->getRawState('table')); + } + public function testPrepareStatement(): void { $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); @@ -130,8 +178,7 @@ public function testPrepareStatement(): void ->method('setSql') ->with($this->equalTo('INSERT IGNORE INTO "foo" ("bar", "boo") VALUES (?, NOW())')); - $this->insert->into('foo') - ->values(['bar' => 'baz', 'boo' => new Expression('NOW()')]); + $this->insert->into('foo')->values(['bar' => 'baz', 'boo' => new Expression('NOW()')]); $this->insert->prepareStatement($mockAdapter, $mockStatement); @@ -150,7 +197,8 @@ public function testPrepareStatement(): void ->method('setSql') ->with($this->equalTo('INSERT IGNORE INTO "sch"."foo" ("bar", "boo") VALUES (?, NOW())')); - $this->insert->into(new TableIdentifier('foo', 'sch')) + $this->insert + ->into(new TableIdentifier('foo', 'sch')) ->values(['bar' => 'baz', 'boo' => new Expression('NOW()')]); $this->insert->prepareStatement($mockAdapter, $mockStatement); @@ -167,132 +215,38 @@ public function testPrepareStatementWithSelect(): void $select = new Select('bar'); $this->insert - ->into('foo') - ->columns(['col1']) - ->select($select->where(['x' => 5])) - ->prepareStatement($mockAdapter, $mockStatement); + ->into('foo') + ->columns(['col1']) + ->select($select->where(['x' => 5])) + ->prepareStatement($mockAdapter, $mockStatement); self::assertEquals( 'INSERT IGNORE INTO "foo" ("col1") SELECT "bar".* FROM "bar" WHERE "x" = ?', - $mockStatement->getSql() + $mockStatement->getSql(), ); $parameters = $mockStatement->getParameterContainer()->getNamedArray(); self::assertSame(['subselect1where1' => 5], $parameters); } - public function testGetSqlString(): void + public function testSpecificationconstantsCouldBeOverridedByExtensionInGetSqlString(): void { - $this->insert->into('foo') + $replace = new Replace(); + $replace->into('foo') ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); self::assertEquals( - 'INSERT IGNORE INTO "foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', - $this->insert->getSqlString(new TrustingSql92Platform()) + 'REPLACE INTO "foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', + $replace->getSqlString(new TrustingSql92Platform()), ); // with TableIdentifier - $this->insert = new InsertIgnore(); - $this->insert->into(new TableIdentifier('foo', 'sch')) - ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); - - self::assertEquals( - 'INSERT IGNORE INTO "sch"."foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', - $this->insert->getSqlString(new TrustingSql92Platform()) - ); - - // with Select - $this->insert = new InsertIgnore(); - $select = new Select(); - $this->insert->into('foo')->select($select->from('bar')); - - self::assertEquals( - 'INSERT IGNORE INTO "foo" SELECT "bar".* FROM "bar"', - $this->insert->getSqlString(new TrustingSql92Platform()) - ); - - // with Select and columns - $this->insert->columns(['col1', 'col2']); - self::assertEquals( - 'INSERT IGNORE INTO "foo" ("col1", "col2") SELECT "bar".* FROM "bar"', - $this->insert->getSqlString(new TrustingSql92Platform()) - ); - } - - public function testGetSqlStringUsingColumnsAndValuesMethods(): void - { - // With columns() and values() - $this->insert - ->into('foo') - ->columns(['col1', 'col2', 'col3']) - ->values(['val1', 'val2', 'val3']); - self::assertEquals( - 'INSERT IGNORE INTO "foo" ("col1", "col2", "col3") VALUES (\'val1\', \'val2\', \'val3\')', - $this->insert->getSqlString(new TrustingSql92Platform()) - ); - } - - // @codingStandardsIgnoreStart - public function test__set(): void - { - // @codingStandardsIgnoreEnd - $this->insert->foo = 'bar'; - self::assertEquals(['foo'], $this->insert->getRawState('columns')); - self::assertEquals(['bar'], $this->insert->getRawState('values')); - } - - // @codingStandardsIgnoreStart - public function test__unset(): void - { - // @codingStandardsIgnoreEnd - $this->insert->foo = 'bar'; - self::assertEquals(['foo'], $this->insert->getRawState('columns')); - self::assertEquals(['bar'], $this->insert->getRawState('values')); - unset($this->insert->foo); - self::assertEquals([], $this->insert->getRawState('columns')); - self::assertEquals([], $this->insert->getRawState('values')); - - $this->insert->foo = null; - self::assertEquals(['foo'], $this->insert->getRawState('columns')); - self::assertEquals([null], $this->insert->getRawState('values')); - - unset($this->insert->foo); - self::assertEquals([], $this->insert->getRawState('columns')); - self::assertEquals([], $this->insert->getRawState('values')); - } - - // @codingStandardsIgnoreStart - public function test__isset(): void - { - // @codingStandardsIgnoreEnd - $this->insert->foo = 'bar'; - self::assertTrue(isset($this->insert->foo)); - - $this->insert->foo = null; - self::assertTrue(isset($this->insert->foo)); - } - - // @codingStandardsIgnoreStart - public function test__get(): void - { - // @codingStandardsIgnoreEnd - $this->insert->foo = 'bar'; - self::assertEquals('bar', $this->insert->foo); - - $this->insert->foo = null; - self::assertNull($this->insert->foo); - } - - #[Group('Laminas-536')] - public function testValuesMerge(): void - { - $this->insert->into('foo') + $replace = new Replace(); + $replace->into(new TableIdentifier('foo', 'sch')) ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); - $this->insert->into('foo') - ->values(['qux' => 100], Insert::VALUES_MERGE); self::assertEquals( - 'INSERT IGNORE INTO "foo" ("bar", "boo", "bam", "qux") VALUES (\'baz\', NOW(), NULL, \'100\')', - $this->insert->getSqlString(new TrustingSql92Platform()) + 'REPLACE INTO "sch"."foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', + $replace->getSqlString(new TrustingSql92Platform()), ); } @@ -338,25 +292,70 @@ public function testSpecificationconstantsCouldBeOverridedByExtensionInPrepareSt $replace->prepareStatement($mockAdapter, $mockStatement); } - public function testSpecificationconstantsCouldBeOverridedByExtensionInGetSqlString(): void + public function testValues(): void { - $replace = new Replace(); - $replace->into('foo') - ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); + $this->insert->values(['foo' => 'bar']); + self::assertEquals(['foo'], $this->insert->getRawState('columns')); + self::assertEquals(['bar'], $this->insert->getRawState('values')); + + // test will merge cols and values of previously set stuff + $this->insert->values(['foo' => 'bax'], Insert::VALUES_MERGE); + $this->insert->values(['boom' => 'bam'], Insert::VALUES_MERGE); + self::assertEquals(['foo', 'boom'], $this->insert->getRawState('columns')); + self::assertEquals(['bax', 'bam'], $this->insert->getRawState('values')); + + $this->insert->values(['foo' => 'bax']); + self::assertEquals(['foo'], $this->insert->getRawState('columns')); + self::assertEquals(['bax'], $this->insert->getRawState('values')); + } + + #[Group('Laminas-536')] + public function testValuesMerge(): void + { + $this->insert->into('foo')->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); + $this->insert->into('foo') + ->values(['qux' => 100], Insert::VALUES_MERGE); self::assertEquals( - 'REPLACE INTO "foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', - $replace->getSqlString(new TrustingSql92Platform()) + 'INSERT IGNORE INTO "foo" ("bar", "boo", "bam", "qux") VALUES (\'baz\', NOW(), NULL, \'100\')', + $this->insert->getSqlString(new TrustingSql92Platform()), ); + } - // with TableIdentifier - $replace = new Replace(); - $replace->into(new TableIdentifier('foo', 'sch')) - ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); + public function testValuesThrowsExceptionWhenArrayMergeOverSelect(): void + { + $this->insert->values(new Select()); - self::assertEquals( - 'REPLACE INTO "sch"."foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', - $replace->getSqlString(new TrustingSql92Platform()) + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage( + 'An array of values cannot be provided with the merge flag when a PhpDb\Sql\Select instance already ' + . 'exists as the value source', ); + $this->insert->values(['foo' => 'bar'], Insert::VALUES_MERGE); + } + + public function testValuesThrowsExceptionWhenNotArrayOrSelect(): void + { + $this->expectException(TypeError::class); + $this->insert->values(5); + } + + public function testValuesThrowsExceptionWhenSelectMergeOverArray(): void + { + $this->insert->values(['foo' => 'bar']); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('A PhpDb\Sql\Select instance cannot be provided with the merge flag'); + $this->insert->values(new Select(), Insert::VALUES_MERGE); + } + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + #[Override] + protected function setUp(): void + { + $this->insert = new InsertIgnore(); } } diff --git a/test/unit/Sql/InsertTest.php b/test/unit/Sql/InsertTest.php index cbc4d745f..bde66673e 100644 --- a/test/unit/Sql/InsertTest.php +++ b/test/unit/Sql/InsertTest.php @@ -51,86 +51,158 @@ final class InsertTest extends TestCase protected Insert $insert; - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - #[Override] - protected function setUp(): void + // @codingStandardsIgnoreStart + public function test__get(): void { - $this->insert = new Insert(); + // @codingStandardsIgnoreEnd + /** @psalm-suppress UndefinedMagicPropertyAssignment */ + $this->insert->foo = 'bar'; + self::assertEquals('bar', $this->insert->foo); + + /** @psalm-suppress UndefinedMagicPropertyAssignment */ + $this->insert->foo = null; + self::assertNull($this->insert->foo); } - public function testInto(): void + // @codingStandardsIgnoreStart + public function test__isset(): void { - $this->insert->into('table'); - self::assertEquals('table', $this->insert->getRawState('table')); + // @codingStandardsIgnoreEnd + /** @psalm-suppress UndefinedMagicPropertyAssignment */ + $this->insert->foo = 'bar'; + /** @psalm-suppress RedundantCondition */ + self::assertTrue(isset($this->insert->foo)); - $tableIdentifier = new TableIdentifier('table', 'schema'); - $this->insert->into($tableIdentifier); - self::assertEquals($tableIdentifier, $this->insert->getRawState('table')); + /** @psalm-suppress UndefinedMagicPropertyAssignment */ + $this->insert->foo = null; + /** @psalm-suppress TypeDoesNotContainType */ + self::assertTrue(isset($this->insert->foo)); } - public function testColumns(): void + // @codingStandardsIgnoreStart + public function test__set(): void { - $columns = ['foo', 'bar']; - $this->insert->columns($columns); - self::assertEquals($columns, $this->insert->getRawState('columns')); + // @codingStandardsIgnoreEnd + /** @psalm-suppress UndefinedMagicPropertyAssignment */ + $this->insert->foo = 'bar'; + self::assertEquals(['foo'], $this->insert->getRawState('columns')); + self::assertEquals(['bar'], $this->insert->getRawState('values')); } - public function testValues(): void + // @codingStandardsIgnoreStart + public function test__unset(): void { - $this->insert->values(['foo' => 'bar']); + // @codingStandardsIgnoreEnd + /** @psalm-suppress UndefinedMagicPropertyAssignment */ + $this->insert->foo = 'bar'; self::assertEquals(['foo'], $this->insert->getRawState('columns')); self::assertEquals(['bar'], $this->insert->getRawState('values')); + unset($this->insert->foo); + self::assertEquals([], $this->insert->getRawState('columns')); + self::assertEquals([], $this->insert->getRawState('values')); - // test will merge cols and values of previously set stuff - $this->insert->values(['foo' => 'bax'], Insert::VALUES_MERGE); - $this->insert->values(['boom' => 'bam'], Insert::VALUES_MERGE); - self::assertEquals(['foo', 'boom'], $this->insert->getRawState('columns')); - self::assertEquals(['bax', 'bam'], $this->insert->getRawState('values')); - - $this->insert->values(['foo' => 'bax']); + /** @psalm-suppress UndefinedMagicPropertyAssignment */ + $this->insert->foo = null; self::assertEquals(['foo'], $this->insert->getRawState('columns')); - self::assertEquals(['bax'], $this->insert->getRawState('values')); + self::assertEquals([null], $this->insert->getRawState('values')); + + unset($this->insert->foo); + self::assertEquals([], $this->insert->getRawState('columns')); + self::assertEquals([], $this->insert->getRawState('values')); } - public function testValuesThrowsExceptionWhenNotArrayOrSelect(): void + public function testColumns(): void { - $this->expectException(TypeError::class); - /** @psalm-suppress InvalidArgument */ - $this->insert->values(5); + $columns = ['foo', 'bar']; + $this->insert->columns($columns); + self::assertEquals($columns, $this->insert->getRawState('columns')); } - public function testValuesThrowsExceptionWhenSelectMergeOverArray(): void + /** + * @throws ReflectionException + */ + #[Group('Laminas-4926')] + public function testEmptyArrayValues(): void { - $this->insert->values(['foo' => 'bar']); + $this->insert->values([]); + self::assertEquals([], $this->readAttribute($this->insert, 'columns')); + } - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('A PhpDb\Sql\Select instance cannot be provided with the merge flag'); - $this->insert->values(new Select(), Insert::VALUES_MERGE); + public function testGetSqlString(): void + { + $this->insert->into('foo')->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); + + self::assertEquals( + 'INSERT INTO "foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', + $this->insert->getSqlString(new TrustingSql92Platform()), + ); + + // with TableIdentifier + $this->insert = new Insert(); + $this->insert + ->into(new TableIdentifier('foo', 'sch')) + ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); + + self::assertEquals( + 'INSERT INTO "sch"."foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', + $this->insert->getSqlString(new TrustingSql92Platform()), + ); + + // with Select + $this->insert = new Insert(); + $select = new Select(); + $this->insert->into('foo')->select($select->from('bar')); + + self::assertEquals( + 'INSERT INTO "foo" SELECT "bar".* FROM "bar"', + $this->insert->getSqlString(new TrustingSql92Platform()), + ); + + // with Select and columns + $this->insert->columns(['col1', 'col2']); + self::assertEquals( + 'INSERT INTO "foo" ("col1", "col2") SELECT "bar".* FROM "bar"', + $this->insert->getSqlString(new TrustingSql92Platform()), + ); } - public function testValuesThrowsExceptionWhenArrayMergeOverSelect(): void + public function testGetSqlStringThrowsExceptionWhenNoValuesOrSelect(): void { - $this->insert->values(new Select()); + $this->insert->into('foo'); $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage( - 'An array of values cannot be provided with the merge flag when a PhpDb\Sql\Select instance already ' - . 'exists as the value source' + $this->expectExceptionMessage('values or select should be present'); + $this->insert->getSqlString(new TrustingSql92Platform()); + } + + public function testGetSqlStringUsingColumnsAndValuesMethods(): void + { + // With columns() and values() + $this->insert + ->into('foo') + ->columns(['col1', 'col2', 'col3']) + ->values(['val1', 'val2', 'val3']); + self::assertEquals( + 'INSERT INTO "foo" ("col1", "col2", "col3") VALUES (\'val1\', \'val2\', \'val3\')', + $this->insert->getSqlString(new TrustingSql92Platform()), ); - $this->insert->values(['foo' => 'bar'], Insert::VALUES_MERGE); } - /** - * @throws ReflectionException - */ - #[Group('Laminas-4926')] - public function testEmptyArrayValues(): void + public function testGetThrowsExceptionForNonExistentColumn(): void { - $this->insert->values([]); - self::assertEquals([], $this->readAttribute($this->insert, 'columns')); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The key nonexistent was not found in this objects column list'); + $value = $this->insert->nonexistent; + } + + public function testInto(): void + { + $this->insert->into('table'); + self::assertEquals('table', $this->insert->getRawState('table')); + + $tableIdentifier = new TableIdentifier('table', 'schema'); + $this->insert->into($tableIdentifier); + self::assertEquals($tableIdentifier, $this->insert->getRawState('table')); } public function testPrepareStatement(): void @@ -147,8 +219,7 @@ public function testPrepareStatement(): void ->method('setSql') ->with($this->equalTo('INSERT INTO "foo" ("bar", "boo") VALUES (?, NOW())')); - $this->insert->into('foo') - ->values(['bar' => 'baz', 'boo' => new Expression('NOW()')]); + $this->insert->into('foo')->values(['bar' => 'baz', 'boo' => new Expression('NOW()')]); $this->insert->prepareStatement($mockAdapter, $mockStatement); @@ -167,12 +238,40 @@ public function testPrepareStatement(): void ->method('setSql') ->with($this->equalTo('INSERT INTO "sch"."foo" ("bar", "boo") VALUES (?, NOW())')); - $this->insert->into(new TableIdentifier('foo', 'sch')) + $this->insert + ->into(new TableIdentifier('foo', 'sch')) ->values(['bar' => 'baz', 'boo' => new Expression('NOW()')]); $this->insert->prepareStatement($mockAdapter, $mockStatement); } + public function testPrepareStatementCreatesParameterContainerWhenNotPresent(): void + { + $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + $mockDriver->expects($this->any())->method('getPrepareType')->willReturn('positional'); + $mockDriver->expects($this->any())->method('formatParameterName')->willReturn('?'); + $mockAdapter = $this->createMockAdapter($mockDriver); + + $mockStatement = $this->getMockBuilder(StatementInterface::class)->getMock(); + $mockStatement->expects($this->once()) + ->method('getParameterContainer') + ->willReturn(null); + $mockStatement->expects($this->once()) + ->method('setParameterContainer') + ->with($this->isInstanceOf(ParameterContainer::class)) + ->willReturnSelf(); + $mockStatement->expects($this->once()) + ->method('setSql') + ->with($this->stringContains('INSERT INTO')) + ->willReturnSelf(); + + $this->insert->into('foo')->values(['bar' => 'baz']); + + $result = $this->insert->prepareStatement($mockAdapter, $mockStatement); + + self::assertSame($mockStatement, $result); + } + public function testPrepareStatementWithSelect(): void { $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); @@ -184,14 +283,14 @@ public function testPrepareStatementWithSelect(): void $select = new Select('bar'); $this->insert - ->into('foo') - ->columns(['col1']) - ->select($select->where(['x' => 5])) - ->prepareStatement($mockAdapter, $mockStatement); + ->into('foo') + ->columns(['col1']) + ->select($select->where(['x' => 5])) + ->prepareStatement($mockAdapter, $mockStatement); self::assertEquals( 'INSERT INTO "foo" ("col1") SELECT "bar".* FROM "bar" WHERE "x" = ?', - $mockStatement->getSql() + $mockStatement->getSql(), ); $parameters = $mockStatement->getParameterContainer(); $this->assertInstanceOf(ParameterContainer::class, $parameters); @@ -200,154 +299,49 @@ public function testPrepareStatementWithSelect(): void self::assertSame(['subselect1where1' => 5], $namedArray); } - public function testGetSqlString(): void + public function testProcessInsertWithPdoDriverUsesDriverColumnQuoting(): void { - $this->insert->into('foo') - ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); - - self::assertEquals( - 'INSERT INTO "foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', - $this->insert->getSqlString(new TrustingSql92Platform()) - ); - - // with TableIdentifier - $this->insert = new Insert(); - $this->insert->into(new TableIdentifier('foo', 'sch')) - ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); + $mockDriver = $this->getMockBuilder(PdoDriverInterface::class)->getMock(); + $mockDriver->expects($this->any())->method('getPrepareType')->willReturn('positional'); + $mockDriver->expects($this->any()) + ->method('formatParameterName') + ->willReturnCallback(static fn(string $name): string => ":{$name}"); + $mockAdapter = $this->createMockAdapter($mockDriver); - self::assertEquals( - 'INSERT INTO "sch"."foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', - $this->insert->getSqlString(new TrustingSql92Platform()) - ); + $mockStatement = new StatementContainer(); - // with Select - $this->insert = new Insert(); - $select = new Select(); - $this->insert->into('foo')->select($select->from('bar')); + $this->insert->into('foo')->values(['bar' => 'baz', 'boo' => 'qux']); - self::assertEquals( - 'INSERT INTO "foo" SELECT "bar".* FROM "bar"', - $this->insert->getSqlString(new TrustingSql92Platform()) - ); + $this->insert->prepareStatement($mockAdapter, $mockStatement); - // with Select and columns - $this->insert->columns(['col1', 'col2']); - self::assertEquals( - 'INSERT INTO "foo" ("col1", "col2") SELECT "bar".* FROM "bar"', - $this->insert->getSqlString(new TrustingSql92Platform()) - ); + $sql = $mockStatement->getSql(); + self::assertStringContainsString(':c_0', $sql); + self::assertStringContainsString(':c_1', $sql); } - public function testGetSqlStringUsingColumnsAndValuesMethods(): void + #[CoversNothing] + public function testSpecificationconstantsCouldBeOverridedByExtensionInGetSqlString(): void { - // With columns() and values() - $this->insert - ->into('foo') - ->columns(['col1', 'col2', 'col3']) - ->values(['val1', 'val2', 'val3']); + $replace = new Replace(); + $replace->into('foo') + ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); + self::assertEquals( - 'INSERT INTO "foo" ("col1", "col2", "col3") VALUES (\'val1\', \'val2\', \'val3\')', - $this->insert->getSqlString(new TrustingSql92Platform()) + 'REPLACE INTO "foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', + $replace->getSqlString(new TrustingSql92Platform()), ); - } - - // @codingStandardsIgnoreStart - public function test__set(): void - { - // @codingStandardsIgnoreEnd - /** @psalm-suppress UndefinedMagicPropertyAssignment */ - $this->insert->foo = 'bar'; - self::assertEquals(['foo'], $this->insert->getRawState('columns')); - self::assertEquals(['bar'], $this->insert->getRawState('values')); - } - - // @codingStandardsIgnoreStart - public function test__unset(): void - { - // @codingStandardsIgnoreEnd - /** @psalm-suppress UndefinedMagicPropertyAssignment */ - $this->insert->foo = 'bar'; - self::assertEquals(['foo'], $this->insert->getRawState('columns')); - self::assertEquals(['bar'], $this->insert->getRawState('values')); - unset($this->insert->foo); - self::assertEquals([], $this->insert->getRawState('columns')); - self::assertEquals([], $this->insert->getRawState('values')); - - /** @psalm-suppress UndefinedMagicPropertyAssignment */ - $this->insert->foo = null; - self::assertEquals(['foo'], $this->insert->getRawState('columns')); - self::assertEquals([null], $this->insert->getRawState('values')); - - unset($this->insert->foo); - self::assertEquals([], $this->insert->getRawState('columns')); - self::assertEquals([], $this->insert->getRawState('values')); - } - // @codingStandardsIgnoreStart - public function test__isset(): void - { - // @codingStandardsIgnoreEnd - /** @psalm-suppress UndefinedMagicPropertyAssignment */ - $this->insert->foo = 'bar'; - /** @psalm-suppress RedundantCondition */ - self::assertTrue(isset($this->insert->foo)); - - /** @psalm-suppress UndefinedMagicPropertyAssignment */ - $this->insert->foo = null; - /** @psalm-suppress TypeDoesNotContainType */ - self::assertTrue(isset($this->insert->foo)); - } - - // @codingStandardsIgnoreStart - public function test__get(): void - { - // @codingStandardsIgnoreEnd - /** @psalm-suppress UndefinedMagicPropertyAssignment */ - $this->insert->foo = 'bar'; - self::assertEquals('bar', $this->insert->foo); - - /** @psalm-suppress UndefinedMagicPropertyAssignment */ - $this->insert->foo = null; - self::assertNull($this->insert->foo); - } - - #[Group('Laminas-536')] - public function testValuesMerge(): void - { - $this->insert->into('foo') + // with TableIdentifier + $replace = new Replace(); + $replace->into(new TableIdentifier('foo', 'sch')) ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); - $this->insert->into('foo') - ->values(['qux' => 100], Insert::VALUES_MERGE); self::assertEquals( - 'INSERT INTO "foo" ("bar", "boo", "bam", "qux") VALUES (\'baz\', NOW(), NULL, \'100\')', - $this->insert->getSqlString(new TrustingSql92Platform()) + 'REPLACE INTO "sch"."foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', + $replace->getSqlString(new TrustingSql92Platform()), ); } - public function testGetSqlStringThrowsExceptionWhenNoValuesOrSelect(): void - { - $this->insert->into('foo'); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('values or select should be present'); - $this->insert->getSqlString(new TrustingSql92Platform()); - } - - public function testUnsetThrowsExceptionForNonExistentColumn(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The key nonexistent was not found in this objects column list'); - unset($this->insert->nonexistent); - } - - public function testGetThrowsExceptionForNonExistentColumn(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The key nonexistent was not found in this objects column list'); - $value = $this->insert->nonexistent; - } - #[CoversNothing] public function testSpecificationconstantsCouldBeOverridedByExtensionInPrepareStatement(): void { @@ -391,75 +385,78 @@ public function testSpecificationconstantsCouldBeOverridedByExtensionInPrepareSt $replace->prepareStatement($mockAdapter, $mockStatement); } - #[CoversNothing] - public function testSpecificationconstantsCouldBeOverridedByExtensionInGetSqlString(): void + public function testUnsetThrowsExceptionForNonExistentColumn(): void { - $replace = new Replace(); - $replace->into('foo') - ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The key nonexistent was not found in this objects column list'); + unset($this->insert->nonexistent); + } - self::assertEquals( - 'REPLACE INTO "foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', - $replace->getSqlString(new TrustingSql92Platform()) - ); + public function testValues(): void + { + $this->insert->values(['foo' => 'bar']); + self::assertEquals(['foo'], $this->insert->getRawState('columns')); + self::assertEquals(['bar'], $this->insert->getRawState('values')); - // with TableIdentifier - $replace = new Replace(); - $replace->into(new TableIdentifier('foo', 'sch')) - ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); + // test will merge cols and values of previously set stuff + $this->insert->values(['foo' => 'bax'], Insert::VALUES_MERGE); + $this->insert->values(['boom' => 'bam'], Insert::VALUES_MERGE); + self::assertEquals(['foo', 'boom'], $this->insert->getRawState('columns')); + self::assertEquals(['bax', 'bam'], $this->insert->getRawState('values')); - self::assertEquals( - 'REPLACE INTO "sch"."foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', - $replace->getSqlString(new TrustingSql92Platform()) - ); + $this->insert->values(['foo' => 'bax']); + self::assertEquals(['foo'], $this->insert->getRawState('columns')); + self::assertEquals(['bax'], $this->insert->getRawState('values')); } - public function testPrepareStatementCreatesParameterContainerWhenNotPresent(): void + #[Group('Laminas-536')] + public function testValuesMerge(): void { - $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); - $mockDriver->expects($this->any())->method('getPrepareType')->willReturn('positional'); - $mockDriver->expects($this->any())->method('formatParameterName')->willReturn('?'); - $mockAdapter = $this->createMockAdapter($mockDriver); - - $mockStatement = $this->getMockBuilder(StatementInterface::class)->getMock(); - $mockStatement->expects($this->once()) - ->method('getParameterContainer') - ->willReturn(null); - $mockStatement->expects($this->once()) - ->method('setParameterContainer') - ->with($this->isInstanceOf(ParameterContainer::class)) - ->willReturnSelf(); - $mockStatement->expects($this->once()) - ->method('setSql') - ->with($this->stringContains('INSERT INTO')) - ->willReturnSelf(); - + $this->insert->into('foo')->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); $this->insert->into('foo') - ->values(['bar' => 'baz']); - - $result = $this->insert->prepareStatement($mockAdapter, $mockStatement); + ->values(['qux' => 100], Insert::VALUES_MERGE); - self::assertSame($mockStatement, $result); + self::assertEquals( + 'INSERT INTO "foo" ("bar", "boo", "bam", "qux") VALUES (\'baz\', NOW(), NULL, \'100\')', + $this->insert->getSqlString(new TrustingSql92Platform()), + ); } - public function testProcessInsertWithPdoDriverUsesDriverColumnQuoting(): void + public function testValuesThrowsExceptionWhenArrayMergeOverSelect(): void { - $mockDriver = $this->getMockBuilder(PdoDriverInterface::class)->getMock(); - $mockDriver->expects($this->any())->method('getPrepareType')->willReturn('positional'); - $mockDriver->expects($this->any()) - ->method('formatParameterName') - ->willReturnCallback(fn(string $name): string => ':' . $name); - $mockAdapter = $this->createMockAdapter($mockDriver); + $this->insert->values(new Select()); - $mockStatement = new StatementContainer(); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage( + 'An array of values cannot be provided with the merge flag when a PhpDb\Sql\Select instance already ' + . 'exists as the value source', + ); + $this->insert->values(['foo' => 'bar'], Insert::VALUES_MERGE); + } - $this->insert->into('foo') - ->values(['bar' => 'baz', 'boo' => 'qux']); + public function testValuesThrowsExceptionWhenNotArrayOrSelect(): void + { + $this->expectException(TypeError::class); + /** @psalm-suppress InvalidArgument */ + $this->insert->values(5); + } - $this->insert->prepareStatement($mockAdapter, $mockStatement); + public function testValuesThrowsExceptionWhenSelectMergeOverArray(): void + { + $this->insert->values(['foo' => 'bar']); - $sql = $mockStatement->getSql(); - self::assertStringContainsString(':c_0', $sql); - self::assertStringContainsString(':c_1', $sql); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('A PhpDb\Sql\Select instance cannot be provided with the merge flag'); + $this->insert->values(new Select(), Insert::VALUES_MERGE); + } + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + #[Override] + protected function setUp(): void + { + $this->insert = new Insert(); } } diff --git a/test/unit/Sql/JoinTest.php b/test/unit/Sql/JoinTest.php index 4faaa59b2..2936b4971 100644 --- a/test/unit/Sql/JoinTest.php +++ b/test/unit/Sql/JoinTest.php @@ -31,52 +31,15 @@ class JoinTest extends TestCase { use DeprecatedAssertionsTrait; - /** - * @throws ReflectionException - */ - public function testInitialPositionIsZero(): void - { - $join = new Join(); - - self::assertAttributeEquals(0, 'position', $join); - } - - /** - * @throws ReflectionException - */ - public function testNextIncrementsThePosition(): void - { - $join = new Join(); - - $join->next(); - - self::assertAttributeEquals(1, 'position', $join); - } - - /** - * @throws ReflectionException - */ - public function testRewindResetsPositionToZero(): void - { - $join = new Join(); - - $join->next(); - $join->next(); - self::assertAttributeEquals(2, 'position', $join); - - $join->rewind(); - self::assertAttributeEquals(0, 'position', $join); - } - - public function testKeyReturnsTheCurrentPosition(): void + #[TestDox('unit test: Test count() returns correct count')] + public function testCount(): void { $join = new Join(); + $join->join('baz', 'foo.fooId = baz.fooId', Join::JOIN_LEFT); + $join->join('bar', 'foo.fooId = bar.fooId', Join::JOIN_LEFT); - $join->next(); - $join->next(); - $join->next(); - - self::assertEquals(3, $join->key()); + self::assertEquals(2, $join->count()); + self::assertCount($join->count(), $join->getJoins()); } public function testCurrentReturnsTheCurrentJoinSpecification(): void @@ -97,16 +60,14 @@ public function testCurrentReturnsTheCurrentJoinSpecification(): void self::assertEquals($expectedSpecification, $join->current()); } - public function testValidReturnsTrueIfTheIteratorIsAtAValidPositionAndFalseIfNot(): void + /** + * @throws ReflectionException + */ + public function testInitialPositionIsZero(): void { $join = new Join(); - $join->join('baz', 'foo.id = baz.id'); - - self::assertTrue($join->valid()); - $join->next(); - - self::assertFalse($join->valid()); + self::assertAttributeEquals(0, 'position', $join); } #[TestDox('unit test: Test join() returns Join object (is chainable)')] @@ -124,6 +85,15 @@ public function testJoinFullOuter(): void self::assertSame($join, $return); } + public function testJoinThrowsOnInvalidMultiElementArray(): void + { + $join = new Join(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("expects 'b' as a single element associative array"); + $join->join(['a' => 'b', 'c' => 'd'], 'on'); + } + public function testJoinWillThrowAnExceptionIfNameIsNoValid(): void { $join = new Join(); @@ -133,15 +103,27 @@ public function testJoinWillThrowAnExceptionIfNameIsNoValid(): void $join->join([], false); } - #[TestDox('unit test: Test count() returns correct count')] - public function testCount(): void + public function testKeyReturnsTheCurrentPosition(): void { $join = new Join(); - $join->join('baz', 'foo.fooId = baz.fooId', Join::JOIN_LEFT); - $join->join('bar', 'foo.fooId = bar.fooId', Join::JOIN_LEFT); - self::assertEquals(2, $join->count()); - self::assertCount($join->count(), $join->getJoins()); + $join->next(); + $join->next(); + $join->next(); + + self::assertEquals(3, $join->key()); + } + + /** + * @throws ReflectionException + */ + public function testNextIncrementsThePosition(): void + { + $join = new Join(); + + $join->next(); + + self::assertAttributeEquals(1, 'position', $join); } #[TestDox('unit test: Test reset() resets the joins')] @@ -155,12 +137,30 @@ public function testReset(): void self::assertEquals(0, $join->count()); } - public function testJoinThrowsOnInvalidMultiElementArray(): void + /** + * @throws ReflectionException + */ + public function testRewindResetsPositionToZero(): void { $join = new Join(); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("expects 'b' as a single element associative array"); - $join->join(['a' => 'b', 'c' => 'd'], 'on'); + $join->next(); + $join->next(); + self::assertAttributeEquals(2, 'position', $join); + + $join->rewind(); + self::assertAttributeEquals(0, 'position', $join); + } + + public function testValidReturnsTrueIfTheIteratorIsAtAValidPositionAndFalseIfNot(): void + { + $join = new Join(); + $join->join('baz', 'foo.id = baz.id'); + + self::assertTrue($join->valid()); + + $join->next(); + + self::assertFalse($join->valid()); } } diff --git a/test/unit/Sql/LiteralTest.php b/test/unit/Sql/LiteralTest.php index f0219da06..5d105311b 100644 --- a/test/unit/Sql/LiteralTest.php +++ b/test/unit/Sql/LiteralTest.php @@ -9,32 +9,6 @@ class LiteralTest extends TestCase { - public function testSetLiteral(): void - { - $literal = new Literal('bar'); - - // First mutation - $result = $literal->setLiteral('foo'); - - // Verify fluent interface - self::assertSame($literal, $result); - - // Verify the first mutation occurred - self::assertEquals('foo', $literal->getLiteral()); - - // Second mutation to verify mutability - $literal->setLiteral('baz'); - - // Verify the instance was actually mutated - self::assertEquals('baz', $literal->getLiteral()); - } - - public function testGetLiteral(): void - { - $literal = new Literal('bar'); - self::assertEquals('bar', $literal->getLiteral()); - } - public function testGetExpressionData(): void { $literal = new Literal('bar'); @@ -42,12 +16,12 @@ public function testGetExpressionData(): void self::assertEquals( 'bar', - $expressionData['spec'] + $expressionData['spec'], ); self::assertEquals( [], - $expressionData['values'] + $expressionData['values'], ); } @@ -58,12 +32,38 @@ public function testGetExpressionDataWillEscapePercent(): void self::assertEquals( 'X LIKE "foo%%"', - $expressionData['spec'] + $expressionData['spec'], ); self::assertEquals( [], - $expressionData['values'] + $expressionData['values'], ); } + + public function testGetLiteral(): void + { + $literal = new Literal('bar'); + self::assertEquals('bar', $literal->getLiteral()); + } + + public function testSetLiteral(): void + { + $literal = new Literal('bar'); + + // First mutation + $result = $literal->setLiteral('foo'); + + // Verify fluent interface + self::assertSame($literal, $result); + + // Verify the first mutation occurred + self::assertEquals('foo', $literal->getLiteral()); + + // Second mutation to verify mutability + $literal->setLiteral('baz'); + + // Verify the instance was actually mutated + self::assertEquals('baz', $literal->getLiteral()); + } } diff --git a/test/unit/Sql/Platform/AbstractPlatformTest.php b/test/unit/Sql/Platform/AbstractPlatformTest.php index fe5429e84..816018247 100644 --- a/test/unit/Sql/Platform/AbstractPlatformTest.php +++ b/test/unit/Sql/Platform/AbstractPlatformTest.php @@ -29,28 +29,38 @@ final class AbstractPlatformTest extends TestCase { private AbstractPlatform $platform; - protected function setUp(): void + public function testGetSqlStringDelegatesToDecoratorSubject(): void { - $this->platform = new AbstractPlatform(); + $select = new Select('foo'); + $this->platform->setSubject($select); + + $sql = $this->platform->getSqlString(); + + self::assertStringContainsString('SELECT', $sql); + self::assertStringContainsString('"foo"', $sql); } - public function testSetSubjectReturnsStatic(): void + public function testGetSqlStringThrowsWhenSubjectNotSqlInterface(): void { - $subject = $this->createMock(SqlInterface::class); - $result = $this->platform->setSubject($subject); + $subject = $this->createMock(PreparableSqlInterface::class); + $this->platform->setSubject($subject); - self::assertSame($this->platform, $result); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('The subject does not appear to implement'); + $this->platform->getSqlString(); } - public function testSetAndGetTypeDecorator(): void + public function testGetTypeDecoratorLoopMatchesByInstanceof(): void { $decorator = $this->createMock(PlatformDecoratorInterface::class); + $decorator->method('setSubject')->willReturnSelf(); + $this->platform->setTypeDecorator(Select::class, $decorator); - $decorators = $this->platform->getDecorators(); + $subject = new Select('foo'); + $result = $this->platform->getTypeDecorator($subject); - self::assertArrayHasKey(Select::class, $decorators); - self::assertSame($decorator, $decorators[Select::class]); + self::assertSame($decorator, $result); } public function testGetTypeDecoratorReturnsSubjectWhenNoMatch(): void @@ -62,17 +72,30 @@ public function testGetTypeDecoratorReturnsSubjectWhenNoMatch(): void self::assertSame($subject, $result); } - public function testGetTypeDecoratorLoopMatchesByInstanceof(): void + public function testPrepareStatementDelegatesToDecoratorSubject(): void { - $decorator = $this->createMock(PlatformDecoratorInterface::class); - $decorator->method('setSubject')->willReturnSelf(); + $select = new Select('foo'); + $select->where(['id' => 1]); + $this->platform->setSubject($select); - $this->platform->setTypeDecorator(Select::class, $decorator); + $mockPlatform = $this->createMock(PlatformInterface::class); + $mockPlatform->method('quoteIdentifier')->willReturnCallback(static fn($v) => '"' . $v . '"'); + $mockPlatform->method('quoteIdentifierInFragment')->willReturnCallback(static fn($v) => '"' . $v . '"'); + $mockPlatform->method('getIdentifierSeparator')->willReturn('.'); + $mockPlatform->method('getSqlPlatformDecorator')->willReturn($this->platform); - $subject = new Select('foo'); - $result = $this->platform->getTypeDecorator($subject); + $mockDriver = $this->createMock(DriverInterface::class); + $mockDriver->method('formatParameterName')->willReturn('?'); - self::assertSame($decorator, $result); + $adapter = $this->getMockBuilder(AdapterInterface::class)->getMock(); + $adapter->method('getPlatform')->willReturn($mockPlatform); + $adapter->method('getDriver')->willReturn($mockDriver); + + $statement = new StatementContainer(); + $result = $this->platform->prepareStatement($adapter, $statement); + + self::assertSame($statement, $result); + self::assertStringContainsString('SELECT', $statement->getSql()); } public function testPrepareStatementThrowsWhenSubjectNotPreparable(): void @@ -88,50 +111,27 @@ public function testPrepareStatementThrowsWhenSubjectNotPreparable(): void $this->platform->prepareStatement($adapter, $statement); } - public function testGetSqlStringThrowsWhenSubjectNotSqlInterface(): void + public function testSetAndGetTypeDecorator(): void { - $subject = $this->createMock(PreparableSqlInterface::class); - $this->platform->setSubject($subject); + $decorator = $this->createMock(PlatformDecoratorInterface::class); + $this->platform->setTypeDecorator(Select::class, $decorator); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The subject does not appear to implement'); - $this->platform->getSqlString(); + $decorators = $this->platform->getDecorators(); + + self::assertArrayHasKey(Select::class, $decorators); + self::assertSame($decorator, $decorators[Select::class]); } - public function testGetSqlStringDelegatesToDecoratorSubject(): void + public function testSetSubjectReturnsStatic(): void { - $select = new Select('foo'); - $this->platform->setSubject($select); - - $sql = $this->platform->getSqlString(); + $subject = $this->createMock(SqlInterface::class); + $result = $this->platform->setSubject($subject); - self::assertStringContainsString('SELECT', $sql); - self::assertStringContainsString('"foo"', $sql); + self::assertSame($this->platform, $result); } - public function testPrepareStatementDelegatesToDecoratorSubject(): void + protected function setUp(): void { - $select = new Select('foo'); - $select->where(['id' => 1]); - $this->platform->setSubject($select); - - $mockPlatform = $this->createMock(PlatformInterface::class); - $mockPlatform->method('quoteIdentifier')->willReturnCallback(fn($v) => '"' . $v . '"'); - $mockPlatform->method('quoteIdentifierInFragment')->willReturnCallback(fn($v) => '"' . $v . '"'); - $mockPlatform->method('getIdentifierSeparator')->willReturn('.'); - $mockPlatform->method('getSqlPlatformDecorator')->willReturn($this->platform); - - $mockDriver = $this->createMock(DriverInterface::class); - $mockDriver->method('formatParameterName')->willReturn('?'); - - $adapter = $this->getMockBuilder(AdapterInterface::class)->getMock(); - $adapter->method('getPlatform')->willReturn($mockPlatform); - $adapter->method('getDriver')->willReturn($mockDriver); - - $statement = new StatementContainer(); - $result = $this->platform->prepareStatement($adapter, $statement); - - self::assertSame($statement, $result); - self::assertStringContainsString('SELECT', $statement->getSql()); + $this->platform = new AbstractPlatform(); } } diff --git a/test/unit/Sql/Platform/PlatformTest.php b/test/unit/Sql/Platform/PlatformTest.php index ae6066dcb..21d1e1bca 100644 --- a/test/unit/Sql/Platform/PlatformTest.php +++ b/test/unit/Sql/Platform/PlatformTest.php @@ -40,80 +40,84 @@ #[CoversMethod(Platform::class, 'getDefaultPlatform')] class PlatformTest extends TestCase { - /** - * @throws ReflectionException - */ - public function testResolveDefaultPlatform(): void + public function testGetDefaultPlatformReturnsInstance(): void { - $adapter = $this->resolveAdapter('sql92'); - $platform = new Platform($adapter->getPlatform()); + $adapterPlatform = new TestAsset\TrustingSql92Platform(); + $platform = new Platform($adapterPlatform); - $reflectionMethod = new ReflectionMethod($platform, 'resolvePlatform'); + $reflectionMethod = new ReflectionMethod($platform, 'getDefaultPlatform'); + $result = $reflectionMethod->invoke($platform); - self::assertEquals($adapter->getPlatform(), $reflectionMethod->invoke($platform, null)); + self::assertSame($adapterPlatform, $result); } - /** - * @throws ReflectionException - */ - public function testResolvePlatformName(): void + public function testGetSqlStringDelegatesToTypeDecorator(): void { - $platform = new Platform($this->resolveAdapter('sql92')->getPlatform()); + $adapterPlatform = new TestAsset\TrustingSql92Platform(); + $platform = new Platform($adapterPlatform); - $reflectionMethod = new ReflectionMethod($platform, 'resolvePlatformName'); + $select = new Select('foo'); + $platform->setSubject($select); - self::assertEquals('mysql', $reflectionMethod->invoke($platform, new TestAsset\TrustingMysqlPlatform())); - self::assertEquals('sqlserver', $reflectionMethod->invoke( - $platform, - new TestAsset\TrustingSqlServerPlatform() - )); - self::assertEquals('oracle', $reflectionMethod->invoke($platform, new TestAsset\TrustingOraclePlatform())); - self::assertEquals('sql92', $reflectionMethod->invoke($platform, new TestAsset\TrustingSql92Platform())); + $sql = $platform->getSqlString($adapterPlatform); + + self::assertStringContainsString('SELECT', $sql); + self::assertStringContainsString('"foo"', $sql); } - protected function resolveAdapter(string $platformName): Adapter + public function testGetSqlStringThrowsWhenSubjectNotSqlInterface(): void { - $platform = null; + $adapterPlatform = new TestAsset\TrustingSql92Platform(); + $platform = new Platform($adapterPlatform); - switch ($platformName) { - case 'sql92': - $platform = new TestAsset\TrustingSql92Platform(); - break; - case 'MySql': - $platform = new TestAsset\TrustingMysqlPlatform(); - break; - case 'Oracle': - $platform = new TestAsset\TrustingOraclePlatform(); - break; - case 'SqlServer': - $platform = new TestAsset\TrustingSqlServerPlatform(); - break; - } + $subject = $this->createMock(PreparableSqlInterface::class); + $platform->setSubject($subject); - /** @var DriverInterface&MockObject $mockDriver */ - $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + $this->expectException(RuntimeException::class); + $platform->getSqlString($adapterPlatform); + } - $mockDriver->expects($this->any()) - ->method('formatParameterName') - ->willReturn('?'); - $mockDriver->expects($this->any()) - ->method('createStatement') - ->willReturnCallback(fn(): StatementContainer => new StatementContainer()); + public function testGetTypeDecoratorFallsThroughWhenNoMatch(): void + { + $adapterPlatform = new TestAsset\TrustingSql92Platform(); + $platform = new Platform($adapterPlatform); - return new Adapter($mockDriver, $platform, new ResultSet()); + $decorator = $this->createMock(PlatformDecoratorInterface::class); + $platform->setTypeDecorator(Insert::class, $decorator); + + $select = new Select('foo'); + $result = $platform->getTypeDecorator($select); + + self::assertSame($select, $result); } - public function testSetTypeDecoratorRegistersDecorator(): void + public function testGetTypeDecoratorMatchesByInstanceofLoop(): void + { + $adapterPlatform = new TestAsset\TrustingSql92Platform(); + $platform = new Platform($adapterPlatform); + + $innerPlatform = new AbstractPlatform(); + $platform->setTypeDecorator(SqlInterface::class, $innerPlatform); + + $select = new Select('foo'); + $result = $platform->getTypeDecorator($select); + + self::assertSame($innerPlatform, $result); + } + + public function testGetTypeDecoratorMatchesExactClass(): void { $adapterPlatform = new TestAsset\TrustingSql92Platform(); $platform = new Platform($adapterPlatform); $decorator = $this->createMock(PlatformDecoratorInterface::class); + $decorator->expects(self::once())->method('setSubject'); $platform->setTypeDecorator(Select::class, $decorator); - $decorators = $platform->getDecorators(); - self::assertArrayHasKey(Select::class, $decorators); - self::assertSame($decorator, $decorators[Select::class]); + $select = new Select('foo'); + $result = $platform->getTypeDecorator($select); + + self::assertSame($decorator, $result); } public function testGetTypeDecoratorReturnsSubjectWhenNoDecoratorRegistered(): void @@ -127,15 +131,50 @@ public function testGetTypeDecoratorReturnsSubjectWhenNoDecoratorRegistered(): v self::assertSame($select, $result); } - public function testGetDefaultPlatformReturnsInstance(): void + public function testPrepareStatementThrowsWhenSubjectNotPreparable(): void { $adapterPlatform = new TestAsset\TrustingSql92Platform(); $platform = new Platform($adapterPlatform); - $reflectionMethod = new ReflectionMethod($platform, 'getDefaultPlatform'); - $result = $reflectionMethod->invoke($platform); + $subject = $this->createMock(SqlInterface::class); + $platform->setSubject($subject); - self::assertSame($adapterPlatform, $result); + $adapter = $this->resolveAdapter('sql92'); + $statement = new StatementContainer(); + + $this->expectException(RuntimeException::class); + $platform->prepareStatement($adapter, $statement); + } + + /** + * @throws ReflectionException + */ + public function testResolveDefaultPlatform(): void + { + $adapter = $this->resolveAdapter('sql92'); + $platform = new Platform($adapter->getPlatform()); + + $reflectionMethod = new ReflectionMethod($platform, 'resolvePlatform'); + + self::assertEquals($adapter->getPlatform(), $reflectionMethod->invoke($platform, null)); + } + + /** + * @throws ReflectionException + */ + public function testResolvePlatformName(): void + { + $platform = new Platform($this->resolveAdapter('sql92')->getPlatform()); + + $reflectionMethod = new ReflectionMethod($platform, 'resolvePlatformName'); + + self::assertEquals('mysql', $reflectionMethod->invoke($platform, new TestAsset\TrustingMysqlPlatform())); + self::assertEquals('sqlserver', $reflectionMethod->invoke( + $platform, + new TestAsset\TrustingSqlServerPlatform(), + )); + self::assertEquals('oracle', $reflectionMethod->invoke($platform, new TestAsset\TrustingOraclePlatform())); + self::assertEquals('sql92', $reflectionMethod->invoke($platform, new TestAsset\TrustingSql92Platform())); } /** @@ -185,87 +224,48 @@ public function testResolvePlatformWithPlatformInterface(): void self::assertSame($mockPlatform, $result); } - public function testPrepareStatementThrowsWhenSubjectNotPreparable(): void - { - $adapterPlatform = new TestAsset\TrustingSql92Platform(); - $platform = new Platform($adapterPlatform); - - $subject = $this->createMock(SqlInterface::class); - $platform->setSubject($subject); - - $adapter = $this->resolveAdapter('sql92'); - $statement = new StatementContainer(); - - $this->expectException(RuntimeException::class); - $platform->prepareStatement($adapter, $statement); - } - - public function testGetSqlStringThrowsWhenSubjectNotSqlInterface(): void - { - $adapterPlatform = new TestAsset\TrustingSql92Platform(); - $platform = new Platform($adapterPlatform); - - $subject = $this->createMock(PreparableSqlInterface::class); - $platform->setSubject($subject); - - $this->expectException(RuntimeException::class); - $platform->getSqlString($adapterPlatform); - } - - public function testGetSqlStringDelegatesToTypeDecorator(): void - { - $adapterPlatform = new TestAsset\TrustingSql92Platform(); - $platform = new Platform($adapterPlatform); - - $select = new Select('foo'); - $platform->setSubject($select); - - $sql = $platform->getSqlString($adapterPlatform); - - self::assertStringContainsString('SELECT', $sql); - self::assertStringContainsString('"foo"', $sql); - } - - public function testGetTypeDecoratorMatchesExactClass(): void + public function testSetTypeDecoratorRegistersDecorator(): void { $adapterPlatform = new TestAsset\TrustingSql92Platform(); $platform = new Platform($adapterPlatform); $decorator = $this->createMock(PlatformDecoratorInterface::class); - $decorator->expects(self::once())->method('setSubject'); $platform->setTypeDecorator(Select::class, $decorator); - $select = new Select('foo'); - $result = $platform->getTypeDecorator($select); - - self::assertSame($decorator, $result); + $decorators = $platform->getDecorators(); + self::assertArrayHasKey(Select::class, $decorators); + self::assertSame($decorator, $decorators[Select::class]); } - public function testGetTypeDecoratorFallsThroughWhenNoMatch(): void + protected function resolveAdapter(string $platformName): Adapter { - $adapterPlatform = new TestAsset\TrustingSql92Platform(); - $platform = new Platform($adapterPlatform); - - $decorator = $this->createMock(PlatformDecoratorInterface::class); - $platform->setTypeDecorator(Insert::class, $decorator); - - $select = new Select('foo'); - $result = $platform->getTypeDecorator($select); - - self::assertSame($select, $result); - } + $platform = null; - public function testGetTypeDecoratorMatchesByInstanceofLoop(): void - { - $adapterPlatform = new TestAsset\TrustingSql92Platform(); - $platform = new Platform($adapterPlatform); + switch ($platformName) { + case 'sql92': + $platform = new TestAsset\TrustingSql92Platform(); + break; + case 'MySql': + $platform = new TestAsset\TrustingMysqlPlatform(); + break; + case 'Oracle': + $platform = new TestAsset\TrustingOraclePlatform(); + break; + case 'SqlServer': + $platform = new TestAsset\TrustingSqlServerPlatform(); + break; + } - $innerPlatform = new AbstractPlatform(); - $platform->setTypeDecorator(SqlInterface::class, $innerPlatform); + /** @var DriverInterface&MockObject $mockDriver */ + $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); - $select = new Select('foo'); - $result = $platform->getTypeDecorator($select); + $mockDriver->expects($this->any()) + ->method('formatParameterName') + ->willReturn('?'); + $mockDriver->expects($this->any()) + ->method('createStatement') + ->willReturnCallback(static fn(): StatementContainer => new StatementContainer()); - self::assertSame($innerPlatform, $result); + return new Adapter($mockDriver, $platform, new ResultSet()); } } diff --git a/test/unit/Sql/Predicate/BetweenTest.php b/test/unit/Sql/Predicate/BetweenTest.php index c00ece8c5..a7e1a5089 100644 --- a/test/unit/Sql/Predicate/BetweenTest.php +++ b/test/unit/Sql/Predicate/BetweenTest.php @@ -27,19 +27,6 @@ final class BetweenTest extends TestCase { protected Between $between; - #[Override] - protected function setUp(): void - { - $this->between = new Between(); - } - - public function testConstructorYieldsNullIdentifierMinimumAndMaximumValues(): void - { - self::assertNull($this->between->getIdentifier()); - self::assertNull($this->between->getMinValue()); - self::assertNull($this->between->getMaxValue()); - } - public function testConstructorCanPassIdentifierMinimumAndMaximumValues(): void { $between = new Between('foo.bar', 1, 300); @@ -94,9 +81,41 @@ public function testConstructorCanPassIdentifierMinimumAndMaximumValues(): void self::assertEquals(ArgumentType::Value, $maxValue->getType()); } - public function testSpecificationIsNullByDefault(): void + public function testConstructorYieldsNullIdentifierMinimumAndMaximumValues(): void { - self::assertNull($this->between->getSpecification()); + self::assertNull($this->between->getIdentifier()); + self::assertNull($this->between->getMinValue()); + self::assertNull($this->between->getMaxValue()); + } + + public function testGetExpressionDataThrowsExceptionWhenIdentifierNotSet(): void + { + $between = new Between(); + $between->setMinValue(1)->setMaxValue(10); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Identifier must be specified'); + $between->getExpressionData(); + } + + public function testGetExpressionDataThrowsExceptionWhenMaxValueNotSet(): void + { + $between = new Between(); + $between->setIdentifier('foo')->setMinValue(1); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('maxValue must be specified'); + $between->getExpressionData(); + } + + public function testGetExpressionDataThrowsExceptionWhenMinValueNotSet(): void + { + $between = new Between(); + $between->setIdentifier('foo')->setMaxValue(10); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('minValue must be specified'); + $between->getExpressionData(); } public function testIdentifierIsMutable(): void @@ -123,30 +142,6 @@ public function testIdentifierIsMutable(): void self::assertEquals(ArgumentType::Identifier, $identifier2->getType()); } - public function testMinValueIsMutable(): void - { - // First mutation - $result = $this->between->setMinValue(10); - - // Verify fluent interface - self::assertSame($this->between, $result); - - // Verify the first mutation occurred - $minValue1 = $this->between->getMinValue(); - self::assertInstanceOf(ArgumentInterface::class, $minValue1); - self::assertEquals(10, $minValue1->getValue()); - self::assertEquals(ArgumentType::Value, $minValue1->getType()); - - // Second mutation with different data to verify mutability - $this->between->setMinValue(20); - - // Verify the instance was actually mutated - $minValue2 = $this->between->getMinValue(); - self::assertInstanceOf(ArgumentInterface::class, $minValue2); - self::assertEquals(20, $minValue2->getValue()); - self::assertEquals(ArgumentType::Value, $minValue2->getType()); - } - public function testMaxValueIsMutable(): void { // First mutation @@ -171,17 +166,36 @@ public function testMaxValueIsMutable(): void self::assertEquals(ArgumentType::Value, $maxValue2->getType()); } - public function testSpecificationIsMutable(): void + public function testMinValueIsMutable(): void { - $this->between->setSpecification('%1$s IS INBETWEEN %2$s AND %3$s'); - self::assertEquals('%1$s IS INBETWEEN %2$s AND %3$s', $this->between->getSpecification()); + // First mutation + $result = $this->between->setMinValue(10); + + // Verify fluent interface + self::assertSame($this->between, $result); + + // Verify the first mutation occurred + $minValue1 = $this->between->getMinValue(); + self::assertInstanceOf(ArgumentInterface::class, $minValue1); + self::assertEquals(10, $minValue1->getValue()); + self::assertEquals(ArgumentType::Value, $minValue1->getType()); + + // Second mutation with different data to verify mutability + $this->between->setMinValue(20); + + // Verify the instance was actually mutated + $minValue2 = $this->between->getMinValue(); + self::assertInstanceOf(ArgumentInterface::class, $minValue2); + self::assertEquals(20, $minValue2->getValue()); + self::assertEquals(ArgumentType::Value, $minValue2->getType()); } public function testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAndValuesAndArrayOfTypes(): void { - $this->between->setIdentifier('foo.bar') - ->setMinValue(10) - ->setMaxValue(19); + $this->between + ->setIdentifier('foo.bar') + ->setMinValue(10) + ->setMaxValue(19); $expressionData = $this->between->getExpressionData(); @@ -207,9 +221,10 @@ public function testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAnd self::assertEquals(19, $values[2]->getValue()); self::assertEquals(ArgumentType::Value, $values[2]->getType()); - $this->between->setIdentifier(Argument::value(10)) - ->setMinValue(Argument::identifier('foo.bar')) - ->setMaxValue(Argument::identifier('foo.baz')); + $this->between + ->setIdentifier(Argument::value(10)) + ->setMinValue(Argument::identifier('foo.bar')) + ->setMaxValue(Argument::identifier('foo.baz')); $expressionData = $this->between->getExpressionData(); @@ -236,33 +251,20 @@ public function testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAnd self::assertEquals(ArgumentType::Identifier, $values[2]->getType()); } - public function testGetExpressionDataThrowsExceptionWhenIdentifierNotSet(): void + public function testSpecificationIsMutable(): void { - $between = new Between(); - $between->setMinValue(1)->setMaxValue(10); - - $this->expectException(LogicException::class); - $this->expectExceptionMessage('Identifier must be specified'); - $between->getExpressionData(); + $this->between->setSpecification('%1$s IS INBETWEEN %2$s AND %3$s'); + self::assertEquals('%1$s IS INBETWEEN %2$s AND %3$s', $this->between->getSpecification()); } - public function testGetExpressionDataThrowsExceptionWhenMinValueNotSet(): void + public function testSpecificationIsNullByDefault(): void { - $between = new Between(); - $between->setIdentifier('foo')->setMaxValue(10); - - $this->expectException(LogicException::class); - $this->expectExceptionMessage('minValue must be specified'); - $between->getExpressionData(); + self::assertNull($this->between->getSpecification()); } - public function testGetExpressionDataThrowsExceptionWhenMaxValueNotSet(): void + #[Override] + protected function setUp(): void { - $between = new Between(); - $between->setIdentifier('foo')->setMinValue(1); - - $this->expectException(LogicException::class); - $this->expectExceptionMessage('maxValue must be specified'); - $between->getExpressionData(); + $this->between = new Between(); } } diff --git a/test/unit/Sql/Predicate/ExpressionTest.php b/test/unit/Sql/Predicate/ExpressionTest.php index f82cbd274..dbe741ad4 100644 --- a/test/unit/Sql/Predicate/ExpressionTest.php +++ b/test/unit/Sql/Predicate/ExpressionTest.php @@ -16,63 +16,64 @@ final class ExpressionTest extends TestCase { - public function testEmptyConstructorYieldsEmptyLiteralAndParameter(): void + #[Group('6849')] + public function testCanPassArrayOfMultiNullsParameterToConstructor(): void { - $expression = new Expression(); - self::assertEquals('', $expression->getExpression()); - self::assertEmpty($expression->getParameters()); + $expression = new Expression('? OR ?', [null, null]); + $null = new Value(null); + self::assertEquals([$null, $null], $expression->getParameters()); } #[Group('6849')] - public function testCanPassLiteralAndSingleScalarParameterToConstructor(): void + public function testCanPassArrayOfMultiPredicatesParameterToConstructor(): void { - $expression = new Expression('foo.bar = ?', 'bar'); - $bar = new Value('bar'); - self::assertEquals('foo.bar = ?', $expression->getExpression()); - self::assertEquals([$bar], $expression->getParameters()); + $predicate = new IsNull('foo.baz'); + $expression = new Expression('? OR ?', [$predicate, $predicate]); + $isNull = new Select($predicate); + self::assertEquals([$isNull, $isNull], $expression->getParameters()); } #[Group('6849')] - public function testCanPassNoParameterToConstructor(): void + public function testCanPassArrayOfMultiScalarsParameterToConstructor(): void { - $expression = new Expression('foo.bar'); - self::assertEquals([], $expression->getParameters()); + $expression = new Expression('? OR ?', ['foo', 'bar']); + $foo = new Value('foo'); + $bar = new Value('bar'); + self::assertEquals([$foo, $bar], $expression->getParameters()); } #[Group('6849')] - public function testCanPassSingleNullParameterToConstructor(): void + public function testCanPassArrayOfOneNullParameterToConstructor(): void { - $expression = new Expression('?', null); + $expression = new Expression('?', [null]); $null = new Value(null); self::assertEquals([$null], $expression->getParameters()); } #[Group('6849')] - public function testCanPassSingleZeroParameterValueToConstructor(): void - { - $predicate = new Expression('?', 0); - $expression = new Value(0); - self::assertEquals([$expression], $predicate->getParameters()); - } - - #[Group('6849')] - public function testCanPassSinglePredicateParameterToConstructor(): void + public function testCanPassArrayOfOnePredicateParameterToConstructor(): void { $predicate = new IsNull('foo.baz'); - $expression = new Expression('?', $predicate); + $expression = new Expression('?', [$predicate]); $isNull = new Select($predicate); self::assertEquals([$isNull], $expression->getParameters()); } #[Group('6849')] - public function testCanPassMultiScalarParametersToConstructor(): void + public function testCanPassArrayOfOneScalarParameterToConstructor(): void { - /** @psalm-suppress TooManyArguments */ - $expression = new Expression('? OR ?', 'foo', 'bar'); + $expression = new Expression('?', ['foo']); $foo = new Value('foo'); - $bar = new Value('bar'); + self::assertEquals([$foo], $expression->getParameters()); + } - self::assertEquals([$foo, $bar], $expression->getParameters()); + #[Group('6849')] + public function testCanPassLiteralAndSingleScalarParameterToConstructor(): void + { + $expression = new Expression('foo.bar = ?', 'bar'); + $bar = new Value('bar'); + self::assertEquals('foo.bar = ?', $expression->getExpression()); + self::assertEquals([$bar], $expression->getParameters()); } #[Group('6849')] @@ -86,54 +87,53 @@ public function testCanPassMultiNullParametersToConstructor(): void } #[Group('6849')] - public function testCanPassArrayOfOneScalarParameterToConstructor(): void - { - $expression = new Expression('?', ['foo']); - $foo = new Value('foo'); - self::assertEquals([$foo], $expression->getParameters()); - } - - #[Group('6849')] - public function testCanPassArrayOfMultiScalarsParameterToConstructor(): void + public function testCanPassMultiScalarParametersToConstructor(): void { - $expression = new Expression('? OR ?', ['foo', 'bar']); + /** @psalm-suppress TooManyArguments */ + $expression = new Expression('? OR ?', 'foo', 'bar'); $foo = new Value('foo'); $bar = new Value('bar'); + self::assertEquals([$foo, $bar], $expression->getParameters()); } #[Group('6849')] - public function testCanPassArrayOfOneNullParameterToConstructor(): void + public function testCanPassNoParameterToConstructor(): void { - $expression = new Expression('?', [null]); - $null = new Value(null); - self::assertEquals([$null], $expression->getParameters()); + $expression = new Expression('foo.bar'); + self::assertEquals([], $expression->getParameters()); } #[Group('6849')] - public function testCanPassArrayOfMultiNullsParameterToConstructor(): void + public function testCanPassSingleNullParameterToConstructor(): void { - $expression = new Expression('? OR ?', [null, null]); + $expression = new Expression('?', null); $null = new Value(null); - self::assertEquals([$null, $null], $expression->getParameters()); + self::assertEquals([$null], $expression->getParameters()); } #[Group('6849')] - public function testCanPassArrayOfOnePredicateParameterToConstructor(): void + public function testCanPassSinglePredicateParameterToConstructor(): void { $predicate = new IsNull('foo.baz'); - $expression = new Expression('?', [$predicate]); + $expression = new Expression('?', $predicate); $isNull = new Select($predicate); self::assertEquals([$isNull], $expression->getParameters()); } #[Group('6849')] - public function testCanPassArrayOfMultiPredicatesParameterToConstructor(): void + public function testCanPassSingleZeroParameterValueToConstructor(): void { - $predicate = new IsNull('foo.baz'); - $expression = new Expression('? OR ?', [$predicate, $predicate]); - $isNull = new Select($predicate); - self::assertEquals([$isNull, $isNull], $expression->getParameters()); + $predicate = new Expression('?', 0); + $expression = new Value(0); + self::assertEquals([$expression], $predicate->getParameters()); + } + + public function testEmptyConstructorYieldsEmptyLiteralAndParameter(): void + { + $expression = new Expression(); + self::assertEquals('', $expression->getExpression()); + self::assertEmpty($expression->getParameters()); } public function testLiteralIsMutable(): void @@ -187,8 +187,7 @@ public function testParameterIsMutable(): void public function testRetrievingWherePartsReturnsSpecificationArrayOfLiteralAndParametersAndArrayOfTypes(): void { $expression = new Expression(); - $expression - ->setExpression('foo.bar = ? AND id != ?') + $expression->setExpression('foo.bar = ? AND id != ?') ->setParameters(['foo', 'bar']); $parameter1 = new Value('foo'); diff --git a/test/unit/Sql/Predicate/InTest.php b/test/unit/Sql/Predicate/InTest.php index 691eacdc8..cd73bb90d 100644 --- a/test/unit/Sql/Predicate/InTest.php +++ b/test/unit/Sql/Predicate/InTest.php @@ -25,16 +25,9 @@ #[Group('unit')] final class InTest extends TestCase { - public function testEmptyConstructorYieldsNullIdentifierAndValueSet(): void - { - $in = new In(); - self::assertNull($in->getIdentifier()); - self::assertNull($in->getValueSet()); - } - - public function testCanPassIdentifierAndValueSetToConstructor(): void + public function testCanPassIdentifierAndEmptyValueSetToConstructor(): void { - $in = new In('foo.bar', [1, 2]); + $in = new In('foo.bar', []); // Verify identifier was set correctly $identifier = $in->getIdentifier(); @@ -42,16 +35,16 @@ public function testCanPassIdentifierAndValueSetToConstructor(): void self::assertEquals('foo.bar', $identifier->getValue()); self::assertEquals(ArgumentType::Identifier, $identifier->getType()); - // Verify value set was set correctly + // Verify empty value set was set correctly $valueSet = $in->getValueSet(); self::assertInstanceOf(ArgumentInterface::class, $valueSet); - self::assertEquals([1, 2], $valueSet->getValue()); + self::assertEquals([], $valueSet->getValue()); self::assertEquals(ArgumentType::Values, $valueSet->getType()); } - public function testCanPassIdentifierAndEmptyValueSetToConstructor(): void + public function testCanPassIdentifierAndValueSetToConstructor(): void { - $in = new In('foo.bar', []); + $in = new In('foo.bar', [1, 2]); // Verify identifier was set correctly $identifier = $in->getIdentifier(); @@ -59,126 +52,104 @@ public function testCanPassIdentifierAndEmptyValueSetToConstructor(): void self::assertEquals('foo.bar', $identifier->getValue()); self::assertEquals(ArgumentType::Identifier, $identifier->getType()); - // Verify empty value set was set correctly + // Verify value set was set correctly $valueSet = $in->getValueSet(); self::assertInstanceOf(ArgumentInterface::class, $valueSet); - self::assertEquals([], $valueSet->getValue()); + self::assertEquals([1, 2], $valueSet->getValue()); self::assertEquals(ArgumentType::Values, $valueSet->getType()); } - public function testIdentifierIsMutable(): void + public function testEmptyConstructorYieldsNullIdentifierAndValueSet(): void { $in = new In(); + self::assertNull($in->getIdentifier()); + self::assertNull($in->getValueSet()); + } - // First mutation - $result = $in->setIdentifier('foo.bar'); - - // Verify fluent interface - self::assertSame($in, $result); - - // Verify the first mutation occurred - $identifier1 = $in->getIdentifier(); - self::assertInstanceOf(ArgumentInterface::class, $identifier1); - self::assertEquals('foo.bar', $identifier1->getValue()); - self::assertEquals(ArgumentType::Identifier, $identifier1->getType()); - - // Second mutation with different data to verify mutability - $in->setIdentifier('baz.qux'); + public function testGetExpressionDataThrowsExceptionWhenIdentifierNotSet(): void + { + $in = new In(); + $in->setValueSet([1, 2]); - // Verify the instance was actually mutated - $identifier2 = $in->getIdentifier(); - self::assertInstanceOf(ArgumentInterface::class, $identifier2); - self::assertEquals('baz.qux', $identifier2->getValue()); - self::assertEquals(ArgumentType::Identifier, $identifier2->getType()); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Identifier must be specified'); + $in->getExpressionData(); } - public function testValueSetIsMutable(): void + public function testGetExpressionDataThrowsExceptionWhenValueSetNotSet(): void { $in = new In(); + $in->setIdentifier('foo'); - // First mutation - $result = $in->setValueSet([1, 2]); - - // Verify fluent interface - self::assertSame($in, $result); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Value set must be provided for IN predicate'); + $in->getExpressionData(); + } - // Verify the first mutation occurred - $valueSet1 = $in->getValueSet(); - self::assertInstanceOf(ArgumentInterface::class, $valueSet1); - self::assertEquals([1, 2], $valueSet1->getValue()); - self::assertEquals(ArgumentType::Values, $valueSet1->getType()); + public function testGetExpressionDataWithEmptyValues(): void + { + new Select(); + $in = new In('foo', []); - // Second mutation with different data to verify mutability - $in->setValueSet([3, 4, 5]); + $expressionData = $in->getExpressionData(); - // Verify the instance was actually mutated - $valueSet2 = $in->getValueSet(); - self::assertInstanceOf(ArgumentInterface::class, $valueSet2); - self::assertEquals([3, 4, 5], $valueSet2->getValue()); - self::assertEquals(ArgumentType::Values, $valueSet2->getType()); + self::assertEquals('%s IN (NULL)', $expressionData['spec']); } - public function testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAndValuesAndArrayOfTypes(): void + public function testGetExpressionDataWithSubselect(): void { - $in = new In(); - $in->setIdentifier('foo.bar') - ->setValueSet([1, 2, 3]); + $select = new Select(); + $in = new In(Argument::value('foo'), $select); $expressionData = $in->getExpressionData(); // Verify specification - self::assertEquals('%s IN (%s, %s, %s)', $expressionData['spec']); + self::assertEquals('%s IN %s', $expressionData['spec']); // Verify expression values $values = $expressionData['values']; self::assertCount(2, $values); - // Verify identifier argument + // Verify value argument (passed as value type) self::assertInstanceOf(ArgumentInterface::class, $values[0]); - self::assertEquals('foo.bar', $values[0]->getValue()); - self::assertEquals(ArgumentType::Identifier, $values[0]->getType()); + self::assertEquals('foo', $values[0]->getValue()); + self::assertEquals(ArgumentType::Value, $values[0]->getType()); - // Verify value set argument + // Verify subselect argument self::assertInstanceOf(ArgumentInterface::class, $values[1]); - self::assertEquals([1, 2, 3], $values[1]->getValue()); - self::assertEquals(ArgumentType::Values, $values[1]->getType()); + self::assertSame($select, $values[1]->getValue()); + self::assertEquals(ArgumentType::Select, $values[1]->getType()); + } - // Test with typed value sets - $in->setIdentifier('foo.bar') - ->setValueSet([ - [1 => ArgumentType::Literal], - [2 => ArgumentType::Value], - [3 => ArgumentType::Literal], - ]); + public function testGetExpressionDataWithSubselectAndArrayIdentifier(): void + { + $select = new Select(); + $in = new In(Argument::identifiers(['foo', 'bar']), $select); $expressionData = $in->getExpressionData(); // Verify specification - self::assertEquals('%s IN (%s, %s, %s)', $expressionData['spec']); + self::assertEquals('(%s, %s) IN %s', $expressionData['spec']); // Verify expression values $values = $expressionData['values']; self::assertCount(2, $values); - // Verify identifier argument + // Verify array identifiers argument self::assertInstanceOf(ArgumentInterface::class, $values[0]); - self::assertEquals('foo.bar', $values[0]->getValue()); - self::assertEquals(ArgumentType::Identifier, $values[0]->getType()); + self::assertEquals(['foo', 'bar'], $values[0]->getValue()); + self::assertEquals(ArgumentType::Identifiers, $values[0]->getType()); - // Verify value set argument with types + // Verify subselect argument self::assertInstanceOf(ArgumentInterface::class, $values[1]); - self::assertEquals([ - [1 => ArgumentType::Literal], - [2 => ArgumentType::Value], - [3 => ArgumentType::Literal], - ], $values[1]->getValue()); - self::assertEquals(ArgumentType::Values, $values[1]->getType()); + self::assertSame($select, $values[1]->getValue()); + self::assertEquals(ArgumentType::Select, $values[1]->getType()); } - public function testGetExpressionDataWithSubselect(): void + public function testGetExpressionDataWithSubselectAndIdentifier(): void { $select = new Select(); - $in = new In(Argument::value('foo'), $select); + $in = new In(Argument::identifier('foo'), $select); $expressionData = $in->getExpressionData(); @@ -189,10 +160,10 @@ public function testGetExpressionDataWithSubselect(): void $values = $expressionData['values']; self::assertCount(2, $values); - // Verify value argument (passed as value type) + // Verify identifier argument self::assertInstanceOf(ArgumentInterface::class, $values[0]); self::assertEquals('foo', $values[0]->getValue()); - self::assertEquals(ArgumentType::Value, $values[0]->getType()); + self::assertEquals(ArgumentType::Identifier, $values[0]->getType()); // Verify subselect argument self::assertInstanceOf(ArgumentInterface::class, $values[1]); @@ -200,25 +171,42 @@ public function testGetExpressionDataWithSubselect(): void self::assertEquals(ArgumentType::Select, $values[1]->getType()); } - public function testGetExpressionDataWithEmptyValues(): void + public function testIdentifierIsMutable(): void { - new Select(); - $in = new In('foo', []); + $in = new In(); - $expressionData = $in->getExpressionData(); + // First mutation + $result = $in->setIdentifier('foo.bar'); - self::assertEquals('%s IN (NULL)', $expressionData['spec']); + // Verify fluent interface + self::assertSame($in, $result); + + // Verify the first mutation occurred + $identifier1 = $in->getIdentifier(); + self::assertInstanceOf(ArgumentInterface::class, $identifier1); + self::assertEquals('foo.bar', $identifier1->getValue()); + self::assertEquals(ArgumentType::Identifier, $identifier1->getType()); + + // Second mutation with different data to verify mutability + $in->setIdentifier('baz.qux'); + + // Verify the instance was actually mutated + $identifier2 = $in->getIdentifier(); + self::assertInstanceOf(ArgumentInterface::class, $identifier2); + self::assertEquals('baz.qux', $identifier2->getValue()); + self::assertEquals(ArgumentType::Identifier, $identifier2->getType()); } - public function testGetExpressionDataWithSubselectAndIdentifier(): void + public function testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAndValuesAndArrayOfTypes(): void { - $select = new Select(); - $in = new In(Argument::identifier('foo'), $select); + $in = new In(); + $in->setIdentifier('foo.bar') + ->setValueSet([1, 2, 3]); $expressionData = $in->getExpressionData(); // Verify specification - self::assertEquals('%s IN %s', $expressionData['spec']); + self::assertEquals('%s IN (%s, %s, %s)', $expressionData['spec']); // Verify expression values $values = $expressionData['values']; @@ -226,58 +214,57 @@ public function testGetExpressionDataWithSubselectAndIdentifier(): void // Verify identifier argument self::assertInstanceOf(ArgumentInterface::class, $values[0]); - self::assertEquals('foo', $values[0]->getValue()); + self::assertEquals('foo.bar', $values[0]->getValue()); self::assertEquals(ArgumentType::Identifier, $values[0]->getType()); - // Verify subselect argument + // Verify value set argument self::assertInstanceOf(ArgumentInterface::class, $values[1]); - self::assertSame($select, $values[1]->getValue()); - self::assertEquals(ArgumentType::Select, $values[1]->getType()); - } + self::assertEquals([1, 2, 3], $values[1]->getValue()); + self::assertEquals(ArgumentType::Values, $values[1]->getType()); - public function testGetExpressionDataWithSubselectAndArrayIdentifier(): void - { - $select = new Select(); - $in = new In(Argument::identifiers(['foo', 'bar']), $select); + // Test with typed value sets + $in->setIdentifier('foo.bar') + ->setValueSet([ + [1 => ArgumentType::Literal], + [2 => ArgumentType::Value], + [3 => ArgumentType::Literal], + ]); $expressionData = $in->getExpressionData(); // Verify specification - self::assertEquals('(%s, %s) IN %s', $expressionData['spec']); + self::assertEquals('%s IN (%s, %s, %s)', $expressionData['spec']); // Verify expression values $values = $expressionData['values']; self::assertCount(2, $values); - // Verify array identifiers argument + // Verify identifier argument self::assertInstanceOf(ArgumentInterface::class, $values[0]); - self::assertEquals(['foo', 'bar'], $values[0]->getValue()); - self::assertEquals(ArgumentType::Identifiers, $values[0]->getType()); + self::assertEquals('foo.bar', $values[0]->getValue()); + self::assertEquals(ArgumentType::Identifier, $values[0]->getType()); - // Verify subselect argument + // Verify value set argument with types self::assertInstanceOf(ArgumentInterface::class, $values[1]); - self::assertSame($select, $values[1]->getValue()); - self::assertEquals(ArgumentType::Select, $values[1]->getType()); + self::assertEquals( + [ + [1 => ArgumentType::Literal], + [2 => ArgumentType::Value], + [3 => ArgumentType::Literal], + ], + $values[1]->getValue(), + ); + self::assertEquals(ArgumentType::Values, $values[1]->getType()); } - public function testGetExpressionDataThrowsExceptionWhenIdentifierNotSet(): void + public function testSetValueSetWithArgumentInterfacePassesThrough(): void { - $in = new In(); - $in->setValueSet([1, 2]); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Identifier must be specified'); - $in->getExpressionData(); - } + $in = new In(); + $values = new Values([1, 2, 3]); - public function testGetExpressionDataThrowsExceptionWhenValueSetNotSet(): void - { - $in = new In(); - $in->setIdentifier('foo'); + $in->setValueSet($values); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Value set must be provided for IN predicate'); - $in->getExpressionData(); + self::assertSame($values, $in->getValueSet()); } public function testSetValueSetWithSelectWrapsInArgumentSelect(): void @@ -292,13 +279,29 @@ public function testSetValueSetWithSelectWrapsInArgumentSelect(): void self::assertSame(ArgumentType::Select, $valueSet->getType()); } - public function testSetValueSetWithArgumentInterfacePassesThrough(): void + public function testValueSetIsMutable(): void { - $in = new In(); - $values = new Values([1, 2, 3]); + $in = new In(); - $in->setValueSet($values); + // First mutation + $result = $in->setValueSet([1, 2]); - self::assertSame($values, $in->getValueSet()); + // Verify fluent interface + self::assertSame($in, $result); + + // Verify the first mutation occurred + $valueSet1 = $in->getValueSet(); + self::assertInstanceOf(ArgumentInterface::class, $valueSet1); + self::assertEquals([1, 2], $valueSet1->getValue()); + self::assertEquals(ArgumentType::Values, $valueSet1->getType()); + + // Second mutation with different data to verify mutability + $in->setValueSet([3, 4, 5]); + + // Verify the instance was actually mutated + $valueSet2 = $in->getValueSet(); + self::assertInstanceOf(ArgumentInterface::class, $valueSet2); + self::assertEquals([3, 4, 5], $valueSet2->getValue()); + self::assertEquals(ArgumentType::Values, $valueSet2->getType()); } } diff --git a/test/unit/Sql/Predicate/IsNullTest.php b/test/unit/Sql/Predicate/IsNullTest.php index 4659bf489..2f22c9066 100644 --- a/test/unit/Sql/Predicate/IsNullTest.php +++ b/test/unit/Sql/Predicate/IsNullTest.php @@ -23,18 +23,6 @@ #[Group('unit')] final class IsNullTest extends TestCase { - public function testEmptyConstructorYieldsNullIdentifier(): void - { - $isNotNull = new IsNotNull(); - self::assertNull($isNotNull->getIdentifier()); - } - - public function testSpecificationIsNullByDefault(): void - { - $isNotNull = new IsNotNull(); - self::assertNull($isNotNull->getSpecification()); - } - public function testCanPassIdentifierToConstructor(): void { $isnull = new IsNotNull('foo.bar'); @@ -46,6 +34,21 @@ public function testCanPassIdentifierToConstructor(): void self::assertEquals(ArgumentType::Identifier, $identifier->getType()); } + public function testEmptyConstructorYieldsNullIdentifier(): void + { + $isNotNull = new IsNotNull(); + self::assertNull($isNotNull->getIdentifier()); + } + + public function testGetExpressionDataThrowsExceptionWhenIdentifierNotSet(): void + { + $isNull = new IsNull(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Identifier must be specified'); + $isNull->getExpressionData(); + } + public function testIdentifierIsMutable(): void { $isNotNull = new IsNotNull(); @@ -72,13 +75,6 @@ public function testIdentifierIsMutable(): void self::assertEquals(ArgumentType::Identifier, $identifier2->getType()); } - public function testSpecificationIsMutable(): void - { - $isNotNull = new IsNotNull(); - $isNotNull->setSpecification('%1$s NOT NULL'); - self::assertEquals('%1$s NOT NULL', $isNotNull->getSpecification()); - } - public function testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAndArrayOfTypes(): void { $isNotNull = new IsNotNull(); @@ -99,13 +95,14 @@ public function testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAnd self::assertEquals(ArgumentType::Identifier, $values[0]->getType()); } - public function testGetExpressionDataThrowsExceptionWhenIdentifierNotSet(): void + public function testSetIdentifierWithArgumentInterfacePassesThrough(): void { - $isNull = new IsNull(); + $isNull = new IsNull(); + $identifier = new Identifier('bar'); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Identifier must be specified'); - $isNull->getExpressionData(); + $isNull->setIdentifier($identifier); + + self::assertSame($identifier, $isNull->getIdentifier()); } public function testSetIdentifierWithStringConvertsToIdentifier(): void @@ -119,13 +116,16 @@ public function testSetIdentifierWithStringConvertsToIdentifier(): void self::assertSame('foo', $identifier->getValue()); } - public function testSetIdentifierWithArgumentInterfacePassesThrough(): void + public function testSpecificationIsMutable(): void { - $isNull = new IsNull(); - $identifier = new Identifier('bar'); - - $isNull->setIdentifier($identifier); + $isNotNull = new IsNotNull(); + $isNotNull->setSpecification('%1$s NOT NULL'); + self::assertEquals('%1$s NOT NULL', $isNotNull->getSpecification()); + } - self::assertSame($identifier, $isNull->getIdentifier()); + public function testSpecificationIsNullByDefault(): void + { + $isNotNull = new IsNotNull(); + self::assertNull($isNotNull->getSpecification()); } } diff --git a/test/unit/Sql/Predicate/LikeTest.php b/test/unit/Sql/Predicate/LikeTest.php index 70534e90b..b7fbcc226 100644 --- a/test/unit/Sql/Predicate/LikeTest.php +++ b/test/unit/Sql/Predicate/LikeTest.php @@ -22,28 +22,6 @@ #[CoversMethod(Like::class, 'getExpressionData')] final class LikeTest extends TestCase { - public function testConstructEmptyArgs(): void - { - $like = new Like(); - self::assertEquals('', $like->getIdentifier()); - self::assertEquals('', $like->getLike()); - } - - public function testConstructWithArgs(): void - { - $like = new Like('bar', 'Foo%'); - - $identifier = $like->getIdentifier(); - self::assertInstanceOf(ArgumentInterface::class, $identifier); - self::assertEquals('bar', $identifier->getValue()); - self::assertEquals(ArgumentType::Identifier, $identifier->getType()); - - $likeValue = $like->getLike(); - self::assertInstanceOf(ArgumentInterface::class, $likeValue); - self::assertEquals('Foo%', $likeValue->getValue()); - self::assertEquals(ArgumentType::Value, $likeValue->getType()); - } - public function testAccessorsMutators(): void { $like = new Like(); @@ -96,6 +74,28 @@ public function testAccessorsMutators(): void self::assertEquals('custom spec', $like->getSpecification()); } + public function testConstructEmptyArgs(): void + { + $like = new Like(); + self::assertEquals('', $like->getIdentifier()); + self::assertEquals('', $like->getLike()); + } + + public function testConstructWithArgs(): void + { + $like = new Like('bar', 'Foo%'); + + $identifier = $like->getIdentifier(); + self::assertInstanceOf(ArgumentInterface::class, $identifier); + self::assertEquals('bar', $identifier->getValue()); + self::assertEquals(ArgumentType::Identifier, $identifier->getType()); + + $likeValue = $like->getLike(); + self::assertInstanceOf(ArgumentInterface::class, $likeValue); + self::assertEquals('Foo%', $likeValue->getValue()); + self::assertEquals(ArgumentType::Value, $likeValue->getType()); + } + public function testGetExpressionData(): void { $like = new Like('bar', 'Foo%'); @@ -141,14 +141,6 @@ public function testGetExpressionData(): void self::assertEquals(ArgumentType::Identifier, $values[1]->getType()); } - public function testInstanceOfPerSetters(): void - { - $like = new Like(); - self::assertInstanceOf(Like::class, $like->setIdentifier('bar')); - self::assertInstanceOf(Like::class, $like->setSpecification('%s LIKE %s')); - self::assertInstanceOf(Like::class, $like->setLike('foo%')); - } - public function testGetExpressionDataThrowsExceptionWhenIdentifierNotSet(): void { $like = new Like(); @@ -168,4 +160,12 @@ public function testGetExpressionDataThrowsExceptionWhenLikeNotSet(): void $this->expectExceptionMessage('Like expression must be specified'); $like->getExpressionData(); } + + public function testInstanceOfPerSetters(): void + { + $like = new Like(); + self::assertInstanceOf(Like::class, $like->setIdentifier('bar')); + self::assertInstanceOf(Like::class, $like->setSpecification('%s LIKE %s')); + self::assertInstanceOf(Like::class, $like->setLike('foo%')); + } } diff --git a/test/unit/Sql/Predicate/LiteralTest.php b/test/unit/Sql/Predicate/LiteralTest.php index 9e500746c..29ddcb81c 100644 --- a/test/unit/Sql/Predicate/LiteralTest.php +++ b/test/unit/Sql/Predicate/LiteralTest.php @@ -9,6 +9,21 @@ class LiteralTest extends TestCase { + public function testGetExpressionData(): void + { + $literal = new Literal('bar'); + + $expressionData = $literal->getExpressionData(); + + self::assertEquals('bar', $expressionData['spec']); + } + + public function testGetLiteral(): void + { + $literal = new Literal('bar'); + self::assertEquals('bar', $literal->getLiteral()); + } + public function testSetLiteral(): void { $literal = new Literal('bar'); @@ -28,19 +43,4 @@ public function testSetLiteral(): void // Verify the instance was actually mutated self::assertEquals('baz', $literal->getLiteral()); } - - public function testGetLiteral(): void - { - $literal = new Literal('bar'); - self::assertEquals('bar', $literal->getLiteral()); - } - - public function testGetExpressionData(): void - { - $literal = new Literal('bar'); - - $expressionData = $literal->getExpressionData(); - - self::assertEquals('bar', $expressionData['spec']); - } } diff --git a/test/unit/Sql/Predicate/NotBetweenTest.php b/test/unit/Sql/Predicate/NotBetweenTest.php index ffbb06015..fff32fff0 100644 --- a/test/unit/Sql/Predicate/NotBetweenTest.php +++ b/test/unit/Sql/Predicate/NotBetweenTest.php @@ -18,17 +18,6 @@ final class NotBetweenTest extends TestCase { protected NotBetween $notBetween; - #[Override] - protected function setUp(): void - { - $this->notBetween = new NotBetween(); - } - - public function testSpecificationIsNullByDefault(): void - { - self::assertNull($this->notBetween->getSpecification()); - } - public function testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAndValuesAndArrayOfTypes(): void { $this->notBetween @@ -89,4 +78,15 @@ public function testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAnd self::assertEquals('foo.baz', $values[2]->getValue()); self::assertEquals(ArgumentType::Identifier, $values[2]->getType()); } + + public function testSpecificationIsNullByDefault(): void + { + self::assertNull($this->notBetween->getSpecification()); + } + + #[Override] + protected function setUp(): void + { + $this->notBetween = new NotBetween(); + } } diff --git a/test/unit/Sql/Predicate/NotInTest.php b/test/unit/Sql/Predicate/NotInTest.php index c75f1287a..39798044c 100644 --- a/test/unit/Sql/Predicate/NotInTest.php +++ b/test/unit/Sql/Predicate/NotInTest.php @@ -13,16 +13,15 @@ final class NotInTest extends TestCase { - public function testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAndValuesAndArrayOfTypes(): void + public function testGetExpressionDataWithSubselect(): void { - $in = new NotIn(); - $in->setIdentifier('foo.bar') - ->setValueSet([1, 2, 3]); + $select = new Select(); + $in = new NotIn('foo', $select); $expressionData = $in->getExpressionData(); // Verify specification - self::assertEquals('%s NOT IN (%s, %s, %s)', $expressionData['spec']); + self::assertEquals('%s NOT IN %s', $expressionData['spec']); // Verify expression values $values = $expressionData['values']; @@ -30,33 +29,33 @@ public function testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAnd // Verify identifier argument self::assertInstanceOf(ArgumentInterface::class, $values[0]); - self::assertEquals('foo.bar', $values[0]->getValue()); + self::assertEquals('foo', $values[0]->getValue()); self::assertEquals(ArgumentType::Identifier, $values[0]->getType()); - // Verify value set argument + // Verify subselect argument self::assertInstanceOf(ArgumentInterface::class, $values[1]); - self::assertEquals([1, 2, 3], $values[1]->getValue()); - self::assertEquals(ArgumentType::Values, $values[1]->getType()); + self::assertSame($select, $values[1]->getValue()); + self::assertEquals(ArgumentType::Select, $values[1]->getType()); } - public function testGetExpressionDataWithSubselect(): void + public function testGetExpressionDataWithSubselectAndArrayIdentifier(): void { $select = new Select(); - $in = new NotIn('foo', $select); + $in = new NotIn(Argument::identifiers(['foo', 'bar']), $select); $expressionData = $in->getExpressionData(); // Verify specification - self::assertEquals('%s NOT IN %s', $expressionData['spec']); + self::assertEquals('(%s, %s) NOT IN %s', $expressionData['spec']); // Verify expression values $values = $expressionData['values']; self::assertCount(2, $values); - // Verify identifier argument + // Verify array identifier argument self::assertInstanceOf(ArgumentInterface::class, $values[0]); - self::assertEquals('foo', $values[0]->getValue()); - self::assertEquals(ArgumentType::Identifier, $values[0]->getType()); + self::assertEquals(['foo', 'bar'], $values[0]->getValue()); + self::assertEquals(ArgumentType::Identifiers, $values[0]->getType()); // Verify subselect argument self::assertInstanceOf(ArgumentInterface::class, $values[1]); @@ -89,28 +88,29 @@ public function testGetExpressionDataWithSubselectAndIdentifier(): void self::assertEquals(ArgumentType::Select, $values[1]->getType()); } - public function testGetExpressionDataWithSubselectAndArrayIdentifier(): void + public function testRetrievingWherePartsReturnsSpecificationArrayOfIdentifierAndValuesAndArrayOfTypes(): void { - $select = new Select(); - $in = new NotIn(Argument::identifiers(['foo', 'bar']), $select); + $in = new NotIn(); + $in->setIdentifier('foo.bar') + ->setValueSet([1, 2, 3]); $expressionData = $in->getExpressionData(); // Verify specification - self::assertEquals('(%s, %s) NOT IN %s', $expressionData['spec']); + self::assertEquals('%s NOT IN (%s, %s, %s)', $expressionData['spec']); // Verify expression values $values = $expressionData['values']; self::assertCount(2, $values); - // Verify array identifier argument + // Verify identifier argument self::assertInstanceOf(ArgumentInterface::class, $values[0]); - self::assertEquals(['foo', 'bar'], $values[0]->getValue()); - self::assertEquals(ArgumentType::Identifiers, $values[0]->getType()); + self::assertEquals('foo.bar', $values[0]->getValue()); + self::assertEquals(ArgumentType::Identifier, $values[0]->getType()); - // Verify subselect argument + // Verify value set argument self::assertInstanceOf(ArgumentInterface::class, $values[1]); - self::assertSame($select, $values[1]->getValue()); - self::assertEquals(ArgumentType::Select, $values[1]->getType()); + self::assertEquals([1, 2, 3], $values[1]->getValue()); + self::assertEquals(ArgumentType::Values, $values[1]->getType()); } } diff --git a/test/unit/Sql/Predicate/NotLikeTest.php b/test/unit/Sql/Predicate/NotLikeTest.php index 4f7228f78..7061d2d9e 100644 --- a/test/unit/Sql/Predicate/NotLikeTest.php +++ b/test/unit/Sql/Predicate/NotLikeTest.php @@ -12,28 +12,6 @@ final class NotLikeTest extends TestCase { - public function testConstructEmptyArgs(): void - { - $notLike = new NotLike(); - self::assertEquals('', $notLike->getIdentifier()); - self::assertEquals('', $notLike->getLike()); - } - - public function testConstructWithArgs(): void - { - $notLike = new NotLike('bar', 'Foo%'); - - $identifier = $notLike->getIdentifier(); - self::assertInstanceOf(ArgumentInterface::class, $identifier); - self::assertEquals('bar', $identifier->getValue()); - self::assertEquals(ArgumentType::Identifier, $identifier->getType()); - - $likeValue = $notLike->getLike(); - self::assertInstanceOf(ArgumentInterface::class, $likeValue); - self::assertEquals('Foo%', $likeValue->getValue()); - self::assertEquals(ArgumentType::Value, $likeValue->getType()); - } - public function testAccessorsMutators(): void { $notLike = new NotLike(); @@ -86,6 +64,28 @@ public function testAccessorsMutators(): void self::assertEquals('custom spec', $notLike->getSpecification()); } + public function testConstructEmptyArgs(): void + { + $notLike = new NotLike(); + self::assertEquals('', $notLike->getIdentifier()); + self::assertEquals('', $notLike->getLike()); + } + + public function testConstructWithArgs(): void + { + $notLike = new NotLike('bar', 'Foo%'); + + $identifier = $notLike->getIdentifier(); + self::assertInstanceOf(ArgumentInterface::class, $identifier); + self::assertEquals('bar', $identifier->getValue()); + self::assertEquals(ArgumentType::Identifier, $identifier->getType()); + + $likeValue = $notLike->getLike(); + self::assertInstanceOf(ArgumentInterface::class, $likeValue); + self::assertEquals('Foo%', $likeValue->getValue()); + self::assertEquals(ArgumentType::Value, $likeValue->getType()); + } + public function testGetExpressionData(): void { $notLike = new NotLike('bar', 'Foo%'); diff --git a/test/unit/Sql/Predicate/OperatorTest.php b/test/unit/Sql/Predicate/OperatorTest.php index 6afeaf4d0..77f89657a 100644 --- a/test/unit/Sql/Predicate/OperatorTest.php +++ b/test/unit/Sql/Predicate/OperatorTest.php @@ -27,19 +27,6 @@ #[Group('unit')] final class OperatorTest extends TestCase { - public function testEmptyConstructorYieldsNullLeftAndRightValues(): void - { - $operator = new Operator(); - self::assertNull($operator->getLeft()); - self::assertNull($operator->getRight()); - } - - public function testEmptyConstructorYieldsDefaultsForOperatorAndLeftAndRightTypes(): void - { - $operator = new Operator(); - self::assertEquals(Operator::OP_EQ, $operator->getOperator()); - } - public function testCanPassAllValuesToConstructor(): void { $operator = new Operator('bar', '>=', 'foo.bar'); @@ -76,6 +63,39 @@ public function testCanPassAllValuesToConstructor(): void self::assertEquals(ArgumentType::Value, $right->getType()); } + public function testEmptyConstructorYieldsDefaultsForOperatorAndLeftAndRightTypes(): void + { + $operator = new Operator(); + self::assertEquals(Operator::OP_EQ, $operator->getOperator()); + } + + public function testEmptyConstructorYieldsNullLeftAndRightValues(): void + { + $operator = new Operator(); + self::assertNull($operator->getLeft()); + self::assertNull($operator->getRight()); + } + + public function testGetExpressionDataThrowsExceptionWhenLeftNotSet(): void + { + $operator = new Operator(); + $operator->setRight('value'); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Left expression must be specified'); + $operator->getExpressionData(); + } + + public function testGetExpressionDataThrowsExceptionWhenRightNotSet(): void + { + $operator = new Operator(); + $operator->setLeft('left'); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Right expression must be specified'); + $operator->getExpressionData(); + } + public function testLeftIsMutable(): void { $operator = new Operator(); @@ -102,41 +122,6 @@ public function testLeftIsMutable(): void self::assertEquals(ArgumentType::Identifier, $left2->getType()); } - public function testRightIsMutable(): void - { - $operator = new Operator(); - - // First mutation - default type (Value) - $result = $operator->setRight('bar'); - - // Verify fluent interface - self::assertSame($operator, $result); - - // Verify the first mutation occurred - $right1 = $operator->getRight(); - self::assertInstanceOf(ArgumentInterface::class, $right1); - self::assertEquals('bar', $right1->getValue()); - self::assertEquals(ArgumentType::Value, $right1->getType()); - - // Second mutation - with explicit type (Identifier) using factory - $operator->setRight(new Identifier('bar')); - - // Verify the instance was actually mutated (same value, different type) - $right2 = $operator->getRight(); - self::assertInstanceOf(ArgumentInterface::class, $right2); - self::assertEquals('bar', $right2->getValue()); - self::assertEquals(ArgumentType::Identifier, $right2->getType()); - - // Third mutation - different value with default type - $operator->setRight('qux'); - - // Verify the instance was mutated again - $right3 = $operator->getRight(); - self::assertInstanceOf(ArgumentInterface::class, $right3); - self::assertEquals('qux', $right3->getValue()); - self::assertEquals(ArgumentType::Value, $right3->getType()); - } - public function testOperatorIsMutable(): void { $operator = new Operator(); @@ -147,8 +132,7 @@ public function testOperatorIsMutable(): void public function testRetrievingWherePartsReturnsSpecificationArrayOfLeftAndRightAndArrayOfTypes(): void { $operator = new Operator(); - $operator - ->setLeft(new Value('foo')) + $operator->setLeft(new Value('foo')) ->setOperator('>=') ->setRight(new Identifier('foo.bar')); @@ -172,24 +156,39 @@ public function testRetrievingWherePartsReturnsSpecificationArrayOfLeftAndRightA self::assertEquals(ArgumentType::Identifier, $values[1]->getType()); } - public function testGetExpressionDataThrowsExceptionWhenLeftNotSet(): void + public function testRightIsMutable(): void { $operator = new Operator(); - $operator->setRight('value'); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Left expression must be specified'); - $operator->getExpressionData(); - } + // First mutation - default type (Value) + $result = $operator->setRight('bar'); - public function testGetExpressionDataThrowsExceptionWhenRightNotSet(): void - { - $operator = new Operator(); - $operator->setLeft('left'); + // Verify fluent interface + self::assertSame($operator, $result); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Right expression must be specified'); - $operator->getExpressionData(); + // Verify the first mutation occurred + $right1 = $operator->getRight(); + self::assertInstanceOf(ArgumentInterface::class, $right1); + self::assertEquals('bar', $right1->getValue()); + self::assertEquals(ArgumentType::Value, $right1->getType()); + + // Second mutation - with explicit type (Identifier) using factory + $operator->setRight(new Identifier('bar')); + + // Verify the instance was actually mutated (same value, different type) + $right2 = $operator->getRight(); + self::assertInstanceOf(ArgumentInterface::class, $right2); + self::assertEquals('bar', $right2->getValue()); + self::assertEquals(ArgumentType::Identifier, $right2->getType()); + + // Third mutation - different value with default type + $operator->setRight('qux'); + + // Verify the instance was mutated again + $right3 = $operator->getRight(); + self::assertInstanceOf(ArgumentInterface::class, $right3); + self::assertEquals('qux', $right3->getValue()); + self::assertEquals(ArgumentType::Value, $right3->getType()); } public function testSetLeftWithExpressionInterfaceWrapsInSelect(): void diff --git a/test/unit/Sql/Predicate/PredicateSetTest.php b/test/unit/Sql/Predicate/PredicateSetTest.php index d5e36985e..68328adf5 100644 --- a/test/unit/Sql/Predicate/PredicateSetTest.php +++ b/test/unit/Sql/Predicate/PredicateSetTest.php @@ -37,80 +37,6 @@ final class PredicateSetTest extends TestCase { use DeprecatedAssertionsTrait; - public function testEmptyConstructorYieldsCountOfZero(): void - { - $predicateSet = new PredicateSet(); - self::assertCount(0, $predicateSet); - } - - public function testCombinationIsAndByDefault(): void - { - $predicateSet = new PredicateSet(); - $predicateSet - ->addPredicate(new IsNull('foo')) - ->addPredicate(new IsNull('bar')); - - $expressionData = $predicateSet->getExpressionData(); - - // 2 predicates = 2 values - self::assertCount(2, $expressionData['values']); - self::assertStringContainsString('AND', $expressionData['spec']); - self::assertStringNotContainsString('OR', $expressionData['spec']); - } - - public function testCanPassPredicatesAndDefaultCombinationViaConstructor(): void - { - new PredicateSet(); - $predicateSet = new PredicateSet([ - new IsNull('foo'), - new IsNull('bar'), - ], 'OR'); - - $expressionData = $predicateSet->getExpressionData(); - - // 2 predicates = 2 values - self::assertCount(2, $expressionData['values']); - self::assertStringContainsString('OR', $expressionData['spec']); - self::assertStringNotContainsString('AND', $expressionData['spec']); - } - - public function testCanPassBothPredicateAndCombinationToAddPredicate(): void - { - $predicateSet = new PredicateSet(); - $predicateSet - ->addPredicate(new IsNull('foo'), 'OR') - ->addPredicate(new IsNull('bar'), 'AND') - ->addPredicate(new IsNull('baz'), 'OR') - ->addPredicate(new IsNull('bat'), 'AND'); - - $expressionData = $predicateSet->getExpressionData(); - - // 4 predicates = 4 values - self::assertCount(4, $expressionData['values']); - - // Verify combinators are in spec string: AND bar AND baz OR bat - $spec = $expressionData['spec']; - self::assertEquals('%s IS NULL AND %s IS NULL OR %s IS NULL AND %s IS NULL', $spec); - } - - public function testCanUseOrPredicateAndAndPredicateMethods(): void - { - $predicateSet = new PredicateSet(); - $predicateSet->orPredicate(new IsNull('foo')) - ->andPredicate(new IsNull('bar')) - ->orPredicate(new IsNull('baz')) - ->andPredicate(new IsNull('bat')); - - $expressionData = $predicateSet->getExpressionData(); - - // 4 predicates = 4 values - self::assertCount(4, $expressionData['values']); - - // Verify spec contains correct pattern: foo AND bar OR baz AND bat - $spec = $expressionData['spec']; - self::assertEquals('%s IS NULL AND %s IS NULL OR %s IS NULL AND %s IS NULL', $spec); - } - /** * @throws ReflectionException */ @@ -157,7 +83,7 @@ public function testAddPredicates(): void self::assertEquals('AND', $predicates[6][0]); self::assertInstanceOf(IsNotNull::class, $predicates[6][1]); - $predicateSet->addPredicates(function (PredicateSet $what) use ($predicateSet): void { + $predicateSet->addPredicates(static function (PredicateSet $what) use ($predicateSet): void { self::assertSame($predicateSet, $what); }); @@ -166,6 +92,16 @@ public function testAddPredicates(): void $predicateSet->addPredicates(null); } + public function testAddPredicatesThrowsWhenStringKeyUsedWithPredicateInterface(): void + { + $predicateSet = new PredicateSet(); + $mock = $this->createMock(PredicateInterface::class); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Using Predicate must not use string keys'); + $predicateSet->addPredicates(['key' => $mock]); + } + /** * Test that Expression objects (not just PredicateInterface) can be added via addPredicates * @@ -223,14 +159,76 @@ public function testAddPredicateThrowsOnInvalidCombination(): void $predicateSet->addPredicate(new IsNull('foo'), 'XOR'); } - public function testAddPredicatesThrowsWhenStringKeyUsedWithPredicateInterface(): void + public function testCanPassBothPredicateAndCombinationToAddPredicate(): void { $predicateSet = new PredicateSet(); - $mock = $this->createMock(PredicateInterface::class); + $predicateSet->addPredicate(new IsNull('foo'), 'OR') + ->addPredicate(new IsNull('bar'), 'AND') + ->addPredicate(new IsNull('baz'), 'OR') + ->addPredicate(new IsNull('bat'), 'AND'); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Using Predicate must not use string keys'); - $predicateSet->addPredicates(['key' => $mock]); + $expressionData = $predicateSet->getExpressionData(); + + // 4 predicates = 4 values + self::assertCount(4, $expressionData['values']); + + // Verify combinators are in spec string: AND bar AND baz OR bat + $spec = $expressionData['spec']; + self::assertEquals('%s IS NULL AND %s IS NULL OR %s IS NULL AND %s IS NULL', $spec); + } + + public function testCanPassPredicatesAndDefaultCombinationViaConstructor(): void + { + new PredicateSet(); + $predicateSet = new PredicateSet([ + new IsNull('foo'), + new IsNull('bar'), + ], 'OR'); + + $expressionData = $predicateSet->getExpressionData(); + + // 2 predicates = 2 values + self::assertCount(2, $expressionData['values']); + self::assertStringContainsString('OR', $expressionData['spec']); + self::assertStringNotContainsString('AND', $expressionData['spec']); + } + + public function testCanUseOrPredicateAndAndPredicateMethods(): void + { + $predicateSet = new PredicateSet(); + $predicateSet->orPredicate(new IsNull('foo')) + ->andPredicate(new IsNull('bar')) + ->orPredicate(new IsNull('baz')) + ->andPredicate(new IsNull('bat')); + + $expressionData = $predicateSet->getExpressionData(); + + // 4 predicates = 4 values + self::assertCount(4, $expressionData['values']); + + // Verify spec contains correct pattern: foo AND bar OR baz AND bat + $spec = $expressionData['spec']; + self::assertEquals('%s IS NULL AND %s IS NULL OR %s IS NULL AND %s IS NULL', $spec); + } + + public function testCombinationIsAndByDefault(): void + { + $predicateSet = new PredicateSet(); + $predicateSet->addPredicate(new IsNull('foo')) + ->addPredicate(new IsNull('bar')); + + $expressionData = $predicateSet->getExpressionData(); + + // 2 predicates = 2 values + self::assertCount(2, $expressionData['values']); + self::assertStringContainsString('AND', $expressionData['spec']); + self::assertStringNotContainsString('OR', $expressionData['spec']); + } + + public function testEmptyConstructorYieldsCountOfZero(): void + { + $predicateSet = new PredicateSet(); + self::assertCount(0, $predicateSet); } public function testGetExpressionDataReturnsEmptyWhenNoPredicates(): void diff --git a/test/unit/Sql/Predicate/PredicateTest.php b/test/unit/Sql/Predicate/PredicateTest.php index 91eabbc97..80f31cbde 100644 --- a/test/unit/Sql/Predicate/PredicateTest.php +++ b/test/unit/Sql/Predicate/PredicateTest.php @@ -20,143 +20,200 @@ #[Group('unit')] final class PredicateTest extends TestCase { - public function testEqualToCreatesOperatorPredicate(): void + public function testBetweenCreatesBetweenPredicate(): void { $predicate = new Predicate(); - $predicate->equalTo('foo.bar', 'bar'); + $predicate->between('foo.bar', 1, 10); $identifier = Argument::identifier('foo.bar'); - $expression = Argument::value('bar'); + $minValue = Argument::value(1); + $maxValue = Argument::value(10); $expressionData = $predicate->getExpressionData(); - self::assertEquals('%s = %s', $expressionData['spec']); - self::assertCount(2, $expressionData['values']); + self::assertEquals('%s BETWEEN %s AND %s', $expressionData['spec']); + self::assertCount(3, $expressionData['values']); self::assertEquals($identifier, $expressionData['values'][0]); - self::assertEquals($expression, $expressionData['values'][1]); + self::assertEquals($minValue, $expressionData['values'][1]); + self::assertEquals($maxValue, $expressionData['values'][2]); } - public function testNotEqualToCreatesOperatorPredicate(): void + public function testBetweenCreatesNotBetweenPredicate(): void { $predicate = new Predicate(); - $predicate->notEqualTo('foo.bar', 'bar'); + $predicate->notBetween('foo.bar', 1, 10); $identifier = Argument::identifier('foo.bar'); - $expression = Argument::value('bar'); + $minValue = Argument::value(1); + $maxValue = Argument::value(10); $expressionData = $predicate->getExpressionData(); - self::assertEquals('%s != %s', $expressionData['spec']); - self::assertCount(2, $expressionData['values']); + self::assertEquals('%s NOT BETWEEN %s AND %s', $expressionData['spec']); + self::assertCount(3, $expressionData['values']); self::assertEquals($identifier, $expressionData['values'][0]); - self::assertEquals($expression, $expressionData['values'][1]); + self::assertEquals($minValue, $expressionData['values'][1]); + self::assertEquals($maxValue, $expressionData['values'][2]); } - public function testLessThanCreatesOperatorPredicate(): void + public function testCanChainPredicateFactoriesBetweenOperators(): void { $predicate = new Predicate(); - $predicate->lessThan('foo.bar', 'bar'); + $predicate->isNull('foo.bar')->or->isNotNull('bar.baz') + ->and->equalTo('baz.bat', 'foo'); - $identifier = Argument::identifier('foo.bar'); - $expression = Argument::value('bar'); + $identifier1 = Argument::identifier('foo.bar'); + $identifier2 = Argument::identifier('bar.baz'); + $identifier3 = Argument::identifier('baz.bat'); + $expression3 = Argument::value('foo'); $expressionData = $predicate->getExpressionData(); - self::assertEquals('%s < %s', $expressionData['spec']); - self::assertCount(2, $expressionData['values']); - self::assertEquals($identifier, $expressionData['values'][0]); - self::assertEquals($expression, $expressionData['values'][1]); + // 3 predicates: IsNull, IsNotNull, Operator = 4 values (1+1+2) + self::assertCount(4, $expressionData['values']); + // Verify combined spec + self::assertEquals('%s IS NULL OR %s IS NOT NULL AND %s = %s', $expressionData['spec']); + self::assertEquals($identifier1, $expressionData['values'][0]); + self::assertEquals($identifier2, $expressionData['values'][1]); + self::assertEquals($identifier3, $expressionData['values'][2]); + self::assertEquals($expression3, $expressionData['values'][3]); } - public function testGreaterThanCreatesOperatorPredicate(): void + public function testCanNestPredicates(): void { $predicate = new Predicate(); - $predicate->greaterThan('foo.bar', 'bar'); + $predicate->isNull('foo.bar') + ->nest() + ->isNotNull('bar.baz') + ->and + ->equalTo('baz.bat', 'foo') + ->unnest(); - $identifier = Argument::identifier('foo.bar'); - $expression = Argument::value('bar'); + $identifier1 = Argument::identifier('foo.bar'); + $identifier2 = Argument::identifier('bar.baz'); + $identifier3 = Argument::identifier('baz.bat'); + $expression3 = Argument::value('foo'); $expressionData = $predicate->getExpressionData(); - self::assertEquals('%s > %s', $expressionData['spec']); - self::assertCount(2, $expressionData['values']); - self::assertEquals($identifier, $expressionData['values'][0]); - self::assertEquals($expression, $expressionData['values'][1]); + // 3 predicates: IsNull + nested(IsNotNull, Operator) = 4 values + self::assertCount(4, $expressionData['values']); + // Verify combined spec with nested brackets + self::assertEquals('%s IS NULL AND (%s IS NOT NULL AND %s = %s)', $expressionData['spec']); + self::assertEquals($identifier1, $expressionData['values'][0]); + self::assertEquals($identifier2, $expressionData['values'][1]); + self::assertEquals($identifier3, $expressionData['values'][2]); + self::assertEquals($expression3, $expressionData['values'][3]); } - public function testLessThanOrEqualToCreatesOperatorPredicate(): void + public function testEqualToCreatesOperatorPredicate(): void { $predicate = new Predicate(); - $predicate->lessThanOrEqualTo('foo.bar', 'bar'); + $predicate->equalTo('foo.bar', 'bar'); $identifier = Argument::identifier('foo.bar'); $expression = Argument::value('bar'); $expressionData = $predicate->getExpressionData(); - self::assertEquals('%s <= %s', $expressionData['spec']); + self::assertEquals('%s = %s', $expressionData['spec']); self::assertCount(2, $expressionData['values']); self::assertEquals($identifier, $expressionData['values'][0]); self::assertEquals($expression, $expressionData['values'][1]); } - public function testGreaterThanOrEqualToCreatesOperatorPredicate(): void + #[TestDox('Unit test: Test expression() is chainable and returns proper values')] + public function testExpression(): void + { + $predicate = new Predicate(); + $value = Argument::value(0); + + // is chainable + self::assertSame($predicate, $predicate->expression('foo = ?', 0)); + $expressionData = $predicate->getExpressionData(); + // with parameter + self::assertEquals('foo = %s', $expressionData['spec']); + self::assertEquals([$value], $expressionData['values']); + } + + #[TestDox('Unit test: Test expression() allows null $parameters')] + public function testExpressionNullParameters(): void { $predicate = new Predicate(); - $predicate->greaterThanOrEqualTo('foo.bar', 'bar'); + + $predicate->expression('foo = bar'); + + $predicates = $predicate->getPredicates(); + + if (isset($predicates[0][1])) { + $expression = $predicates[0][1]; + $this->assertInstanceOf(Expression::class, $expression); + self::assertEquals([], $expression->getParameters()); + } else { + $this->fail('Expression not found'); + } + } + + public function testGreaterThanCreatesOperatorPredicate(): void + { + $predicate = new Predicate(); + $predicate->greaterThan('foo.bar', 'bar'); $identifier = Argument::identifier('foo.bar'); $expression = Argument::value('bar'); $expressionData = $predicate->getExpressionData(); - self::assertEquals('%s >= %s', $expressionData['spec']); + self::assertEquals('%s > %s', $expressionData['spec']); self::assertCount(2, $expressionData['values']); self::assertEquals($identifier, $expressionData['values'][0]); self::assertEquals($expression, $expressionData['values'][1]); } - public function testLikeCreatesLikePredicate(): void + public function testGreaterThanOrEqualToCreatesOperatorPredicate(): void { $predicate = new Predicate(); - $predicate->like('foo.bar', 'bar%'); + $predicate->greaterThanOrEqualTo('foo.bar', 'bar'); $identifier = Argument::identifier('foo.bar'); - $expression = Argument::value('bar%'); + $expression = Argument::value('bar'); $expressionData = $predicate->getExpressionData(); - self::assertEquals('%s LIKE %s', $expressionData['spec']); + self::assertEquals('%s >= %s', $expressionData['spec']); self::assertCount(2, $expressionData['values']); self::assertEquals($identifier, $expressionData['values'][0]); self::assertEquals($expression, $expressionData['values'][1]); } - public function testNotLikeCreatesLikePredicate(): void + public function testInCreatesInPredicate(): void { $predicate = new Predicate(); - $predicate->notLike('foo.bar', 'bar%'); + $predicate->in('foo.bar', ['foo', 'bar']); $identifier = Argument::identifier('foo.bar'); - $expression = Argument::value('bar%'); + $expression = Argument::values(['foo', 'bar']); $expressionData = $predicate->getExpressionData(); - self::assertEquals('%s NOT LIKE %s', $expressionData['spec']); + self::assertEquals('%s IN (%s, %s)', $expressionData['spec']); self::assertCount(2, $expressionData['values']); self::assertEquals($identifier, $expressionData['values'][0]); self::assertEquals($expression, $expressionData['values'][1]); } - public function testLiteralCreatesLiteralPredicate(): void + public function testIsNotNullCreatesIsNotNullPredicate(): void { $predicate = new Predicate(); - $predicate->literal('foo.bar = ?'); + $predicate->isNotNull('foo.bar'); + + $identifier = Argument::identifier('foo.bar'); $expressionData = $predicate->getExpressionData(); - self::assertCount(0, $expressionData['values']); - self::assertEquals('foo.bar = ?', $expressionData['spec']); + self::assertEquals('%s IS NOT NULL', $expressionData['spec']); + self::assertCount(1, $expressionData['values']); + self::assertEquals($identifier, $expressionData['values'][0]); } public function testIsNullCreatesIsNullPredicate(): void @@ -173,217 +230,168 @@ public function testIsNullCreatesIsNullPredicate(): void self::assertEquals($identifier, $expressionData['values'][0]); } - public function testIsNotNullCreatesIsNotNullPredicate(): void + public function testLessThanCreatesOperatorPredicate(): void { $predicate = new Predicate(); - $predicate->isNotNull('foo.bar'); + $predicate->lessThan('foo.bar', 'bar'); $identifier = Argument::identifier('foo.bar'); + $expression = Argument::value('bar'); $expressionData = $predicate->getExpressionData(); - self::assertEquals('%s IS NOT NULL', $expressionData['spec']); - self::assertCount(1, $expressionData['values']); + self::assertEquals('%s < %s', $expressionData['spec']); + self::assertCount(2, $expressionData['values']); self::assertEquals($identifier, $expressionData['values'][0]); + self::assertEquals($expression, $expressionData['values'][1]); } - public function testInCreatesInPredicate(): void + public function testLessThanOrEqualToCreatesOperatorPredicate(): void { $predicate = new Predicate(); - $predicate->in('foo.bar', ['foo', 'bar']); + $predicate->lessThanOrEqualTo('foo.bar', 'bar'); $identifier = Argument::identifier('foo.bar'); - $expression = Argument::values(['foo', 'bar']); + $expression = Argument::value('bar'); $expressionData = $predicate->getExpressionData(); - self::assertEquals('%s IN (%s, %s)', $expressionData['spec']); + self::assertEquals('%s <= %s', $expressionData['spec']); self::assertCount(2, $expressionData['values']); self::assertEquals($identifier, $expressionData['values'][0]); self::assertEquals($expression, $expressionData['values'][1]); } - public function testNotInCreatesNotInPredicate(): void + public function testLikeCreatesLikePredicate(): void { $predicate = new Predicate(); - $predicate->notIn('foo.bar', ['foo', 'bar']); + $predicate->like('foo.bar', 'bar%'); $identifier = Argument::identifier('foo.bar'); - $expression = Argument::values(['foo', 'bar']); + $expression = Argument::value('bar%'); $expressionData = $predicate->getExpressionData(); - self::assertEquals('%s NOT IN (%s, %s)', $expressionData['spec']); + self::assertEquals('%s LIKE %s', $expressionData['spec']); self::assertCount(2, $expressionData['values']); self::assertEquals($identifier, $expressionData['values'][0]); self::assertEquals($expression, $expressionData['values'][1]); } - public function testBetweenCreatesBetweenPredicate(): void + #[TestDox('Unit test: Test literal() is chainable, returns proper values, and is backwards compatible with 2.0.*')] + public function testLiteral(): void { $predicate = new Predicate(); - $predicate->between('foo.bar', 1, 10); - $identifier = Argument::identifier('foo.bar'); - $minValue = Argument::value(1); - $maxValue = Argument::value(10); + // is chainable + self::assertSame($predicate, $predicate->literal('foo = bar')); $expressionData = $predicate->getExpressionData(); - self::assertEquals('%s BETWEEN %s AND %s', $expressionData['spec']); - self::assertCount(3, $expressionData['values']); - self::assertEquals($identifier, $expressionData['values'][0]); - self::assertEquals($minValue, $expressionData['values'][1]); - self::assertEquals($maxValue, $expressionData['values'][2]); - } + // with parameter + self::assertEquals('foo = bar', $expressionData['spec']); + self::assertEquals([], $expressionData['values']); - public function testBetweenCreatesNotBetweenPredicate(): void - { + // test literal() is backwards-compatible, and works with with parameters $predicate = new Predicate(); - $predicate->notBetween('foo.bar', 1, 10); - - $identifier = Argument::identifier('foo.bar'); - $minValue = Argument::value(1); - $maxValue = Argument::value(10); + $predicate->expression('foo = ?', 'bar'); + $expression = Argument::value('bar'); $expressionData = $predicate->getExpressionData(); - self::assertEquals('%s NOT BETWEEN %s AND %s', $expressionData['spec']); - self::assertCount(3, $expressionData['values']); - self::assertEquals($identifier, $expressionData['values'][0]); - self::assertEquals($minValue, $expressionData['values'][1]); - self::assertEquals($maxValue, $expressionData['values'][2]); - } + // with parameter + self::assertEquals('foo = %s', $expressionData['spec']); + self::assertEquals([$expression], $expressionData['values']); - public function testCanChainPredicateFactoriesBetweenOperators(): void - { + // test literal() is backwards-compatible, and works with with parameters, even 0 which tests as false $predicate = new Predicate(); - $predicate->isNull('foo.bar') - ->or - ->isNotNull('bar.baz') - ->and - ->equalTo('baz.bat', 'foo'); - - $identifier1 = Argument::identifier('foo.bar'); - $identifier2 = Argument::identifier('bar.baz'); - $identifier3 = Argument::identifier('baz.bat'); - $expression3 = Argument::value('foo'); + $predicate->expression('foo = ?', 0); + $expression = Argument::value(0); $expressionData = $predicate->getExpressionData(); - // 3 predicates: IsNull, IsNotNull, Operator = 4 values (1+1+2) - self::assertCount(4, $expressionData['values']); - // Verify combined spec - self::assertEquals('%s IS NULL OR %s IS NOT NULL AND %s = %s', $expressionData['spec']); - self::assertEquals($identifier1, $expressionData['values'][0]); - self::assertEquals($identifier2, $expressionData['values'][1]); - self::assertEquals($identifier3, $expressionData['values'][2]); - self::assertEquals($expression3, $expressionData['values'][3]); + // with parameter + self::assertEquals('foo = %s', $expressionData['spec']); + self::assertEquals([$expression], $expressionData['values']); } - public function testCanNestPredicates(): void + public function testLiteralCreatesLiteralPredicate(): void { $predicate = new Predicate(); - $predicate->isNull('foo.bar') - ->nest() - ->isNotNull('bar.baz') - ->and - ->equalTo('baz.bat', 'foo') - ->unnest(); - - $identifier1 = Argument::identifier('foo.bar'); - $identifier2 = Argument::identifier('bar.baz'); - $identifier3 = Argument::identifier('baz.bat'); - $expression3 = Argument::value('foo'); + $predicate->literal('foo.bar = ?'); $expressionData = $predicate->getExpressionData(); - // 3 predicates: IsNull + nested(IsNotNull, Operator) = 4 values - self::assertCount(4, $expressionData['values']); - // Verify combined spec with nested brackets - self::assertEquals('%s IS NULL AND (%s IS NOT NULL AND %s = %s)', $expressionData['spec']); - self::assertEquals($identifier1, $expressionData['values'][0]); - self::assertEquals($identifier2, $expressionData['values'][1]); - self::assertEquals($identifier3, $expressionData['values'][2]); - self::assertEquals($expression3, $expressionData['values'][3]); + self::assertCount(0, $expressionData['values']); + self::assertEquals('foo.bar = ?', $expressionData['spec']); } - #[TestDox('Unit test: Test expression() is chainable and returns proper values')] - public function testExpression(): void + public function testMagicGetNestReturnsNestedPredicate(): void { $predicate = new Predicate(); - $value = Argument::value(0); - // is chainable - self::assertSame($predicate, $predicate->expression('foo = ?', 0)); - $expressionData = $predicate->getExpressionData(); - // with parameter - self::assertEquals('foo = %s', $expressionData['spec']); - self::assertEquals([$value], $expressionData['values']); + $nested = $predicate->nest; + + self::assertInstanceOf(Predicate::class, $nested); + self::assertNotSame($predicate, $nested); } - #[TestDox('Unit test: Test expression() allows null $parameters')] - public function testExpressionNullParameters(): void + public function testMagicGetUnnestReturnsParentPredicate(): void { $predicate = new Predicate(); - $predicate->expression('foo = bar'); - - $predicates = $predicate->getPredicates(); + $nested = $predicate->nest; + $parent = $nested->unnest; - if (isset($predicates[0][1])) { - $expression = $predicates[0][1]; - $this->assertInstanceOf(Expression::class, $expression); - self::assertEquals([], $expression->getParameters()); - } else { - $this->fail('Expression not found'); - } + self::assertSame($predicate, $parent); } - #[TestDox('Unit test: Test literal() is chainable, returns proper values, and is backwards compatible with 2.0.*')] - public function testLiteral(): void + public function testNotEqualToCreatesOperatorPredicate(): void { $predicate = new Predicate(); + $predicate->notEqualTo('foo.bar', 'bar'); - // is chainable - self::assertSame($predicate, $predicate->literal('foo = bar')); + $identifier = Argument::identifier('foo.bar'); + $expression = Argument::value('bar'); $expressionData = $predicate->getExpressionData(); - // with parameter - self::assertEquals('foo = bar', $expressionData['spec']); - self::assertEquals([], $expressionData['values']); + self::assertEquals('%s != %s', $expressionData['spec']); + self::assertCount(2, $expressionData['values']); + self::assertEquals($identifier, $expressionData['values'][0]); + self::assertEquals($expression, $expressionData['values'][1]); + } - // test literal() is backwards-compatible, and works with with parameters + public function testNotInCreatesNotInPredicate(): void + { $predicate = new Predicate(); - $predicate->expression('foo = ?', 'bar'); - - $expression = Argument::value('bar'); - $expressionData = $predicate->getExpressionData(); - - // with parameter - self::assertEquals('foo = %s', $expressionData['spec']); - self::assertEquals([$expression], $expressionData['values']); + $predicate->notIn('foo.bar', ['foo', 'bar']); - // test literal() is backwards-compatible, and works with with parameters, even 0 which tests as false - $predicate = new Predicate(); - $predicate->expression('foo = ?', 0); + $identifier = Argument::identifier('foo.bar'); + $expression = Argument::values(['foo', 'bar']); - $expression = Argument::value(0); $expressionData = $predicate->getExpressionData(); - // with parameter - self::assertEquals('foo = %s', $expressionData['spec']); - self::assertEquals([$expression], $expressionData['values']); + self::assertEquals('%s NOT IN (%s, %s)', $expressionData['spec']); + self::assertCount(2, $expressionData['values']); + self::assertEquals($identifier, $expressionData['values'][0]); + self::assertEquals($expression, $expressionData['values'][1]); } - public function testUnnestThrowsWhenNotNested(): void + public function testNotLikeCreatesLikePredicate(): void { $predicate = new Predicate(); + $predicate->notLike('foo.bar', 'bar%'); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Not nested'); - $predicate->unnest(); + $identifier = Argument::identifier('foo.bar'); + $expression = Argument::value('bar%'); + + $expressionData = $predicate->getExpressionData(); + + self::assertEquals('%s NOT LIKE %s', $expressionData['spec']); + self::assertCount(2, $expressionData['values']); + self::assertEquals($identifier, $expressionData['values'][0]); + self::assertEquals($expression, $expressionData['values'][1]); } public function testPredicateMethodAddsCustomPredicateInterface(): void @@ -397,24 +405,13 @@ public function testPredicateMethodAddsCustomPredicateInterface(): void self::assertCount(1, $predicate); } - public function testMagicGetNestReturnsNestedPredicate(): void - { - $predicate = new Predicate(); - - $nested = $predicate->nest; - - self::assertInstanceOf(Predicate::class, $nested); - self::assertNotSame($predicate, $nested); - } - - public function testMagicGetUnnestReturnsParentPredicate(): void + public function testUnnestThrowsWhenNotNested(): void { $predicate = new Predicate(); - $nested = $predicate->nest; - $parent = $nested->unnest; - - self::assertSame($predicate, $parent); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Not nested'); + $predicate->unnest(); } /** @@ -433,7 +430,7 @@ public function testWillBindSqlParametersToExpressionsWithGivenParameter(): void self::assertSame( 'SELECT "a_table".* FROM "a_table" WHERE (some_expression(\'\'))', - $actual + $actual, ); } @@ -450,7 +447,7 @@ public function testWillBindSqlParametersToExpressionsWithGivenStringParameter() self::assertSame( 'SELECT "a_table".* FROM "a_table" WHERE (some_expression(\'a string\'))', - $actual + $actual, ); } diff --git a/test/unit/Sql/SelectTest.php b/test/unit/Sql/SelectTest.php index 1dcd508bb..636a66807 100644 --- a/test/unit/Sql/SelectTest.php +++ b/test/unit/Sql/SelectTest.php @@ -75,520 +75,850 @@ final class SelectTest extends TestCase { use AdapterTestTrait; - public function testConstruct(): void - { - $select = new Select('foo'); - self::assertEquals('foo', $select->getRawState('table')); - } - - #[TestDox('unit test: Test from() returns Select object (is chainable)')] - public function testFrom(): void - { - $select = new Select(); - - // First mutation - $result = $select->from('foo'); - - // Verify fluent interface - self::assertSame($select, $result); - - // Verify the first mutation occurred - self::assertEquals('foo', $select->getRawState('table')); - - // Second mutation to verify mutability - $select->from('bar'); - - // Verify the instance was actually mutated - self::assertEquals('bar', $select->getRawState('table')); - } - - #[TestDox('unit test: Test quantifier() returns Select object (is chainable)')] - public function testQuantifier(): void + /** + * @psalm-return array, + * 3: string, + * 4: array, + * 5: bool, + * }> + */ + public static function providerData(): array { - $select = new Select(); - - // First mutation - $result = $select->quantifier(Select::QUANTIFIER_DISTINCT); + // phpcs:disable Generic.Files.LineLength.TooLong + // basic table + $select0 = new Select(); + $select0->from('foo'); - // Verify fluent interface - self::assertSame($select, $result); + $sqlPrep0 = 'SELECT "foo".* FROM "foo"'; + $sqlStr0 = 'SELECT "foo".* FROM "foo"'; + $internalTests0 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + ]; - // Verify the first mutation occurred - self::assertEquals(Select::QUANTIFIER_DISTINCT, $select->getRawState('quantifier')); + // table as TableIdentifier + $select1 = new Select(); + $select1->from(new TableIdentifier('foo', 'bar')); - // Second mutation to verify mutability - $select->quantifier(Select::QUANTIFIER_ALL); + $sqlPrep1 = 'SELECT "bar"."foo".* FROM "bar"."foo"'; + $sqlStr1 = 'SELECT "bar"."foo".* FROM "bar"."foo"'; + $internalTests1 = [ + 'processSelect' => [[['"bar"."foo".*']], '"bar"."foo"'], + ]; - // Verify the instance was actually mutated - self::assertEquals(Select::QUANTIFIER_ALL, $select->getRawState('quantifier')); - } + // table with alias + $select2 = new Select(); + $select2->from(['f' => 'foo']); - #[TestDox('unit test: Test quantifier() accepts expression')] - public function testQuantifierParameterExpressionInterface(): void - { - $expr = $this->getMockBuilder(ExpressionInterface::class)->onlyMethods([])->getMock(); - $select = new Select(); - /** @psalm-suppress InvalidArgument */ - $select->quantifier($expr); - self::assertSame( - $expr, - $select->getRawState(Select::QUANTIFIER) - ); - } + $sqlPrep2 = 'SELECT "f".* FROM "foo" AS "f"'; + $sqlStr2 = 'SELECT "f".* FROM "foo" AS "f"'; + $internalTests2 = [ + 'processSelect' => [[['"f".*']], '"foo" AS "f"'], + ]; - #[TestDox('unit test: Test columns() returns Select object (is chainable)')] - public function testColumns(): void - { - $select = new Select(); + // table with alias with table as TableIdentifier + $select3 = new Select(); + $select3->from(['f' => new TableIdentifier('foo')]); - // First mutation - $result = $select->columns(['foo', 'bar']); + $sqlPrep3 = 'SELECT "f".* FROM "foo" AS "f"'; + $sqlStr3 = 'SELECT "f".* FROM "foo" AS "f"'; + $internalTests3 = [ + 'processSelect' => [[['"f".*']], '"foo" AS "f"'], + ]; - // Verify fluent interface - self::assertSame($select, $result); + // columns + $select4 = new Select(); + $select4->from('foo')->columns(['bar', 'baz']); + $sqlPrep4 = 'SELECT "foo"."bar" AS "bar", "foo"."baz" AS "baz" FROM "foo"'; + $sqlStr4 = 'SELECT "foo"."bar" AS "bar", "foo"."baz" AS "baz" FROM "foo"'; + $internalTests4 = [ + 'processSelect' => [ + [ + ['"foo"."bar"', '"bar"'], + ['"foo"."baz"', '"baz"'], + ], + '"foo"', + ], + ]; - // Verify the first mutation occurred - self::assertEquals(['foo', 'bar'], $select->getRawState('columns')); + // columns with AS associative array + $select5 = new Select(); + $select5->from('foo')->columns(['bar' => 'baz']); + $sqlPrep5 = 'SELECT "foo"."baz" AS "bar" FROM "foo"'; + $sqlStr5 = 'SELECT "foo"."baz" AS "bar" FROM "foo"'; + $internalTests5 = [ + 'processSelect' => [[['"foo"."baz"', '"bar"']], '"foo"'], + ]; - // Second mutation to verify mutability - $select->columns(['baz', 'qux']); + // columns with AS associative array mixed + $select6 = new Select(); + $select6->from('foo')->columns(['bar' => 'baz', 'bam']); + $sqlPrep6 = 'SELECT "foo"."baz" AS "bar", "foo"."bam" AS "bam" FROM "foo"'; + $sqlStr6 = 'SELECT "foo"."baz" AS "bar", "foo"."bam" AS "bam" FROM "foo"'; + $internalTests6 = [ + 'processSelect' => [ + [ + ['"foo"."baz"', '"bar"'], + ['"foo"."bam"', '"bam"'], + ], + '"foo"', + ], + ]; - // Verify the instance was actually mutated - self::assertEquals(['baz', 'qux'], $select->getRawState('columns')); - } + // columns where value is Expression, with AS + $select7 = new Select(); + $select7->from('foo')->columns(['bar' => new Expression('COUNT(some_column)')]); + $sqlPrep7 = 'SELECT COUNT(some_column) AS "bar" FROM "foo"'; + $sqlStr7 = 'SELECT COUNT(some_column) AS "bar" FROM "foo"'; + $internalTests7 = [ + 'processSelect' => [[['COUNT(some_column)', '"bar"']], '"foo"'], + ]; - #[TestDox('unit test: Test isTableReadOnly() returns correct state for read only')] - public function testIsTableReadOnly(): void - { - $select = new Select('foo'); - self::assertTrue($select->isTableReadOnly()); + // columns where value is Expression + $select8 = new Select(); + $select8->from('foo')->columns([new Expression('COUNT(some_column) AS bar')]); + $sqlPrep8 = 'SELECT COUNT(some_column) AS bar FROM "foo"'; + $sqlStr8 = 'SELECT COUNT(some_column) AS bar FROM "foo"'; + $internalTests8 = [ + 'processSelect' => [[['COUNT(some_column) AS bar']], '"foo"'], + ]; - $select = new Select(); - self::assertFalse($select->isTableReadOnly()); - } + // columns where value is Expression with parameters + $select9 = new Select(); + $select9->from('foo') + ->columns( + [ + new Expression( + '(COUNT(?) + ?) AS ?', + [ + new Argument\Identifier('some_column'), + new Argument\Value(5), + new Argument\Identifier('bar'), + ], + ), + ], + ); + $sqlPrep9 = 'SELECT (COUNT("some_column") + ?) AS "bar" FROM "foo"'; + $sqlStr9 = 'SELECT (COUNT("some_column") + \'5\') AS "bar" FROM "foo"'; + $params9 = ['column1' => 5]; + $internalTests9 = [ + 'processSelect' => [[['(COUNT("some_column") + ?) AS "bar"']], '"foo"'], + ]; - #[TestDox('unit test: Test join() returns same Select object (is chainable)')] - public function testJoin(): void - { - $select = new Select(); + // joins (plain) + $select10 = new Select(); + $select10->from('foo')->join('zac', 'm = n'); + $sqlPrep10 = 'SELECT "foo".*, "zac".* FROM "foo" INNER JOIN "zac" ON "m" = "n"'; + $sqlStr10 = 'SELECT "foo".*, "zac".* FROM "foo" INNER JOIN "zac" ON "m" = "n"'; + $internalTests10 = [ + 'processSelect' => [[['"foo".*'], ['"zac".*']], '"foo"'], + 'processJoins' => [[['INNER', '"zac"', '"m" = "n"']]], + ]; - // First mutation - $result = $select->join('foo', 'x = y'); + // join with columns + $select11 = new Select(); + $select11->from('foo')->join('zac', 'm = n', ['bar', 'baz']); + $sqlPrep11 = 'SELECT "foo".*, "zac"."bar" AS "bar", "zac"."baz" AS "baz" FROM "foo" INNER JOIN "zac" ON "m" = "n"'; + $sqlStr11 = 'SELECT "foo".*, "zac"."bar" AS "bar", "zac"."baz" AS "baz" FROM "foo" INNER JOIN "zac" ON "m" = "n"'; + $internalTests11 = [ + 'processSelect' => [ + [ + ['"foo".*'], + ['"zac"."bar"', '"bar"'], + ['"zac"."baz"', '"baz"'], + ], + '"foo"', + ], + 'processJoins' => [[['INNER', '"zac"', '"m" = "n"']]], + ]; - // Verify fluent interface - self::assertSame($select, $result); + // join with alternate type + $select12 = new Select(); + $select12->from('foo')->join('zac', 'm = n', ['bar', 'baz'], Select::JOIN_OUTER); + $sqlPrep12 = 'SELECT "foo".*, "zac"."bar" AS "bar", "zac"."baz" AS "baz" FROM "foo" OUTER JOIN "zac" ON "m" = "n"'; + $sqlStr12 = 'SELECT "foo".*, "zac"."bar" AS "bar", "zac"."baz" AS "baz" FROM "foo" OUTER JOIN "zac" ON "m" = "n"'; + $internalTests12 = [ + 'processSelect' => [ + [ + ['"foo".*'], + ['"zac"."bar"', '"bar"'], + ['"zac"."baz"', '"baz"'], + ], + '"foo"', + ], + 'processJoins' => [[['OUTER', '"zac"', '"m" = "n"']]], + ]; - // Verify the first mutation occurred - $joins = $select->getRawState('joins'); - self::assertInstanceOf(Join::class, $joins); - self::assertEquals( - [ + // join with column aliases + $select13 = new Select(); + $select13->from('foo')->join('zac', 'm = n', ['BAR' => 'bar', 'BAZ' => 'baz']); + $sqlPrep13 = 'SELECT "foo".*, "zac"."bar" AS "BAR", "zac"."baz" AS "BAZ" FROM "foo" INNER JOIN "zac" ON "m" = "n"'; + $sqlStr13 = 'SELECT "foo".*, "zac"."bar" AS "BAR", "zac"."baz" AS "BAZ" FROM "foo" INNER JOIN "zac" ON "m" = "n"'; + $internalTests13 = [ + 'processSelect' => [ [ - 'name' => 'foo', - 'on' => 'x = y', - 'columns' => [Select::SQL_STAR], - 'type' => Select::JOIN_INNER, + ['"foo".*'], + ['"zac"."bar"', '"BAR"'], + ['"zac"."baz"', '"BAZ"'], ], + '"foo"', ], - $joins->getJoins() - ); + 'processJoins' => [[['INNER', '"zac"', '"m" = "n"']]], + ]; - // Second mutation to verify mutability (joins accumulate) - $select->join('bar', 'a = b'); + // join with table aliases + $select14 = new Select(); + $select14->from('foo')->join(['b' => 'bar'], 'b.foo_id = foo.foo_id'); + $sqlPrep14 = 'SELECT "foo".*, "b".* FROM "foo" INNER JOIN "bar" AS "b" ON "b"."foo_id" = "foo"."foo_id"'; + $sqlStr14 = 'SELECT "foo".*, "b".* FROM "foo" INNER JOIN "bar" AS "b" ON "b"."foo_id" = "foo"."foo_id"'; + $internalTests14 = [ + 'processSelect' => [[['"foo".*'], ['"b".*']], '"foo"'], + 'processJoins' => [[['INNER', '"bar" AS "b"', '"b"."foo_id" = "foo"."foo_id"']]], + ]; - // Verify the instance was actually mutated - $joins2 = $select->getRawState('joins'); - self::assertCount(2, $joins2->getJoins()); - self::assertEquals('bar', $joins2->getJoins()[1]['name']); - } + // where (simple string) + $select15 = new Select(); + $select15->from('foo')->where('x = 5'); + $sqlPrep15 = 'SELECT "foo".* FROM "foo" WHERE x = 5'; + $sqlStr15 = 'SELECT "foo".* FROM "foo" WHERE x = 5'; + $internalTests15 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processWhere' => ['x = 5'], + ]; - #[TestDox('unit test: Test join() exception with bad join')] - public function testBadJoin(): void - { - $select = new Select(); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("expects 'foo' as"); - $select->join(['foo'], 'x = y'); - } - - #[TestDox('unit test: Test where() returns Select object (is chainable)')] - public function testWhereReturnsSameSelectObject(): void - { - $select = new Select(); - self::assertSame($select, $select->where('x = y')); - } - - #[TestDox('unit test: Test where() will accept a string for the predicate to create an expression predicate')] - public function testWhereArgument1IsString(): void - { - $select = new Select(); - $select->where('x = ?'); - - /** @var Where $where */ - $where = $select->getRawState('where'); - $predicates = $where->getPredicates(); - self::assertCount(1, $predicates); - self::assertIsArray($predicates[0]); - self::assertInstanceOf(Predicate\Expression::class, $predicates[0][1]); - self::assertEquals(Predicate\PredicateSet::OP_AND, $predicates[0][0]); - self::assertEquals('x = ?', $predicates[0][1]->getExpression()); + // where (returning parameters) + $select16 = new Select(); + $select16->from('foo')->where(['x = ?' => 5]); + $sqlPrep16 = 'SELECT "foo".* FROM "foo" WHERE x = ?'; + $sqlStr16 = 'SELECT "foo".* FROM "foo" WHERE x = \'5\''; + $params16 = ['where1' => 5]; + $internalTests16 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processWhere' => ['x = ?'], + ]; - $select = new Select(); - $select->where('x = y'); + // group + $select17 = new Select(); + $select17->from('foo')->group(['col1', 'col2']); + $sqlPrep17 = 'SELECT "foo".* FROM "foo" GROUP BY "col1", "col2"'; + $sqlStr17 = 'SELECT "foo".* FROM "foo" GROUP BY "col1", "col2"'; + $internalTests17 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processGroup' => [['"col1"', '"col2"']], + ]; - /** @var Where $where */ - $where = $select->getRawState('where'); - $predicates = $where->getPredicates(); - self::assertIsArray($predicates[0]); - self::assertInstanceOf(Literal::class, $predicates[0][1]); - } + $select18 = new Select(); + $select18->from('foo')->group('col1')->group('col2'); + $sqlPrep18 = 'SELECT "foo".* FROM "foo" GROUP BY "col1", "col2"'; + $sqlStr18 = 'SELECT "foo".* FROM "foo" GROUP BY "col1", "col2"'; + $internalTests18 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processGroup' => [['"col1"', '"col2"']], + ]; - #[TestDox('unit test: Test where() will accept an array with a string key (containing ?) used as an - expression with placeholder')] - public function testWhereArgument1IsAssociativeArrayContainingReplacementCharacter(): void - { - $select = new Select(); - $select->where(['foo > ?' => 5]); + $select19 = new Select(); + $select19->from('foo')->group(new Expression('DAY(?)', [new Argument\Identifier('col1')])); + $sqlPrep19 = 'SELECT "foo".* FROM "foo" GROUP BY DAY("col1")'; + $sqlStr19 = 'SELECT "foo".* FROM "foo" GROUP BY DAY("col1")'; + $internalTests19 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processGroup' => [['DAY("col1")']], + ]; - /** @var Where $where */ - $where = $select->getRawState('where'); - $predicates = $where->getPredicates(); - $expression = new Value(5); + // having (simple string) + $select20 = new Select(); + $select20->from('foo')->having('x = 5'); + $sqlPrep20 = 'SELECT "foo".* FROM "foo" HAVING x = 5'; + $sqlStr20 = 'SELECT "foo".* FROM "foo" HAVING x = 5'; + $internalTests20 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processHaving' => ['x = 5'], + ]; - self::assertCount(1, $predicates); - self::assertIsArray($predicates[0]); - self::assertInstanceOf(Predicate\Expression::class, $predicates[0][1]); - self::assertEquals(Predicate\PredicateSet::OP_AND, $predicates[0][0]); - self::assertEquals('foo > ?', $predicates[0][1]->getExpression()); - self::assertEquals([$expression], $predicates[0][1]->getParameters()); - } + // having (returning parameters) + $select21 = new Select(); + $select21->from('foo')->having(['x = ?' => 5]); + $sqlPrep21 = 'SELECT "foo".* FROM "foo" HAVING x = ?'; + $sqlStr21 = 'SELECT "foo".* FROM "foo" HAVING x = \'5\''; + $params21 = ['having1' => 5]; + $internalTests21 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processHaving' => ['x = ?'], + ]; - #[TestDox('unit test: Test where() will accept any array with string key (without ?) to be used - as Operator predicate')] - public function testWhereArgument1IsAssociativeArrayNotContainingReplacementCharacter(): void - { - $select = new Select(); - $select->where(['name' => 'Ralph', 'age' => 33]); + // order + $select22 = new Select(); + $select22->from('foo')->order('c1'); + $sqlPrep22 = 'SELECT "foo".* FROM "foo" ORDER BY "c1" ASC'; + $sqlStr22 = 'SELECT "foo".* FROM "foo" ORDER BY "c1" ASC'; + $internalTests22 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processOrder' => [[['"c1"', Select::ORDER_ASCENDING]]], + ]; - $identifier1 = new Identifier('name'); - $expression1 = new Value('Ralph'); - $identifier2 = new Identifier('age'); - $expression2 = new Value(33); + $select23 = new Select(); + $select23->from('foo')->order(['c1', 'c2']); + $sqlPrep23 = 'SELECT "foo".* FROM "foo" ORDER BY "c1" ASC, "c2" ASC'; + $sqlStr23 = 'SELECT "foo".* FROM "foo" ORDER BY "c1" ASC, "c2" ASC'; + $internalTests23 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processOrder' => [[ + ['"c1"', Select::ORDER_ASCENDING], + ['"c2"', Select::ORDER_ASCENDING], + ]], + ]; - /** @var Where $where */ - $where = $select->getRawState('where'); - $predicates = $where->getPredicates(); - self::assertCount(2, $predicates); - self::assertIsArray($predicates[0]); - self::assertIsArray($predicates[1]); + $select24 = new Select(); + $select24->from('foo')->order(['c1' => 'DESC', 'c2' => 'Asc']); + // notice partially lower case ASC + $sqlPrep24 = 'SELECT "foo".* FROM "foo" ORDER BY "c1" DESC, "c2" ASC'; + $sqlStr24 = 'SELECT "foo".* FROM "foo" ORDER BY "c1" DESC, "c2" ASC'; + $internalTests24 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processOrder' => [[ + ['"c1"', Select::ORDER_DESCENDING], + ['"c2"', Select::ORDER_ASCENDING], + ]], + ]; - self::assertInstanceOf(Operator::class, $predicates[0][1]); - self::assertEquals(Predicate\PredicateSet::OP_AND, $predicates[0][0]); - self::assertEquals($identifier1, $predicates[0][1]->getLeft()); - self::assertEquals($expression1, $predicates[0][1]->getRight()); + $select25 = new Select(); + $select25->from('foo')->order(['c1' => 'asc'])->order('c2 desc'); + // notice partially lower case ASC + $sqlPrep25 = 'SELECT "foo".* FROM "foo" ORDER BY "c1" ASC, "c2" DESC'; + $sqlStr25 = 'SELECT "foo".* FROM "foo" ORDER BY "c1" ASC, "c2" DESC'; + $internalTests25 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processOrder' => [[ + ['"c1"', Select::ORDER_ASCENDING], + ['"c2"', Select::ORDER_DESCENDING], + ]], + ]; - self::assertInstanceOf(Operator::class, $predicates[1][1]); - self::assertEquals(Predicate\PredicateSet::OP_AND, $predicates[1][0]); - self::assertEquals($identifier2, $predicates[1][1]->getLeft()); - self::assertEquals($expression2, $predicates[1][1]->getRight()); + // limit + $select26 = new Select(); + $select26->from('foo')->limit(5); + $sqlPrep26 = 'SELECT "foo".* FROM "foo" LIMIT ?'; + $sqlStr26 = 'SELECT "foo".* FROM "foo" LIMIT \'5\''; + $params26 = ['limit' => 5]; + $internalTests26 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processLimit' => ['?'], + ]; - $select = new Select(); - $select->where(['x = y']); + // limit with offset + $select27 = new Select(); + $select27->from('foo')->limit(5)->offset(10); + $sqlPrep27 = 'SELECT "foo".* FROM "foo" LIMIT ? OFFSET ?'; + $sqlStr27 = 'SELECT "foo".* FROM "foo" LIMIT \'5\' OFFSET \'10\''; + $params27 = ['limit' => 5, 'offset' => 10]; + $internalTests27 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processLimit' => ['?'], + 'processOffset' => ['?'], + ]; - /** @var Where $where */ - $where = $select->getRawState('where'); - $predicates = $where->getPredicates(); - self::assertIsArray($predicates[0]); - self::assertInstanceOf(Literal::class, $predicates[0][1]); - } + // joins with a few keywords in the on clause + $select28 = new Select(); + $select28->from('foo')->join( + 'zac', + '(m = n AND c.x) BETWEEN x AND y.z OR (c.x < y.z AND c.x <= y.z AND c.x > y.z AND c.x >= y.z)', + ); + $sqlPrep28 = 'SELECT "foo".*, "zac".* FROM "foo" INNER JOIN "zac" ON ("m" = "n" AND "c"."x") BETWEEN "x" AND "y"."z" OR ("c"."x" < "y"."z" AND "c"."x" <= "y"."z" AND "c"."x" > "y"."z" AND "c"."x" >= "y"."z")'; + $sqlStr28 = 'SELECT "foo".*, "zac".* FROM "foo" INNER JOIN "zac" ON ("m" = "n" AND "c"."x") BETWEEN "x" AND "y"."z" OR ("c"."x" < "y"."z" AND "c"."x" <= "y"."z" AND "c"."x" > "y"."z" AND "c"."x" >= "y"."z")'; + $internalTests28 = [ + 'processSelect' => [[['"foo".*'], ['"zac".*']], '"foo"'], + 'processJoins' => [[[ + 'INNER', + '"zac"', + '("m" = "n" AND "c"."x") BETWEEN "x" AND "y"."z" OR ("c"."x" < "y"."z" AND "c"."x" <= "y"."z" AND "c"."x" > "y"."z" AND "c"."x" >= "y"."z")', + ]]], + ]; - #[TestDox(' - unit test: Test where() will accept any array with string key (without ?) with Predicate throw Exception - ')] - public function testWhereArgument1IsAssociativeArrayIsPredicate(): void - { - $select = new Select(); - $where = [ - 'name' => new Predicate\Literal("name = 'Ralph'"), - 'age' => new Predicate\Expression('age = ?', 33), + // order with compound name + $select29 = new Select(); + $select29->from('foo')->order('c1.d2'); + $sqlPrep29 = 'SELECT "foo".* FROM "foo" ORDER BY "c1"."d2" ASC'; + $sqlStr29 = 'SELECT "foo".* FROM "foo" ORDER BY "c1"."d2" ASC'; + $internalTests29 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processOrder' => [[['"c1"."d2"', Select::ORDER_ASCENDING]]], ]; - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Using Predicate must not use string keys'); - $select->where($where); - } - #[TestDox('unit test: Test where() will accept an indexed array to be used by joining string expressions')] - public function testWhereArgument1IsIndexedArray(): void - { - $select = new Select(); - $select->where(['name = "Ralph"']); + // group with compound name + $select30 = new Select(); + $select30->from('foo')->group('c1.d2'); + $sqlPrep30 = 'SELECT "foo".* FROM "foo" GROUP BY "c1"."d2"'; + $sqlStr30 = 'SELECT "foo".* FROM "foo" GROUP BY "c1"."d2"'; + $internalTests30 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processGroup' => [['"c1"."d2"']], + ]; - /** @var Where $where */ - $where = $select->getRawState('where'); - $predicates = $where->getPredicates(); - self::assertCount(1, $predicates); - self::assertIsArray($predicates[0]); - self::assertInstanceOf(Literal::class, $predicates[0][1]); - self::assertEquals(Predicate\PredicateSet::OP_AND, $predicates[0][0]); - self::assertEquals('name = "Ralph"', $predicates[0][1]->getLiteral()); - } + // join with expression in ON part + $select31 = new Select(); + $select31->from('foo')->join('zac', new Predicate\Expression('(m = n AND c.x) BETWEEN x AND y.z')); + $sqlPrep31 = 'SELECT "foo".*, "zac".* FROM "foo" INNER JOIN "zac" ON (m = n AND c.x) BETWEEN x AND y.z'; + $sqlStr31 = 'SELECT "foo".*, "zac".* FROM "foo" INNER JOIN "zac" ON (m = n AND c.x) BETWEEN x AND y.z'; + $internalTests31 = [ + 'processSelect' => [[['"foo".*'], ['"zac".*']], '"foo"'], + 'processJoins' => [[['INNER', '"zac"', '(m = n AND c.x) BETWEEN x AND y.z']]], + ]; - #[TestDox('unit test: Test where() will accept an indexed array to be used by joining string expressions, - combined by OR')] - public function testWhereArgument1IsIndexedArrayArgument2IsOr(): void - { - $select = new Select(); - $select->where(['name = "Ralph"'], Predicate\PredicateSet::OP_OR); + $select32subselect = new Select(); + $select32subselect->from('bar')->where->like('y', '%Foo%'); + $select32 = new Select(); + $select32->from(['x' => $select32subselect]); - /** @var Where $where */ - $where = $select->getRawState('where'); - $predicates = $where->getPredicates(); - self::assertCount(1, $predicates); - self::assertIsArray($predicates[0]); - self::assertInstanceOf(Literal::class, $predicates[0][1]); - self::assertEquals(Predicate\PredicateSet::OP_OR, $predicates[0][0]); - self::assertEquals('name = "Ralph"', $predicates[0][1]->getLiteral()); - } + $sqlPrep32 = 'SELECT "x".* FROM (SELECT "bar".* FROM "bar" WHERE "y" LIKE ?) AS "x"'; + $sqlStr32 = 'SELECT "x".* FROM (SELECT "bar".* FROM "bar" WHERE "y" LIKE \'%Foo%\') AS "x"'; + $internalTests32 = [ + 'processSelect' => [[['"x".*']], '(SELECT "bar".* FROM "bar" WHERE "y" LIKE ?) AS "x"'], + ]; - #[TestDox('unit test: Test where() will accept a closure to be executed with Where object as argument')] - public function testWhereArgument1IsClosure(): void - { - $select = new Select(); - /** @var Where $where */ - $where = $select->getRawState('where'); + $select33 = new Select(); + $select33->from('table') + ->columns(['*']) + ->where([ + 'c1' => null, + 'c2' => [1, 2, 3], + new IsNotNull('c3'), + ]); + $sqlPrep33 = 'SELECT "table".* FROM "table" WHERE "c1" IS NULL AND "c2" IN (?, ?, ?) AND "c3" IS NOT NULL'; + $sqlStr33 = 'SELECT "table".* FROM "table" WHERE "c1" IS NULL AND "c2" IN (\'1\', \'2\', \'3\') AND "c3" IS NOT NULL'; + $internalTests33 = [ + 'processSelect' => [[['"table".*']], '"table"'], + 'processWhere' => ['"c1" IS NULL AND "c2" IN (?, ?, ?) AND "c3" IS NOT NULL'], + ]; - $select->where(function (Where $what) use ($where): void { - self::assertSame($where, $what); - }); - } + // @author Demian Katz + $select34 = new Select(); + $select34->from('table') + ->order([ + new Expression('isnull(?) DESC', [new Argument\Identifier('name')]), + 'name', + ]); + $sqlPrep34 = 'SELECT "table".* FROM "table" ORDER BY isnull("name") DESC, "name" ASC'; + $sqlStr34 = 'SELECT "table".* FROM "table" ORDER BY isnull("name") DESC, "name" ASC'; + $internalTests34 = [ + 'processOrder' => [[['isnull("name") DESC'], ['"name"', Select::ORDER_ASCENDING]]], + ]; - #[TestDox('unit test: Test where() will accept any Predicate object as-is')] - public function testWhereArgument1IsPredicate(): void - { - $select = new Select(); - $predicate = new Predicate\Predicate([ - new Predicate\Expression('name = ?', 'Ralph'), - new Predicate\Expression('age = ?', 33), - ]); - $select->where($predicate); + // join with Expression object in COLUMNS part (Laminas-514) + // @co-author Koen Pieters (kpieters) + $select35 = new Select(); + $select35->from('foo') + ->columns([]) + ->join('bar', 'm = n', ['thecount' => new Expression('COUNT(*)')]); + $sqlPrep35 = 'SELECT COUNT(*) AS "thecount" FROM "foo" INNER JOIN "bar" ON "m" = "n"'; + $sqlStr35 = 'SELECT COUNT(*) AS "thecount" FROM "foo" INNER JOIN "bar" ON "m" = "n"'; + $internalTests35 = [ + 'processSelect' => [[['COUNT(*)', '"thecount"']], '"foo"'], + 'processJoins' => [[['INNER', '"bar"', '"m" = "n"']]], + ]; - /** @var Where $where */ - $where = $select->getRawState('where'); - $predicates = $where->getPredicates(); - self::assertIsArray($predicates[0]); - self::assertSame($predicate, $predicates[0][1]); - } + // multiple joins with expressions + // reported by @jdolieslager + $select36 = new Select(); + $select36->from('foo') + ->join('tableA', new Predicate\Operator('id', '=', 1)) + ->join('tableB', new Predicate\Operator('id', '=', 2)) + ->join('tableC', new Predicate\PredicateSet([ + new Predicate\Operator('id', '=', 3), + new Predicate\Operator('number', '>', 20), + ])); + $sqlPrep36 = + 'SELECT "foo".*, "tableA".*, "tableB".*, "tableC".* FROM "foo"' + . ' INNER JOIN "tableA" ON "id" = :join1part1 INNER JOIN "tableB" ON "id" = :join2part1 ' + . 'INNER JOIN "tableC" ON "id" = :join3part1 AND "number" > :join3part2'; + $sqlStr36 = + 'SELECT "foo".*, "tableA".*, "tableB".*, "tableC".* FROM "foo" ' + . 'INNER JOIN "tableA" ON "id" = \'1\' INNER JOIN "tableB" ON "id" = \'2\' ' + . 'INNER JOIN "tableC" ON "id" = \'3\' AND "number" > \'20\''; + $internalTests36 = []; - #[TestDox('unit test: Test where() will accept a Where object')] - public function testWhereArgument1IsWhereObject(): void - { - $select = new Select(); - $select->where($newWhere = new Where()); - self::assertSame($newWhere, $select->getRawState('where')); - } + /** + * @link https://github.com/zendframework/zf2/pull/2714 + */ + $select37 = new Select(); + $select37->from('foo')->columns(['bar'], false); + $sqlPrep37 = 'SELECT "bar" AS "bar" FROM "foo"'; + $sqlStr37 = 'SELECT "bar" AS "bar" FROM "foo"'; + $internalTests37 = [ + 'processSelect' => [[['"bar"', '"bar"']], '"foo"'], + ]; - /** - * @throws ReflectionException - */ - #[TestDox('unit test: Test order()')] - public function testOrder(): void - { - $select = new Select(); - $return = $select->order('id DESC'); - self::assertSame($select, $return); // test fluent interface - self::assertEquals(['id DESC'], $select->getRawState('order')); + // @link https://github.com/zendframework/zf2/issues/3294 + // Test TableIdentifier In Joins + $select38 = new Select(); + $select38->from('foo') + ->columns([]) + ->join(new TableIdentifier('bar', 'baz'), 'm = n', ['thecount' => new Expression('COUNT(*)')]); + $sqlPrep38 = 'SELECT COUNT(*) AS "thecount" FROM "foo" INNER JOIN "baz"."bar" ON "m" = "n"'; + $sqlStr38 = 'SELECT COUNT(*) AS "thecount" FROM "foo" INNER JOIN "baz"."bar" ON "m" = "n"'; + $internalTests38 = [ + 'processSelect' => [[['COUNT(*)', '"thecount"']], '"foo"'], + 'processJoins' => [[['INNER', '"baz"."bar"', '"m" = "n"']]], + ]; - $select = new Select(); - $select->order('id DESC') - ->order('name ASC, age DESC'); - self::assertEquals(['id DESC', 'name ASC', 'age DESC'], $select->getRawState('order')); + // subselect in join + $select39subselect = new Select(); + $select39subselect->from('bar')->where->like('y', '%Foo%'); + $select39 = new Select(); + $select39->from('foo')->join(['z' => $select39subselect], 'z.foo = bar.id'); + $sqlPrep39 = 'SELECT "foo".*, "z".* FROM "foo" INNER JOIN (SELECT "bar".* FROM "bar" WHERE "y" LIKE ?) AS "z" ON "z"."foo" = "bar"."id"'; + $sqlStr39 = 'SELECT "foo".*, "z".* FROM "foo" INNER JOIN (SELECT "bar".* FROM "bar" WHERE "y" LIKE \'%Foo%\') AS "z" ON "z"."foo" = "bar"."id"'; + $internalTests39 = [ + 'processJoins' => [ + [['INNER', '(SELECT "bar".* FROM "bar" WHERE "y" LIKE ?) AS "z"', '"z"."foo" = "bar"."id"']], + ], + ]; - $select = new Select(); - $select->order(['name ASC', 'age DESC']); - self::assertEquals(['name ASC', 'age DESC'], $select->getRawState('order')); + // @link https://github.com/zendframework/zf2/issues/3294 + // Test TableIdentifier In Joins, with multiple joins + $select40 = new Select(); + $select40->from('foo') + ->join(['a' => new TableIdentifier('another_foo', 'another_schema')], 'a.x = foo.foo_column') + ->join('bar', 'foo.colx = bar.colx'); + $sqlPrep40 = + 'SELECT "foo".*, "a".*, "bar".* FROM "foo"' + . ' INNER JOIN "another_schema"."another_foo" AS "a" ON "a"."x" = "foo"."foo_column"' + . ' INNER JOIN "bar" ON "foo"."colx" = "bar"."colx"'; + $sqlStr40 = + 'SELECT "foo".*, "a".*, "bar".* FROM "foo"' + . ' INNER JOIN "another_schema"."another_foo" AS "a" ON "a"."x" = "foo"."foo_column"' + . ' INNER JOIN "bar" ON "foo"."colx" = "bar"."colx"'; + $internalTests40 = [ + 'processSelect' => [[['"foo".*'], ['"a".*'], ['"bar".*']], '"foo"'], + 'processJoins' => [ + [ + ['INNER', '"another_schema"."another_foo" AS "a"', '"a"."x" = "foo"."foo_column"'], + ['INNER', '"bar"', '"foo"."colx" = "bar"."colx"'], + ], + ], + ]; - $select = new Select(); - $select->order(new Expression('RAND()')); + $select41 = new Select(); + $select41->from('foo')->quantifier(Select::QUANTIFIER_DISTINCT); + $sqlPrep41 = 'SELECT DISTINCT "foo".* FROM "foo"'; + $sqlStr41 = 'SELECT DISTINCT "foo".* FROM "foo"'; + $internalTests41 = [ + 'processSelect' => [Select::QUANTIFIER_DISTINCT, [['"foo".*']], '"foo"'], + ]; - $sr = new ReflectionObject($select); - $method = $sr->getMethod('processOrder'); - self::assertEquals( - [[['RAND()']]], - $method->invokeArgs($select, [new TrustingSql92Platform()]) - ); + $select42 = new Select(); + $select42->from('foo')->quantifier(new Expression('TOP ?', [10])); + $sqlPrep42 = 'SELECT TOP ? "foo".* FROM "foo"'; + $sqlStr42 = 'SELECT TOP \'10\' "foo".* FROM "foo"'; + $internalTests42 = [ + 'processSelect' => ['TOP ?', [['"foo".*']], '"foo"'], + ]; - $select = new Select(); - /** @psalm-suppress InvalidArgument - mocked Operator */ - $select->order( - $this->getMockBuilder(Operator::class) - ->onlyMethods([]) - ->setConstructorArgs(['rating', '<', '10']) - ->getMock() - ); - $sr = new ReflectionObject($select); - $method = $sr->getMethod('processOrder'); - self::assertEquals( - [[['"rating" < \'10\'']]], - $method->invokeArgs($select, [new TrustingSql92Platform()]) - ); - } + $select43 = new Select(); + $select43->from(['x' => 'foo'])->columns(['bar' => 'foo.bar'], false); + $sqlPrep43 = 'SELECT "foo"."bar" AS "bar" FROM "foo" AS "x"'; + $sqlStr43 = 'SELECT "foo"."bar" AS "bar" FROM "foo" AS "x"'; + $internalTests43 = [ + 'processSelect' => [[['"foo"."bar"', '"bar"']], '"foo" AS "x"'], + ]; - #[TestDox('unit test: Test order() correctly splits parameters.')] - public function testOrderCorrectlySplitsParameter(): void - { - $select = new Select(); - $select->order('name desc'); - self::assertEquals( - 'SELECT * ORDER BY "name" DESC', - $select->getSqlString(new TrustingSql92Platform()) - ); - } + $select44 = new Select(); + $select44->from('foo')->where('a = b'); + $select44b = new Select(); + $select44b->from('bar')->where('c = d'); + $select44->combine($select44b, Select::COMBINE_UNION, 'ALL'); + $sqlPrep44 = '( SELECT "foo".* FROM "foo" WHERE a = b ) UNION ALL ( SELECT "bar".* FROM "bar" WHERE c = d )'; + $sqlStr44 = '( SELECT "foo".* FROM "foo" WHERE a = b ) UNION ALL ( SELECT "bar".* FROM "bar" WHERE c = d )'; + $internalTests44 = [ + 'processCombine' => ['UNION ALL', 'SELECT "bar".* FROM "bar" WHERE c = d'], + ]; - #[TestDox(': unit test: test limit()')] - public function testLimit(): void - { - $select = new Select(); + // limit with offset + $select45 = new Select(); + $select45->from('foo')->limit('5')->offset('10'); + $sqlPrep45 = 'SELECT "foo".* FROM "foo" LIMIT ? OFFSET ?'; + $sqlStr45 = 'SELECT "foo".* FROM "foo" LIMIT \'5\' OFFSET \'10\''; + $params45 = ['limit' => 5, 'offset' => 10]; + $internalTests45 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processLimit' => ['?'], + 'processOffset' => ['?'], + ]; - // First mutation - $result = $select->limit(5); + // functions without table + $select46 = new Select(); + $select46->columns([ + new Expression('SOME_DB_FUNCTION_ONE()'), + 'foo' => new Expression('SOME_DB_FUNCTION_TWO()'), + ]); + $sqlPrep46 = 'SELECT SOME_DB_FUNCTION_ONE() AS Expression1, SOME_DB_FUNCTION_TWO() AS "foo"'; + $sqlStr46 = 'SELECT SOME_DB_FUNCTION_ONE() AS Expression1, SOME_DB_FUNCTION_TWO() AS "foo"'; + $params46 = []; + $internalTests46 = []; - // Verify fluent interface - self::assertSame($select, $result); + // limit with big offset and limit + $select47 = new Select(); + $select47->from('foo')->limit('10000000000000000000')->offset('10000000000000000000'); + $sqlPrep47 = 'SELECT "foo".* FROM "foo" LIMIT ? OFFSET ?'; + $sqlStr47 = 'SELECT "foo".* FROM "foo" LIMIT \'10000000000000000000\' OFFSET \'10000000000000000000\''; + $params47 = ['limit' => 10_000_000_000_000_000_000, 'offset' => 10_000_000_000_000_000_000]; + $internalTests47 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processLimit' => ['?'], + 'processOffset' => ['?'], + ]; - // Verify the first mutation occurred - $limit = $select->getRawState(Select::LIMIT); - self::assertIsNumeric($limit); - self::assertEquals(5, $limit); + //combine and union with order at the end + $select48 = new Select(); + $select48->from('foo')->where('a = b'); + $select48b = new Select(); + $select48b->from('bar')->where('c = d'); + $select48->combine($select48b); - // Second mutation to verify mutability - $select->limit(10); + $select48combined = new Select(); + $select48 = $select48combined->from(['sub' => $select48])->order('id DESC'); + $sqlPrep48 = 'SELECT "sub".* FROM (( SELECT "foo".* FROM "foo" WHERE a = b ) UNION ( SELECT "bar".* FROM "bar" WHERE c = d )) AS "sub" ORDER BY "id" DESC'; + $sqlStr48 = 'SELECT "sub".* FROM (( SELECT "foo".* FROM "foo" WHERE a = b ) UNION ( SELECT "bar".* FROM "bar" WHERE c = d )) AS "sub" ORDER BY "id" DESC'; + $internalTests48 = [ + 'processCombine' => null, + ]; - // Verify the instance was actually mutated - self::assertEquals(10, $select->getRawState(Select::LIMIT)); - } + //Expression as joinName + $select49 = new Select(); + $select49->from(new TableIdentifier('foo')) + ->join(['bar' => new Expression('psql_function_which_returns_table')], 'foo.id = bar.fooid'); + $sqlPrep49 = 'SELECT "foo".*, "bar".* FROM "foo" INNER JOIN psql_function_which_returns_table AS "bar" ON "foo"."id" = "bar"."fooid"'; + $sqlStr49 = 'SELECT "foo".*, "bar".* FROM "foo" INNER JOIN psql_function_which_returns_table AS "bar" ON "foo"."id" = "bar"."fooid"'; + $internalTests49 = [ + 'processSelect' => [[['"foo".*'], ['"bar".*']], '"foo"'], + 'processJoins' => [[[ + 'INNER', + 'psql_function_which_returns_table AS "bar"', + '"foo"."id" = "bar"."fooid"', + ]]], + ]; - #[TestDox(': unit test: test limit() throws exception when invalid parameter passed')] - public function testLimitExceptionOnInvalidParameter(): void - { - $select = new Select(); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage(Select::class . '::limit expects parameter to be numeric'); - $select->limit('foobar'); - } + // Test generic predicate is appended with AND + $select50 = new Select(); + $select50->from(new TableIdentifier('foo'))->where->nest->isNull('bar') + ->and->predicate(new Predicate\Literal('1=1')); + $sqlPrep50 = 'SELECT "foo".* FROM "foo" WHERE ("bar" IS NULL AND 1=1)'; + $sqlStr50 = 'SELECT "foo".* FROM "foo" WHERE ("bar" IS NULL AND 1=1)'; + $internalTests50 = []; - #[TestDox(': unit test: test offset()')] - public function testOffset(): void - { - $select = new Select(); + // Test generic predicate is appended with OR + $select51 = new Select(); + $select51->from(new TableIdentifier('foo'))->where->nest->isNull('bar') + ->or->predicate(new Predicate\Literal('1=1')); + $sqlPrep51 = 'SELECT "foo".* FROM "foo" WHERE ("bar" IS NULL OR 1=1)'; + $sqlStr51 = 'SELECT "foo".* FROM "foo" WHERE ("bar" IS NULL OR 1=1)'; + $internalTests51 = []; - // First mutation - $result = $select->offset(10); + /** + * @link https://github.com/zendframework/zf2/issues/7222 + */ + $select52 = new Select(); + $select52->from('foo')->join('zac', '(catalog_category_website.category_id = catalog_category.category_id)'); + $sqlPrep52 = 'SELECT "foo".*, "zac".* FROM "foo" INNER JOIN "zac" ON ("catalog_category_website"."category_id" = "catalog_category"."category_id")'; + $sqlStr52 = 'SELECT "foo".*, "zac".* FROM "foo" INNER JOIN "zac" ON ("catalog_category_website"."category_id" = "catalog_category"."category_id")'; + $internalTests52 = [ + 'processSelect' => [[['"foo".*'], ['"zac".*']], '"foo"'], + 'processJoins' => [ + [ + ['INNER', '"zac"', '("catalog_category_website"."category_id" = "catalog_category"."category_id")'], + ], + ], + ]; - // Verify fluent interface - self::assertSame($select, $result); + $subSelect53 = new Select(); + $subSelect53->from('bar')->columns(['id'])->limit(10)->offset(9); + $select53 = new Select(); + $select53->from('foo')->where(new In('bar_id', $subSelect53))->limit(11)->offset(12); + $params53 = ['limit' => 11, 'offset' => 12, 'subselect1limit' => 10, 'subselect1offset' => 9]; + $sqlPrep53 = 'SELECT "foo".* FROM "foo" WHERE "bar_id" IN (SELECT "bar"."id" AS "id" FROM "bar" LIMIT :subselect1limit OFFSET :subselect1offset) LIMIT :limit OFFSET :offset'; + $sqlStr53 = 'SELECT "foo".* FROM "foo" WHERE "bar_id" IN (SELECT "bar"."id" AS "id" FROM "bar" LIMIT \'10\' OFFSET \'9\') LIMIT \'11\' OFFSET \'12\''; + $internalTests53 = [ + 'processSelect' => [[['"foo".*']], '"foo"'], + 'processWhere' => ['"bar_id" IN (SELECT "bar"."id" AS "id" FROM "bar" LIMIT ? OFFSET ?)'], + 'processLimit' => ['?'], + 'processOffset' => ['?'], + ]; - // Verify the first mutation occurred - $offset = $select->getRawState(Select::OFFSET); - self::assertIsNumeric($offset); - self::assertEquals(10, $offset); + // join with alternate type full outer + $select54 = new Select(); + $select54->from('foo')->join('zac', 'm = n', ['bar', 'baz'], Select::JOIN_FULL_OUTER); + $sqlPrep54 = 'SELECT "foo".*, "zac"."bar" AS "bar", "zac"."baz" AS "baz" FROM "foo" FULL OUTER JOIN "zac" ON "m" = "n"'; + $sqlStr54 = 'SELECT "foo".*, "zac"."bar" AS "bar", "zac"."baz" AS "baz" FROM "foo" FULL OUTER JOIN "zac" ON "m" = "n"'; + $internalTests54 = [ + 'processSelect' => [ + [ + ['"foo".*'], + ['"zac"."bar"', '"bar"'], + ['"zac"."baz"', '"baz"'], + ], + '"foo"', + ], + 'processJoins' => [[['FULL OUTER', '"zac"', '"m" = "n"']]], + ]; - // Second mutation to verify mutability - $select->offset(20); + /** + * $select = the select object + * $sqlPrep = the sql as a result of preparation + * $params = the param container contents result of preparation + * $sqlStr = the sql as a result of getting a string back + * $internalTests what the internal functions should return (safe-guarding extension) + */ - // Verify the instance was actually mutated - self::assertEquals(20, $select->getRawState(Select::OFFSET)); + return [ + // $select $sqlPrep $params $sqlStr $internalTests use named param + [$select0, $sqlPrep0, [], $sqlStr0, $internalTests0, false], + [$select1, $sqlPrep1, [], $sqlStr1, $internalTests1, false], + [$select2, $sqlPrep2, [], $sqlStr2, $internalTests2, false], + [$select3, $sqlPrep3, [], $sqlStr3, $internalTests3, false], + [$select4, $sqlPrep4, [], $sqlStr4, $internalTests4, false], + [$select5, $sqlPrep5, [], $sqlStr5, $internalTests5, false], + [$select6, $sqlPrep6, [], $sqlStr6, $internalTests6, false], + [$select7, $sqlPrep7, [], $sqlStr7, $internalTests7, false], + [$select8, $sqlPrep8, [], $sqlStr8, $internalTests8, false], + [$select9, $sqlPrep9, $params9, $sqlStr9, $internalTests9, false], + [$select10, $sqlPrep10, [], $sqlStr10, $internalTests10, false], + [$select11, $sqlPrep11, [], $sqlStr11, $internalTests11, false], + [$select12, $sqlPrep12, [], $sqlStr12, $internalTests12, false], + [$select13, $sqlPrep13, [], $sqlStr13, $internalTests13, false], + [$select14, $sqlPrep14, [], $sqlStr14, $internalTests14, false], + [$select15, $sqlPrep15, [], $sqlStr15, $internalTests15, false], + [$select16, $sqlPrep16, $params16, $sqlStr16, $internalTests16, false], + [$select17, $sqlPrep17, [], $sqlStr17, $internalTests17, false], + [$select18, $sqlPrep18, [], $sqlStr18, $internalTests18, false], + [$select19, $sqlPrep19, [], $sqlStr19, $internalTests19, false], + [$select20, $sqlPrep20, [], $sqlStr20, $internalTests20, false], + [$select21, $sqlPrep21, $params21, $sqlStr21, $internalTests21, false], + [$select22, $sqlPrep22, [], $sqlStr22, $internalTests22, false], + [$select23, $sqlPrep23, [], $sqlStr23, $internalTests23, false], + [$select24, $sqlPrep24, [], $sqlStr24, $internalTests24, false], + [$select25, $sqlPrep25, [], $sqlStr25, $internalTests25, false], + [$select26, $sqlPrep26, $params26, $sqlStr26, $internalTests26, false], + [$select27, $sqlPrep27, $params27, $sqlStr27, $internalTests27, false], + [$select28, $sqlPrep28, [], $sqlStr28, $internalTests28, false], + [$select29, $sqlPrep29, [], $sqlStr29, $internalTests29, false], + [$select30, $sqlPrep30, [], $sqlStr30, $internalTests30, false], + [$select31, $sqlPrep31, [], $sqlStr31, $internalTests31, false], + [$select32, $sqlPrep32, [], $sqlStr32, $internalTests32, false], + [$select33, $sqlPrep33, [], $sqlStr33, $internalTests33, false], + [$select34, $sqlPrep34, [], $sqlStr34, $internalTests34, false], + [$select35, $sqlPrep35, [], $sqlStr35, $internalTests35, false], + [$select36, $sqlPrep36, [], $sqlStr36, $internalTests36, true], + [$select37, $sqlPrep37, [], $sqlStr37, $internalTests37, false], + [$select38, $sqlPrep38, [], $sqlStr38, $internalTests38, false], + [$select39, $sqlPrep39, [], $sqlStr39, $internalTests39, false], + [$select40, $sqlPrep40, [], $sqlStr40, $internalTests40, false], + [$select41, $sqlPrep41, [], $sqlStr41, $internalTests41, false], + [$select42, $sqlPrep42, [], $sqlStr42, $internalTests42, false], + [$select43, $sqlPrep43, [], $sqlStr43, $internalTests43, false], + [$select44, $sqlPrep44, [], $sqlStr44, $internalTests44, false], + [$select45, $sqlPrep45, $params45, $sqlStr45, $internalTests45, false], + [$select46, $sqlPrep46, $params46, $sqlStr46, $internalTests46, false], + [$select47, $sqlPrep47, $params47, $sqlStr47, $internalTests47, false], + [$select48, $sqlPrep48, [], $sqlStr48, $internalTests48, false], + [$select49, $sqlPrep49, [], $sqlStr49, $internalTests49, false], + [$select50, $sqlPrep50, [], $sqlStr50, $internalTests50, false], + [$select51, $sqlPrep51, [], $sqlStr51, $internalTests51, false], + [$select52, $sqlPrep52, [], $sqlStr52, $internalTests52, false], + [$select53, $sqlPrep53, $params53, $sqlStr53, $internalTests53, true], + [$select54, $sqlPrep54, [], $sqlStr54, $internalTests54, false], + ]; + + // phpcs:enable Generic.Files.LineLength.TooLong } - #[TestDox(': unit test: test offset() throws exception when invalid parameter passed')] - public function testOffsetExceptionOnInvalidParameter(): void + #[TestDox('unit test: Test join() exception with bad join')] + public function testBadJoin(): void { $select = new Select(); $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage(Select::class . '::offset expects parameter to be numeric'); - $select->offset('foobar'); + $this->expectExceptionMessage("expects 'foo' as"); + $select->join(['foo'], 'x = y'); } - #[TestDox('unit test: Test group() returns same Select object (is chainable)')] - public function testGroup(): void + public function testCloneDeepCopiesAllSubObjects(): void { $select = new Select(); + $select->from('foo'); + $select->where('id = 1'); + $select->having('cnt > 0'); + $select->join('bar', 'foo.id = bar.foo_id'); - // First mutation - $result = $select->group(['col1', 'col2']); + $clone = clone $select; + $clone->where('id = 2'); + $clone->having('cnt > 1'); + $clone->join('baz', 'foo.id = baz.foo_id'); - // Verify fluent interface - self::assertSame($select, $result); + self::assertCount(1, $select->where); + self::assertCount(2, $clone->where); + self::assertCount(1, $select->having); + self::assertCount(2, $clone->having); + self::assertCount(1, $select->joins); + self::assertCount(2, $clone->joins); + } - // Verify the first mutation occurred - self::assertEquals(['col1', 'col2'], $select->getRawState('group')); + #[TestDox('unit test: Test __clone() will clone the where object so that this select can be used + in multiple contexts')] + public function testCloning(): void + { + $select = new Select(); + $select1 = clone $select; + $select1->where('id = foo'); + $select1->having('id = foo'); - // Second mutation to verify mutability (group accumulates) - $select->group('col3'); + self::assertEquals(0, $select->where->count()); + self::assertEquals(1, $select1->where->count()); - // Verify the instance was actually mutated - self::assertEquals(['col1', 'col2', 'col3'], $select->getRawState('group')); + self::assertEquals(0, $select->having->count()); + self::assertEquals(1, $select1->having->count()); } - #[TestDox('unit test: Test having() returns same Select object (is chainable)')] - public function testHaving(): void + #[TestDox('unit test: Test columns() returns Select object (is chainable)')] + public function testColumns(): void { $select = new Select(); // First mutation - $result = $select->having(['x = ?' => 5]); + $result = $select->columns(['foo', 'bar']); // Verify fluent interface self::assertSame($select, $result); // Verify the first mutation occurred - $having = $select->getRawState('having'); - self::assertInstanceOf(Having::class, $having); - self::assertEquals(1, $having->count()); + self::assertEquals(['foo', 'bar'], $select->getRawState('columns')); - // Second mutation to verify mutability (having predicates accumulate) - $select->having(['y = ?' => 10]); + // Second mutation to verify mutability + $select->columns(['baz', 'qux']); // Verify the instance was actually mutated - self::assertEquals(2, $select->getRawState('having')->count()); + self::assertEquals(['baz', 'qux'], $select->getRawState('columns')); } - #[TestDox('unit test: Test having() returns same Select object (is chainable)')] - public function testHavingArgument1IsHavingObject(): void + public function testColumnsWithPrefixDisabled(): void { $select = new Select(); - $having = new Having(); - $return = $select->having($having); - self::assertSame($select, $return); - self::assertSame($having, $select->getRawState('having')); - } + $select->from('foo')->columns(['col'], false); - #[TestDox('unit test: Test where() accepts Expression (ExpressionInterface) in array')] - public function testWhereAcceptsExpressionInterface(): void - { - $select = new Select(); - $select->from('foo') - ->where([ - new Expression('COUNT(?) > ?', [new Identifier('id'), Argument::value(5)]), - ]); + $sql = $select->getSqlString(new TrustingSql92Platform()); - $where = $select->getRawState('where'); - self::assertInstanceOf(Where::class, $where); - self::assertEquals(1, $where->count()); + self::assertStringNotContainsString('"foo"."col"', $sql); + self::assertStringContainsString('"col"', $sql); } - #[TestDox('unit test: Test having() accepts Expression (ExpressionInterface) in array')] - public function testHavingAcceptsExpressionInterface(): void + public function testColumnsWithPrefixDisabledOmitsTablePrefix(): void { $select = new Select(); - $select->from('foo') - ->group('category') - ->having([ - new Expression('SUM(?) > ?', [Argument::identifier('amount'), Argument::value(100)]), - ]); + $select->from(['a' => 'users'])->columns(['id'], false); - $having = $select->getRawState('having'); - self::assertInstanceOf(Having::class, $having); - self::assertEquals(1, $having->count()); + $sql = $select->getSqlString(new TrustingSql92Platform()); + + self::assertStringContainsString('"id"', $sql); + self::assertStringNotContainsString('"a"."id"', $sql); } #[TestDox('unit test: Test combine() returns same Select object (is chainable)')] @@ -621,975 +951,614 @@ public function testCombine(): void self::assertEquals('DISTINCT', $state2['modifier']); } - #[TestDox('unit test: Test reset() resets internal stat of Select object, based on input')] - public function testReset(): void - { - $select = new Select(); - - // table - $select->from('foo'); - self::assertEquals('foo', $select->getRawState(Select::TABLE)); - $select->reset(Select::TABLE); - self::assertNull($select->getRawState(Select::TABLE)); - - // columns - $select->columns(['foo']); - self::assertEquals(['foo'], $select->getRawState(Select::COLUMNS)); - $select->reset(Select::COLUMNS); - self::assertEmpty($select->getRawState(Select::COLUMNS)); - - // joins - $select->join('foo', 'id = boo'); - $joins = $select->getRawState(Select::JOINS); - self::assertInstanceOf(Join::class, $joins); - self::assertEquals( - [['name' => 'foo', 'on' => 'id = boo', 'columns' => ['*'], 'type' => 'inner']], - $joins->getJoins() - ); - $select->reset(Select::JOINS); - $emptyJoins = $select->getRawState(Select::JOINS); - self::assertInstanceOf(Join::class, $emptyJoins); - self::assertEmpty($emptyJoins->getJoins()); - - // where - $select->where('foo = bar'); - /** @var Where $where1 */ - $where1 = $select->getRawState(Select::WHERE); - self::assertEquals(1, $where1->count()); - $select->reset(Select::WHERE); - /** @var Where $where2 */ - $where2 = $select->getRawState(Select::WHERE); - self::assertEquals(0, $where2->count()); - self::assertNotSame($where1, $where2); - - // group - $select->group(['foo']); - self::assertEquals(['foo'], $select->getRawState(Select::GROUP)); - $select->reset(Select::GROUP); - self::assertEmpty($select->getRawState(Select::GROUP)); - - // having - $select->having('foo = bar'); - /** @var Having $having1 */ - $having1 = $select->getRawState(Select::HAVING); - self::assertEquals(1, $having1->count()); - $select->reset(Select::HAVING); - /** @var Having $having2 */ - $having2 = $select->getRawState(Select::HAVING); - self::assertEquals(0, $having2->count()); - self::assertNotSame($having1, $having2); - - // limit - $select->limit(5); - self::assertEquals(5, $select->getRawState(Select::LIMIT)); - $select->reset(Select::LIMIT); - self::assertNull($select->getRawState(Select::LIMIT)); - - // offset - $select->offset(10); - self::assertEquals(10, $select->getRawState(Select::OFFSET)); - $select->reset(Select::OFFSET); - self::assertNull($select->getRawState(Select::OFFSET)); - - // order - $select->order('foo asc'); - self::assertEquals(['foo asc'], $select->getRawState(Select::ORDER)); - $select->reset(Select::ORDER); - self::assertEmpty($select->getRawState(Select::ORDER)); - } - - /** @noinspection PhpUnusedParameterInspection */ - #[DataProvider('providerData')] - #[TestDox('unit test: Test prepareStatement() will produce expected sql and parameters based on - a variety of provided arguments [uses data provider]')] - public function testPrepareStatement( - Select $select, - string $expectedSqlString, - array $expectedParameters, - mixed $unused1, - mixed $unused2, - bool $useNamedParameters = false - ): void { - $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); - $mockDriver - ->expects($this->any()) - ->method('formatParameterName') - ->willReturnCallback(fn(string $name): string => $useNamedParameters ? ':' . $name : '?'); - - $mockAdapter = $this->createMockAdapter($mockDriver); - - $parameterContainer = new ParameterContainer(); - - $mockStatement = $this->getMockBuilder(StatementInterface::class)->getMock(); - $mockStatement->expects($this->any())->method('getParameterContainer') - ->willReturn($parameterContainer); - $mockStatement->expects($this->any())->method('setSql')->with($this->equalTo($expectedSqlString)); - - $select->prepareStatement($mockAdapter, $mockStatement); - - if ($expectedParameters !== []) { - self::assertEquals($expectedParameters, $parameterContainer->getNamedArray()); - } - } - - #[Group('Laminas-5192')] - public function testSelectUsingTableIdentifierWithEmptyScheme(): void - { - $select = new Select(); - $select->from(new TableIdentifier('foo')); - $select->join(new TableIdentifier('bar'), 'foo.id = bar.fooid'); - - self::assertEquals( - 'SELECT "foo".*, "bar".* FROM "foo" INNER JOIN "bar" ON "foo"."id" = "bar"."fooid"', - $select->getSqlString(new TrustingSql92Platform()) - ); - } - - /** @noinspection PhpUnusedParameterInspection */ - #[DataProvider('providerData')] - #[TestDox('unit test: Test getSqlString() will produce expected sql and parameters based on - a variety of provided arguments [uses data provider]')] - public function testGetSqlString(Select $select, mixed $unused, mixed $unused2, string $expectedSqlString): void - { - self::assertEquals($expectedSqlString, $select->getSqlString(new TrustingSql92Platform())); - } - - #[TestDox('unit test: Test __get() returns expected objects magically')] - public function testMagicAccessor(): void + public function testCombineThrowsWhenAlreadyCombined(): void { $select = new Select(); - self::assertInstanceOf(Where::class, $select->where); - } - - #[TestDox('unit test: Test __clone() will clone the where object so that this select can be used - in multiple contexts')] - public function testCloning(): void - { - $select = new Select(); - $select1 = clone $select; - $select1->where('id = foo'); - $select1->having('id = foo'); - - self::assertEquals(0, $select->where->count()); - self::assertEquals(1, $select1->where->count()); - - self::assertEquals(0, $select->having->count()); - self::assertEquals(1, $select1->having->count()); - } - - /** - * @throws ReflectionException - * @noinspection PhpUnusedParameterInspection - */ - #[DataProvider('providerData')] - #[TestDox('unit test: Text process*() methods will return proper array when internally called, - part of extension API')] - public function testProcessMethods( - Select $select, - mixed $unused, - mixed $unused2, - mixed $unused3, - array $internalTests - ): void { - if ($internalTests === []) { - $this->expectNotToPerformAssertions(); - return; - } - - $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); - $mockDriver->expects($this->any())->method('formatParameterName')->willReturn('?'); - $parameterContainer = new ParameterContainer(); - - $sr = new ReflectionObject($select); + $select->from('t1'); + $select->combine(new Select('t2')); - /** - * @var string $method - * @var array $expected - */ - foreach ($internalTests as $method => $expected) { - $mr = $sr->getMethod($method); - /** @psalm-suppress MixedAssignment */ - $return = $mr->invokeArgs($select, [new Sql92(), $mockDriver, $parameterContainer]); - self::assertEquals($expected, $return); - } + $this->expectException(InvalidArgumentException::class); + $select->combine(new Select('t3')); } - /** - * @psalm-return array, - * 3: string, - * 4: array, - * 5: bool, - * }> - */ - public static function providerData(): array + public function testCombineWrapsStatementInParentheses(): void { - // phpcs:disable Generic.Files.LineLength.TooLong - // basic table - $select0 = new Select(); - $select0->from('foo'); + $select1 = new Select(); + $select1->from('t1'); - $sqlPrep0 = 'SELECT "foo".* FROM "foo"'; - $sqlStr0 = 'SELECT "foo".* FROM "foo"'; - $internalTests0 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - ]; + $select2 = new Select(); + $select2->from('t2'); - // table as TableIdentifier - $select1 = new Select(); - $select1->from(new TableIdentifier('foo', 'bar')); + $select1->combine($select2); - $sqlPrep1 = 'SELECT "bar"."foo".* FROM "bar"."foo"'; - $sqlStr1 = 'SELECT "bar"."foo".* FROM "bar"."foo"'; - $internalTests1 = [ - 'processSelect' => [[['"bar"."foo".*']], '"bar"."foo"'], - ]; + $sql = $select1->getSqlString(new TrustingSql92Platform()); - // table with alias - $select2 = new Select(); - $select2->from(['f' => 'foo']); + self::assertStringContainsString('( SELECT', $sql); + self::assertStringContainsString('UNION', $sql); + self::assertStringContainsString(') UNION (', $sql); + } - $sqlPrep2 = 'SELECT "f".* FROM "foo" AS "f"'; - $sqlStr2 = 'SELECT "f".* FROM "foo" AS "f"'; - $internalTests2 = [ - 'processSelect' => [[['"f".*']], '"foo" AS "f"'], - ]; + public function testConstruct(): void + { + $select = new Select('foo'); + self::assertEquals('foo', $select->getRawState('table')); + } - // table with alias with table as TableIdentifier - $select3 = new Select(); - $select3->from(['f' => new TableIdentifier('foo')]); + #[TestDox('unit test: Test from() returns Select object (is chainable)')] + public function testFrom(): void + { + $select = new Select(); - $sqlPrep3 = 'SELECT "f".* FROM "foo" AS "f"'; - $sqlStr3 = 'SELECT "f".* FROM "foo" AS "f"'; - $internalTests3 = [ - 'processSelect' => [[['"f".*']], '"foo" AS "f"'], - ]; + // First mutation + $result = $select->from('foo'); - // columns - $select4 = new Select(); - $select4->from('foo')->columns(['bar', 'baz']); - $sqlPrep4 = 'SELECT "foo"."bar" AS "bar", "foo"."baz" AS "baz" FROM "foo"'; - $sqlStr4 = 'SELECT "foo"."bar" AS "bar", "foo"."baz" AS "baz" FROM "foo"'; - $internalTests4 = [ - 'processSelect' => [[['"foo"."bar"', '"bar"'], ['"foo"."baz"', '"baz"']], '"foo"'], - ]; + // Verify fluent interface + self::assertSame($select, $result); - // columns with AS associative array - $select5 = new Select(); - $select5->from('foo')->columns(['bar' => 'baz']); - $sqlPrep5 = 'SELECT "foo"."baz" AS "bar" FROM "foo"'; - $sqlStr5 = 'SELECT "foo"."baz" AS "bar" FROM "foo"'; - $internalTests5 = [ - 'processSelect' => [[['"foo"."baz"', '"bar"']], '"foo"'], - ]; + // Verify the first mutation occurred + self::assertEquals('foo', $select->getRawState('table')); - // columns with AS associative array mixed - $select6 = new Select(); - $select6->from('foo')->columns(['bar' => 'baz', 'bam']); - $sqlPrep6 = 'SELECT "foo"."baz" AS "bar", "foo"."bam" AS "bam" FROM "foo"'; - $sqlStr6 = 'SELECT "foo"."baz" AS "bar", "foo"."bam" AS "bam" FROM "foo"'; - $internalTests6 = [ - 'processSelect' => [[['"foo"."baz"', '"bar"'], ['"foo"."bam"', '"bam"']], '"foo"'], - ]; + // Second mutation to verify mutability + $select->from('bar'); - // columns where value is Expression, with AS - $select7 = new Select(); - $select7->from('foo')->columns(['bar' => new Expression('COUNT(some_column)')]); - $sqlPrep7 = 'SELECT COUNT(some_column) AS "bar" FROM "foo"'; - $sqlStr7 = 'SELECT COUNT(some_column) AS "bar" FROM "foo"'; - $internalTests7 = [ - 'processSelect' => [[['COUNT(some_column)', '"bar"']], '"foo"'], - ]; + // Verify the instance was actually mutated + self::assertEquals('bar', $select->getRawState('table')); + } - // columns where value is Expression - $select8 = new Select(); - $select8->from('foo')->columns([new Expression('COUNT(some_column) AS bar')]); - $sqlPrep8 = 'SELECT COUNT(some_column) AS bar FROM "foo"'; - $sqlStr8 = 'SELECT COUNT(some_column) AS bar FROM "foo"'; - $internalTests8 = [ - 'processSelect' => [[['COUNT(some_column) AS bar']], '"foo"'], - ]; + public function testFromThrowsExceptionForInvalidArrayFormat(): void + { + $select = new Select(); - // columns where value is Expression with parameters - $select9 = new Select(); - $select9->from('foo')->columns( - [ - new Expression( - '(COUNT(?) + ?) AS ?', - [ - new Argument\Identifier('some_column'), - new Argument\Value(5), - new Argument\Identifier('bar'), - ], - ), - ] - ); - $sqlPrep9 = 'SELECT (COUNT("some_column") + ?) AS "bar" FROM "foo"'; - $sqlStr9 = 'SELECT (COUNT("some_column") + \'5\') AS "bar" FROM "foo"'; - $params9 = ['column1' => 5]; - $internalTests9 = [ - 'processSelect' => [[['(COUNT("some_column") + ?) AS "bar"']], '"foo"'], - ]; + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('from() expects $table as an array is a single element associative array'); + $select->from(['foo', 'bar']); // Numeric array instead of associative + } - // joins (plain) - $select10 = new Select(); - $select10->from('foo')->join('zac', 'm = n'); - $sqlPrep10 = 'SELECT "foo".*, "zac".* FROM "foo" INNER JOIN "zac" ON "m" = "n"'; - $sqlStr10 = 'SELECT "foo".*, "zac".* FROM "foo" INNER JOIN "zac" ON "m" = "n"'; - $internalTests10 = [ - 'processSelect' => [[['"foo".*'], ['"zac".*']], '"foo"'], - 'processJoins' => [[['INNER', '"zac"', '"m" = "n"']]], - ]; + public function testFromThrowsExceptionForInvalidTableType(): void + { + $select = new Select(); - // join with columns - $select11 = new Select(); - $select11->from('foo')->join('zac', 'm = n', ['bar', 'baz']); - $sqlPrep11 = 'SELECT "foo".*, "zac"."bar" AS "bar", "zac"."baz" AS "baz" FROM "foo" INNER JOIN "zac" ON "m" = "n"'; - $sqlStr11 = 'SELECT "foo".*, "zac"."bar" AS "bar", "zac"."baz" AS "baz" FROM "foo" INNER JOIN "zac" ON "m" = "n"'; - $internalTests11 = [ - 'processSelect' => [[['"foo".*'], ['"zac"."bar"', '"bar"'], ['"zac"."baz"', '"baz"']], '"foo"'], - 'processJoins' => [[['INNER', '"zac"', '"m" = "n"']]], - ]; + $this->expectException(TypeError::class); + /** @noinspection ALL */ + $select->from(123); + } - // join with alternate type - $select12 = new Select(); - $select12->from('foo')->join('zac', 'm = n', ['bar', 'baz'], Select::JOIN_OUTER); - $sqlPrep12 = 'SELECT "foo".*, "zac"."bar" AS "bar", "zac"."baz" AS "baz" FROM "foo" OUTER JOIN "zac" ON "m" = "n"'; - $sqlStr12 = 'SELECT "foo".*, "zac"."bar" AS "bar", "zac"."baz" AS "baz" FROM "foo" OUTER JOIN "zac" ON "m" = "n"'; - $internalTests12 = [ - 'processSelect' => [[['"foo".*'], ['"zac"."bar"', '"bar"'], ['"zac"."baz"', '"baz"']], '"foo"'], - 'processJoins' => [[['OUTER', '"zac"', '"m" = "n"']]], - ]; + public function testFromThrowsExceptionWhenTableReadOnly(): void + { + $select = new Select('foo'); // Creating with table makes it read-only - // join with column aliases - $select13 = new Select(); - $select13->from('foo')->join('zac', 'm = n', ['BAR' => 'bar', 'BAZ' => 'baz']); - $sqlPrep13 = 'SELECT "foo".*, "zac"."bar" AS "BAR", "zac"."baz" AS "BAZ" FROM "foo" INNER JOIN "zac" ON "m" = "n"'; - $sqlStr13 = 'SELECT "foo".*, "zac"."bar" AS "BAR", "zac"."baz" AS "BAZ" FROM "foo" INNER JOIN "zac" ON "m" = "n"'; - $internalTests13 = [ - 'processSelect' => [[['"foo".*'], ['"zac"."bar"', '"BAR"'], ['"zac"."baz"', '"BAZ"']], '"foo"'], - 'processJoins' => [[['INNER', '"zac"', '"m" = "n"']]], - ]; + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage( + 'Since this object was created with a table and/or schema in the constructor, it is read only.', + ); + $select->from('bar'); + } - // join with table aliases - $select14 = new Select(); - $select14->from('foo')->join(['b' => 'bar'], 'b.foo_id = foo.foo_id'); - $sqlPrep14 = 'SELECT "foo".*, "b".* FROM "foo" INNER JOIN "bar" AS "b" ON "b"."foo_id" = "foo"."foo_id"'; - $sqlStr14 = 'SELECT "foo".*, "b".* FROM "foo" INNER JOIN "bar" AS "b" ON "b"."foo_id" = "foo"."foo_id"'; - $internalTests14 = [ - 'processSelect' => [[['"foo".*'], ['"b".*']], '"foo"'], - 'processJoins' => [[['INNER', '"bar" AS "b"', '"b"."foo_id" = "foo"."foo_id"']]], - ]; + public function testFromWithAliasArrayResolvesTableAndAlias(): void + { + $select = new Select(); + $select->from(['a' => 'users'])->columns(['id']); - // where (simple string) - $select15 = new Select(); - $select15->from('foo')->where('x = 5'); - $sqlPrep15 = 'SELECT "foo".* FROM "foo" WHERE x = 5'; - $sqlStr15 = 'SELECT "foo".* FROM "foo" WHERE x = 5'; - $internalTests15 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processWhere' => ['x = 5'], - ]; + $sql = $select->getSqlString(new TrustingSql92Platform()); - // where (returning parameters) - $select16 = new Select(); - $select16->from('foo')->where(['x = ?' => 5]); - $sqlPrep16 = 'SELECT "foo".* FROM "foo" WHERE x = ?'; - $sqlStr16 = 'SELECT "foo".* FROM "foo" WHERE x = \'5\''; - $params16 = ['where1' => 5]; - $internalTests16 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processWhere' => ['x = ?'], - ]; + self::assertStringContainsString('"users" AS "a"', $sql); + self::assertStringContainsString('"a"."id"', $sql); + } - // group - $select17 = new Select(); - $select17->from('foo')->group(['col1', 'col2']); - $sqlPrep17 = 'SELECT "foo".* FROM "foo" GROUP BY "col1", "col2"'; - $sqlStr17 = 'SELECT "foo".* FROM "foo" GROUP BY "col1", "col2"'; - $internalTests17 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processGroup' => [['"col1"', '"col2"']], - ]; + public function testGetRawStateInitializesLazyProperties(): void + { + $select = new Select(); + $rawState = $select->getRawState(); - $select18 = new Select(); - $select18->from('foo')->group('col1')->group('col2'); - $sqlPrep18 = 'SELECT "foo".* FROM "foo" GROUP BY "col1", "col2"'; - $sqlStr18 = 'SELECT "foo".* FROM "foo" GROUP BY "col1", "col2"'; - $internalTests18 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processGroup' => [['"col1"', '"col2"']], - ]; + self::assertInstanceOf(Where::class, $rawState[Select::WHERE]); + self::assertInstanceOf(Join::class, $rawState[Select::JOINS]); + self::assertInstanceOf(Having::class, $rawState[Select::HAVING]); + } - $select19 = new Select(); - $select19->from('foo')->group(new Expression('DAY(?)', [new Argument\Identifier('col1')])); - $sqlPrep19 = 'SELECT "foo".* FROM "foo" GROUP BY DAY("col1")'; - $sqlStr19 = 'SELECT "foo".* FROM "foo" GROUP BY DAY("col1")'; - $internalTests19 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processGroup' => [['DAY("col1")']], - ]; + /** @noinspection PhpUnusedParameterInspection */ + #[DataProvider('providerData')] + #[TestDox('unit test: Test getSqlString() will produce expected sql and parameters based on + a variety of provided arguments [uses data provider]')] + public function testGetSqlString(Select $select, mixed $unused, mixed $unused2, string $expectedSqlString): void + { + self::assertEquals($expectedSqlString, $select->getSqlString(new TrustingSql92Platform())); + } - // having (simple string) - $select20 = new Select(); - $select20->from('foo')->having('x = 5'); - $sqlPrep20 = 'SELECT "foo".* FROM "foo" HAVING x = 5'; - $sqlStr20 = 'SELECT "foo".* FROM "foo" HAVING x = 5'; - $internalTests20 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processHaving' => ['x = 5'], - ]; + public function testGetThrowsExceptionForInvalidProperty(): void + { + $select = new Select(); - // having (returning parameters) - $select21 = new Select(); - $select21->from('foo')->having(['x = ?' => 5]); - $sqlPrep21 = 'SELECT "foo".* FROM "foo" HAVING x = ?'; - $sqlStr21 = 'SELECT "foo".* FROM "foo" HAVING x = \'5\''; - $params21 = ['having1' => 5]; - $internalTests21 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processHaving' => ['x = ?'], - ]; + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Not a valid magic property for this object'); + /** @noinspection ALL */ + $value = $select->invalidProperty; /** @phpstan-ignore-line */ + } - // order - $select22 = new Select(); - $select22->from('foo')->order('c1'); - $sqlPrep22 = 'SELECT "foo".* FROM "foo" ORDER BY "c1" ASC'; - $sqlStr22 = 'SELECT "foo".* FROM "foo" ORDER BY "c1" ASC'; - $internalTests22 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processOrder' => [[['"c1"', Select::ORDER_ASCENDING]]], - ]; + #[TestDox('unit test: Test group() returns same Select object (is chainable)')] + public function testGroup(): void + { + $select = new Select(); - $select23 = new Select(); - $select23->from('foo')->order(['c1', 'c2']); - $sqlPrep23 = 'SELECT "foo".* FROM "foo" ORDER BY "c1" ASC, "c2" ASC'; - $sqlStr23 = 'SELECT "foo".* FROM "foo" ORDER BY "c1" ASC, "c2" ASC'; - $internalTests23 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processOrder' => [[['"c1"', Select::ORDER_ASCENDING], ['"c2"', Select::ORDER_ASCENDING]]], - ]; + // First mutation + $result = $select->group(['col1', 'col2']); - $select24 = new Select(); - $select24->from('foo')->order(['c1' => 'DESC', 'c2' => 'Asc']); - // notice partially lower case ASC - $sqlPrep24 = 'SELECT "foo".* FROM "foo" ORDER BY "c1" DESC, "c2" ASC'; - $sqlStr24 = 'SELECT "foo".* FROM "foo" ORDER BY "c1" DESC, "c2" ASC'; - $internalTests24 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processOrder' => [[['"c1"', Select::ORDER_DESCENDING], ['"c2"', Select::ORDER_ASCENDING]]], - ]; + // Verify fluent interface + self::assertSame($select, $result); - $select25 = new Select(); - $select25->from('foo')->order(['c1' => 'asc'])->order('c2 desc'); - // notice partially lower case ASC - $sqlPrep25 = 'SELECT "foo".* FROM "foo" ORDER BY "c1" ASC, "c2" DESC'; - $sqlStr25 = 'SELECT "foo".* FROM "foo" ORDER BY "c1" ASC, "c2" DESC'; - $internalTests25 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processOrder' => [[['"c1"', Select::ORDER_ASCENDING], ['"c2"', Select::ORDER_DESCENDING]]], - ]; + // Verify the first mutation occurred + self::assertEquals(['col1', 'col2'], $select->getRawState('group')); - // limit - $select26 = new Select(); - $select26->from('foo')->limit(5); - $sqlPrep26 = 'SELECT "foo".* FROM "foo" LIMIT ?'; - $sqlStr26 = 'SELECT "foo".* FROM "foo" LIMIT \'5\''; - $params26 = ['limit' => 5]; - $internalTests26 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processLimit' => ['?'], - ]; + // Second mutation to verify mutability (group accumulates) + $select->group('col3'); - // limit with offset - $select27 = new Select(); - $select27->from('foo')->limit(5)->offset(10); - $sqlPrep27 = 'SELECT "foo".* FROM "foo" LIMIT ? OFFSET ?'; - $sqlStr27 = 'SELECT "foo".* FROM "foo" LIMIT \'5\' OFFSET \'10\''; - $params27 = ['limit' => 5, 'offset' => 10]; - $internalTests27 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processLimit' => ['?'], - 'processOffset' => ['?'], - ]; + // Verify the instance was actually mutated + self::assertEquals(['col1', 'col2', 'col3'], $select->getRawState('group')); + } - // joins with a few keywords in the on clause - $select28 = new Select(); - $select28->from('foo')->join('zac', '(m = n AND c.x) BETWEEN x AND y.z OR (c.x < y.z AND c.x <= y.z AND c.x > y.z AND c.x >= y.z)'); - $sqlPrep28 = 'SELECT "foo".*, "zac".* FROM "foo" INNER JOIN "zac" ON ("m" = "n" AND "c"."x") BETWEEN "x" AND "y"."z" OR ("c"."x" < "y"."z" AND "c"."x" <= "y"."z" AND "c"."x" > "y"."z" AND "c"."x" >= "y"."z")'; - $sqlStr28 = 'SELECT "foo".*, "zac".* FROM "foo" INNER JOIN "zac" ON ("m" = "n" AND "c"."x") BETWEEN "x" AND "y"."z" OR ("c"."x" < "y"."z" AND "c"."x" <= "y"."z" AND "c"."x" > "y"."z" AND "c"."x" >= "y"."z")'; - $internalTests28 = [ - 'processSelect' => [[['"foo".*'], ['"zac".*']], '"foo"'], - 'processJoins' => [[['INNER', '"zac"', '("m" = "n" AND "c"."x") BETWEEN "x" AND "y"."z" OR ("c"."x" < "y"."z" AND "c"."x" <= "y"."z" AND "c"."x" > "y"."z" AND "c"."x" >= "y"."z")']]], - ]; + #[TestDox('unit test: Test having() returns same Select object (is chainable)')] + public function testHaving(): void + { + $select = new Select(); - // order with compound name - $select29 = new Select(); - $select29->from('foo')->order('c1.d2'); - $sqlPrep29 = 'SELECT "foo".* FROM "foo" ORDER BY "c1"."d2" ASC'; - $sqlStr29 = 'SELECT "foo".* FROM "foo" ORDER BY "c1"."d2" ASC'; - $internalTests29 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processOrder' => [[['"c1"."d2"', Select::ORDER_ASCENDING]]], - ]; + // First mutation + $result = $select->having(['x = ?' => 5]); - // group with compound name - $select30 = new Select(); - $select30->from('foo')->group('c1.d2'); - $sqlPrep30 = 'SELECT "foo".* FROM "foo" GROUP BY "c1"."d2"'; - $sqlStr30 = 'SELECT "foo".* FROM "foo" GROUP BY "c1"."d2"'; - $internalTests30 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processGroup' => [['"c1"."d2"']], - ]; + // Verify fluent interface + self::assertSame($select, $result); - // join with expression in ON part - $select31 = new Select(); - $select31->from('foo')->join('zac', new Predicate\Expression('(m = n AND c.x) BETWEEN x AND y.z')); - $sqlPrep31 = 'SELECT "foo".*, "zac".* FROM "foo" INNER JOIN "zac" ON (m = n AND c.x) BETWEEN x AND y.z'; - $sqlStr31 = 'SELECT "foo".*, "zac".* FROM "foo" INNER JOIN "zac" ON (m = n AND c.x) BETWEEN x AND y.z'; - $internalTests31 = [ - 'processSelect' => [[['"foo".*'], ['"zac".*']], '"foo"'], - 'processJoins' => [[['INNER', '"zac"', '(m = n AND c.x) BETWEEN x AND y.z']]], - ]; + // Verify the first mutation occurred + $having = $select->getRawState('having'); + self::assertInstanceOf(Having::class, $having); + self::assertEquals(1, $having->count()); - $select32subselect = new Select(); - $select32subselect->from('bar')->where->like('y', '%Foo%'); - $select32 = new Select(); - $select32->from(['x' => $select32subselect]); + // Second mutation to verify mutability (having predicates accumulate) + $select->having(['y = ?' => 10]); - $sqlPrep32 = 'SELECT "x".* FROM (SELECT "bar".* FROM "bar" WHERE "y" LIKE ?) AS "x"'; - $sqlStr32 = 'SELECT "x".* FROM (SELECT "bar".* FROM "bar" WHERE "y" LIKE \'%Foo%\') AS "x"'; - $internalTests32 = [ - 'processSelect' => [[['"x".*']], '(SELECT "bar".* FROM "bar" WHERE "y" LIKE ?) AS "x"'], - ]; + // Verify the instance was actually mutated + self::assertEquals(2, $select->getRawState('having')->count()); + } - $select33 = new Select(); - $select33->from('table')->columns(['*'])->where([ - 'c1' => null, - 'c2' => [1, 2, 3], - new IsNotNull('c3'), - ]); - $sqlPrep33 = 'SELECT "table".* FROM "table" WHERE "c1" IS NULL AND "c2" IN (?, ?, ?) AND "c3" IS NOT NULL'; - $sqlStr33 = 'SELECT "table".* FROM "table" WHERE "c1" IS NULL AND "c2" IN (\'1\', \'2\', \'3\') AND "c3" IS NOT NULL'; - $internalTests33 = [ - 'processSelect' => [[['"table".*']], '"table"'], - 'processWhere' => ['"c1" IS NULL AND "c2" IN (?, ?, ?) AND "c3" IS NOT NULL'], - ]; + #[TestDox('unit test: Test having() accepts Expression (ExpressionInterface) in array')] + public function testHavingAcceptsExpressionInterface(): void + { + $select = new Select(); + $select->from('foo') + ->group('category') + ->having([ + new Expression('SUM(?) > ?', [Argument::identifier('amount'), Argument::value(100)]), + ]); - // @author Demian Katz - $select34 = new Select(); - $select34->from('table')->order([ - new Expression('isnull(?) DESC', [new Argument\Identifier('name')]), - 'name', - ]); - $sqlPrep34 = 'SELECT "table".* FROM "table" ORDER BY isnull("name") DESC, "name" ASC'; - $sqlStr34 = 'SELECT "table".* FROM "table" ORDER BY isnull("name") DESC, "name" ASC'; - $internalTests34 = [ - 'processOrder' => [[['isnull("name") DESC'], ['"name"', Select::ORDER_ASCENDING]]], - ]; + $having = $select->getRawState('having'); + self::assertInstanceOf(Having::class, $having); + self::assertEquals(1, $having->count()); + } - // join with Expression object in COLUMNS part (Laminas-514) - // @co-author Koen Pieters (kpieters) - $select35 = new Select(); - $select35->from('foo')->columns([])->join('bar', 'm = n', ['thecount' => new Expression("COUNT(*)")]); - $sqlPrep35 = 'SELECT COUNT(*) AS "thecount" FROM "foo" INNER JOIN "bar" ON "m" = "n"'; - $sqlStr35 = 'SELECT COUNT(*) AS "thecount" FROM "foo" INNER JOIN "bar" ON "m" = "n"'; - $internalTests35 = [ - 'processSelect' => [[['COUNT(*)', '"thecount"']], '"foo"'], - 'processJoins' => [[['INNER', '"bar"', '"m" = "n"']]], - ]; + #[TestDox('unit test: Test having() returns same Select object (is chainable)')] + public function testHavingArgument1IsHavingObject(): void + { + $select = new Select(); + $having = new Having(); + $return = $select->having($having); + self::assertSame($select, $return); + self::assertSame($having, $select->getRawState('having')); + } - // multiple joins with expressions - // reported by @jdolieslager - $select36 = new Select(); - $select36->from('foo') - ->join('tableA', new Predicate\Operator('id', '=', 1)) - ->join('tableB', new Predicate\Operator('id', '=', 2)) - ->join('tableC', new Predicate\PredicateSet([ - new Predicate\Operator('id', '=', 3), - new Predicate\Operator('number', '>', 20), - ])); - $sqlPrep36 = 'SELECT "foo".*, "tableA".*, "tableB".*, "tableC".* FROM "foo"' - . ' INNER JOIN "tableA" ON "id" = :join1part1 INNER JOIN "tableB" ON "id" = :join2part1 ' - . 'INNER JOIN "tableC" ON "id" = :join3part1 AND "number" > :join3part2'; - $sqlStr36 = 'SELECT "foo".*, "tableA".*, "tableB".*, "tableC".* FROM "foo" ' - . 'INNER JOIN "tableA" ON "id" = \'1\' INNER JOIN "tableB" ON "id" = \'2\' ' - . 'INNER JOIN "tableC" ON "id" = \'3\' AND "number" > \'20\''; - $internalTests36 = []; + #[TestDox('unit test: Test isTableReadOnly() returns correct state for read only')] + public function testIsTableReadOnly(): void + { + $select = new Select('foo'); + self::assertTrue($select->isTableReadOnly()); - /** - * @link https://github.com/zendframework/zf2/pull/2714 - */ - $select37 = new Select(); - $select37->from('foo')->columns(['bar'], false); - $sqlPrep37 = 'SELECT "bar" AS "bar" FROM "foo"'; - $sqlStr37 = 'SELECT "bar" AS "bar" FROM "foo"'; - $internalTests37 = [ - 'processSelect' => [[['"bar"', '"bar"']], '"foo"'], - ]; + $select = new Select(); + self::assertFalse($select->isTableReadOnly()); + } - // @link https://github.com/zendframework/zf2/issues/3294 - // Test TableIdentifier In Joins - $select38 = new Select(); - $select38->from('foo')->columns([]) - ->join(new TableIdentifier('bar', 'baz'), 'm = n', ['thecount' => new Expression("COUNT(*)")]); - $sqlPrep38 = 'SELECT COUNT(*) AS "thecount" FROM "foo" INNER JOIN "baz"."bar" ON "m" = "n"'; - $sqlStr38 = 'SELECT COUNT(*) AS "thecount" FROM "foo" INNER JOIN "baz"."bar" ON "m" = "n"'; - $internalTests38 = [ - 'processSelect' => [[['COUNT(*)', '"thecount"']], '"foo"'], - 'processJoins' => [[['INNER', '"baz"."bar"', '"m" = "n"']]], - ]; + #[TestDox('unit test: Test join() returns same Select object (is chainable)')] + public function testJoin(): void + { + $select = new Select(); - // subselect in join - $select39subselect = new Select(); - $select39subselect->from('bar')->where->like('y', '%Foo%'); - $select39 = new Select(); - $select39->from('foo')->join(['z' => $select39subselect], 'z.foo = bar.id'); - $sqlPrep39 = 'SELECT "foo".*, "z".* FROM "foo" INNER JOIN (SELECT "bar".* FROM "bar" WHERE "y" LIKE ?) AS "z" ON "z"."foo" = "bar"."id"'; - $sqlStr39 = 'SELECT "foo".*, "z".* FROM "foo" INNER JOIN (SELECT "bar".* FROM "bar" WHERE "y" LIKE \'%Foo%\') AS "z" ON "z"."foo" = "bar"."id"'; - $internalTests39 = [ - 'processJoins' => [ - [['INNER', '(SELECT "bar".* FROM "bar" WHERE "y" LIKE ?) AS "z"', '"z"."foo" = "bar"."id"']], - ], - ]; + // First mutation + $result = $select->join('foo', 'x = y'); - // @link https://github.com/zendframework/zf2/issues/3294 - // Test TableIdentifier In Joins, with multiple joins - $select40 = new Select(); - $select40->from('foo') - ->join(['a' => new TableIdentifier('another_foo', 'another_schema')], 'a.x = foo.foo_column') - ->join('bar', 'foo.colx = bar.colx'); - $sqlPrep40 = 'SELECT "foo".*, "a".*, "bar".* FROM "foo"' - . ' INNER JOIN "another_schema"."another_foo" AS "a" ON "a"."x" = "foo"."foo_column"' - . ' INNER JOIN "bar" ON "foo"."colx" = "bar"."colx"'; - $sqlStr40 = 'SELECT "foo".*, "a".*, "bar".* FROM "foo"' - . ' INNER JOIN "another_schema"."another_foo" AS "a" ON "a"."x" = "foo"."foo_column"' - . ' INNER JOIN "bar" ON "foo"."colx" = "bar"."colx"'; - $internalTests40 = [ - 'processSelect' => [[['"foo".*'], ['"a".*'], ['"bar".*']], '"foo"'], - 'processJoins' => [ + // Verify fluent interface + self::assertSame($select, $result); + + // Verify the first mutation occurred + $joins = $select->getRawState('joins'); + self::assertInstanceOf(Join::class, $joins); + self::assertEquals( + [ [ - ['INNER', '"another_schema"."another_foo" AS "a"', '"a"."x" = "foo"."foo_column"'], - ['INNER', '"bar"', '"foo"."colx" = "bar"."colx"'], + 'name' => 'foo', + 'on' => 'x = y', + 'columns' => [Select::SQL_STAR], + 'type' => Select::JOIN_INNER, ], ], - ]; + $joins->getJoins(), + ); - $select41 = new Select(); - $select41->from('foo')->quantifier(Select::QUANTIFIER_DISTINCT); - $sqlPrep41 = 'SELECT DISTINCT "foo".* FROM "foo"'; - $sqlStr41 = 'SELECT DISTINCT "foo".* FROM "foo"'; - $internalTests41 = [ - 'processSelect' => [Select::QUANTIFIER_DISTINCT, [['"foo".*']], '"foo"'], - ]; + // Second mutation to verify mutability (joins accumulate) + $select->join('bar', 'a = b'); - $select42 = new Select(); - $select42->from('foo')->quantifier(new Expression('TOP ?', [10])); - $sqlPrep42 = 'SELECT TOP ? "foo".* FROM "foo"'; - $sqlStr42 = 'SELECT TOP \'10\' "foo".* FROM "foo"'; - $internalTests42 = [ - 'processSelect' => ['TOP ?', [['"foo".*']], '"foo"'], - ]; + // Verify the instance was actually mutated + $joins2 = $select->getRawState('joins'); + self::assertCount(2, $joins2->getJoins()); + self::assertEquals('bar', $joins2->getJoins()[1]['name']); + } - $select43 = new Select(); - $select43->from(['x' => 'foo'])->columns(['bar' => 'foo.bar'], false); - $sqlPrep43 = 'SELECT "foo"."bar" AS "bar" FROM "foo" AS "x"'; - $sqlStr43 = 'SELECT "foo"."bar" AS "bar" FROM "foo" AS "x"'; - $internalTests43 = [ - 'processSelect' => [[['"foo"."bar"', '"bar"']], '"foo" AS "x"'], - ]; + #[TestDox(': unit test: test limit()')] + public function testLimit(): void + { + $select = new Select(); - $select44 = new Select(); - $select44->from('foo')->where('a = b'); - $select44b = new Select(); - $select44b->from('bar')->where('c = d'); - $select44->combine($select44b, Select::COMBINE_UNION, 'ALL'); - $sqlPrep44 = '( SELECT "foo".* FROM "foo" WHERE a = b ) UNION ALL ( SELECT "bar".* FROM "bar" WHERE c = d )'; - $sqlStr44 = '( SELECT "foo".* FROM "foo" WHERE a = b ) UNION ALL ( SELECT "bar".* FROM "bar" WHERE c = d )'; - $internalTests44 = [ - 'processCombine' => ['UNION ALL', 'SELECT "bar".* FROM "bar" WHERE c = d'], - ]; + // First mutation + $result = $select->limit(5); - // limit with offset - $select45 = new Select(); - $select45->from('foo')->limit("5")->offset("10"); - $sqlPrep45 = 'SELECT "foo".* FROM "foo" LIMIT ? OFFSET ?'; - $sqlStr45 = 'SELECT "foo".* FROM "foo" LIMIT \'5\' OFFSET \'10\''; - $params45 = ['limit' => 5, 'offset' => 10]; - $internalTests45 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processLimit' => ['?'], - 'processOffset' => ['?'], - ]; + // Verify fluent interface + self::assertSame($select, $result); - // functions without table - $select46 = new Select(); - $select46->columns([ - new Expression('SOME_DB_FUNCTION_ONE()'), - 'foo' => new Expression('SOME_DB_FUNCTION_TWO()'), - ]); - $sqlPrep46 = 'SELECT SOME_DB_FUNCTION_ONE() AS Expression1, SOME_DB_FUNCTION_TWO() AS "foo"'; - $sqlStr46 = 'SELECT SOME_DB_FUNCTION_ONE() AS Expression1, SOME_DB_FUNCTION_TWO() AS "foo"'; - $params46 = []; - $internalTests46 = []; + // Verify the first mutation occurred + $limit = $select->getRawState(Select::LIMIT); + self::assertIsNumeric($limit); + self::assertEquals(5, $limit); - // limit with big offset and limit - $select47 = new Select(); - $select47->from('foo')->limit("10000000000000000000")->offset("10000000000000000000"); - $sqlPrep47 = 'SELECT "foo".* FROM "foo" LIMIT ? OFFSET ?'; - $sqlStr47 = 'SELECT "foo".* FROM "foo" LIMIT \'10000000000000000000\' OFFSET \'10000000000000000000\''; - $params47 = ['limit' => 10000000000000000000, 'offset' => 10000000000000000000]; - $internalTests47 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processLimit' => ['?'], - 'processOffset' => ['?'], - ]; + // Second mutation to verify mutability + $select->limit(10); - //combine and union with order at the end - $select48 = new Select(); - $select48->from('foo')->where('a = b'); - $select48b = new Select(); - $select48b->from('bar')->where('c = d'); - $select48->combine($select48b); + // Verify the instance was actually mutated + self::assertEquals(10, $select->getRawState(Select::LIMIT)); + } - $select48combined = new Select(); - $select48 = $select48combined->from(['sub' => $select48])->order('id DESC'); - $sqlPrep48 = 'SELECT "sub".* FROM (( SELECT "foo".* FROM "foo" WHERE a = b ) UNION ( SELECT "bar".* FROM "bar" WHERE c = d )) AS "sub" ORDER BY "id" DESC'; - $sqlStr48 = 'SELECT "sub".* FROM (( SELECT "foo".* FROM "foo" WHERE a = b ) UNION ( SELECT "bar".* FROM "bar" WHERE c = d )) AS "sub" ORDER BY "id" DESC'; - $internalTests48 = [ - 'processCombine' => null, - ]; + #[TestDox(': unit test: test limit() throws exception when invalid parameter passed')] + public function testLimitExceptionOnInvalidParameter(): void + { + $select = new Select(); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(Select::class . '::limit expects parameter to be numeric'); + $select->limit('foobar'); + } - //Expression as joinName - $select49 = new Select(); - $select49->from(new TableIdentifier('foo')) - ->join(['bar' => new Expression('psql_function_which_returns_table')], 'foo.id = bar.fooid'); - $sqlPrep49 = 'SELECT "foo".*, "bar".* FROM "foo" INNER JOIN psql_function_which_returns_table AS "bar" ON "foo"."id" = "bar"."fooid"'; - $sqlStr49 = 'SELECT "foo".*, "bar".* FROM "foo" INNER JOIN psql_function_which_returns_table AS "bar" ON "foo"."id" = "bar"."fooid"'; - $internalTests49 = [ - 'processSelect' => [[['"foo".*'], ['"bar".*']], '"foo"'], - 'processJoins' => [[['INNER', 'psql_function_which_returns_table AS "bar"', '"foo"."id" = "bar"."fooid"']]], - ]; + #[TestDox('unit test: Test __get() returns expected objects magically')] + public function testMagicAccessor(): void + { + $select = new Select(); + self::assertInstanceOf(Where::class, $select->where); + } - // Test generic predicate is appended with AND - $select50 = new Select(); - $select50->from(new TableIdentifier('foo')) - ->where - ->nest - ->isNull('bar') - ->and - ->predicate(new Predicate\Literal('1=1')); - $sqlPrep50 = 'SELECT "foo".* FROM "foo" WHERE ("bar" IS NULL AND 1=1)'; - $sqlStr50 = 'SELECT "foo".* FROM "foo" WHERE ("bar" IS NULL AND 1=1)'; - $internalTests50 = []; + public function testMagicGetJoinsReturnsJoinInstance(): void + { + $select = new Select(); - // Test generic predicate is appended with OR - $select51 = new Select(); - $select51->from(new TableIdentifier('foo')) - ->where - ->nest - ->isNull('bar') - ->or - ->predicate(new Predicate\Literal('1=1')); - $sqlPrep51 = 'SELECT "foo".* FROM "foo" WHERE ("bar" IS NULL OR 1=1)'; - $sqlStr51 = 'SELECT "foo".* FROM "foo" WHERE ("bar" IS NULL OR 1=1)'; - $internalTests51 = []; + self::assertInstanceOf(Join::class, $select->joins); + } - /** - * @link https://github.com/zendframework/zf2/issues/7222 - */ - $select52 = new Select(); - $select52->from('foo')->join('zac', '(catalog_category_website.category_id = catalog_category.category_id)'); - $sqlPrep52 = 'SELECT "foo".*, "zac".* FROM "foo" INNER JOIN "zac" ON ("catalog_category_website"."category_id" = "catalog_category"."category_id")'; - $sqlStr52 = 'SELECT "foo".*, "zac".* FROM "foo" INNER JOIN "zac" ON ("catalog_category_website"."category_id" = "catalog_category"."category_id")'; - $internalTests52 = [ - 'processSelect' => [[['"foo".*'], ['"zac".*']], '"foo"'], - 'processJoins' => [ - [ - ['INNER', '"zac"', '("catalog_category_website"."category_id" = "catalog_category"."category_id")'], - ], - ], - ]; + #[TestDox(': unit test: test offset()')] + public function testOffset(): void + { + $select = new Select(); - $subSelect53 = new Select(); - $subSelect53->from('bar')->columns(['id'])->limit(10)->offset(9); - $select53 = new Select(); - $select53->from('foo')->where(new In('bar_id', $subSelect53))->limit(11)->offset(12); - $params53 = ['limit' => 11, 'offset' => 12, 'subselect1limit' => 10, 'subselect1offset' => 9]; - $sqlPrep53 = 'SELECT "foo".* FROM "foo" WHERE "bar_id" IN (SELECT "bar"."id" AS "id" FROM "bar" LIMIT :subselect1limit OFFSET :subselect1offset) LIMIT :limit OFFSET :offset'; - $sqlStr53 = 'SELECT "foo".* FROM "foo" WHERE "bar_id" IN (SELECT "bar"."id" AS "id" FROM "bar" LIMIT \'10\' OFFSET \'9\') LIMIT \'11\' OFFSET \'12\''; - $internalTests53 = [ - 'processSelect' => [[['"foo".*']], '"foo"'], - 'processWhere' => ['"bar_id" IN (SELECT "bar"."id" AS "id" FROM "bar" LIMIT ? OFFSET ?)'], - 'processLimit' => ['?'], - 'processOffset' => ['?'], - ]; + // First mutation + $result = $select->offset(10); - // join with alternate type full outer - $select54 = new Select(); - $select54->from('foo')->join('zac', 'm = n', ['bar', 'baz'], Select::JOIN_FULL_OUTER); - $sqlPrep54 = 'SELECT "foo".*, "zac"."bar" AS "bar", "zac"."baz" AS "baz" FROM "foo" FULL OUTER JOIN "zac" ON "m" = "n"'; - $sqlStr54 = 'SELECT "foo".*, "zac"."bar" AS "bar", "zac"."baz" AS "baz" FROM "foo" FULL OUTER JOIN "zac" ON "m" = "n"'; - $internalTests54 = [ - 'processSelect' => [[['"foo".*'], ['"zac"."bar"', '"bar"'], ['"zac"."baz"', '"baz"']], '"foo"'], - 'processJoins' => [[['FULL OUTER', '"zac"', '"m" = "n"']]], - ]; + // Verify fluent interface + self::assertSame($select, $result); - /** - * $select = the select object - * $sqlPrep = the sql as a result of preparation - * $params = the param container contents result of preparation - * $sqlStr = the sql as a result of getting a string back - * $internalTests what the internal functions should return (safe-guarding extension) - */ + // Verify the first mutation occurred + $offset = $select->getRawState(Select::OFFSET); + self::assertIsNumeric($offset); + self::assertEquals(10, $offset); - return [ - // $select $sqlPrep $params $sqlStr $internalTests use named param - [$select0, $sqlPrep0, [], $sqlStr0, $internalTests0, false], - [$select1, $sqlPrep1, [], $sqlStr1, $internalTests1, false], - [$select2, $sqlPrep2, [], $sqlStr2, $internalTests2, false], - [$select3, $sqlPrep3, [], $sqlStr3, $internalTests3, false], - [$select4, $sqlPrep4, [], $sqlStr4, $internalTests4, false], - [$select5, $sqlPrep5, [], $sqlStr5, $internalTests5, false], - [$select6, $sqlPrep6, [], $sqlStr6, $internalTests6, false], - [$select7, $sqlPrep7, [], $sqlStr7, $internalTests7, false], - [$select8, $sqlPrep8, [], $sqlStr8, $internalTests8, false], - [$select9, $sqlPrep9, $params9, $sqlStr9, $internalTests9, false], - [$select10, $sqlPrep10, [], $sqlStr10, $internalTests10, false], - [$select11, $sqlPrep11, [], $sqlStr11, $internalTests11, false], - [$select12, $sqlPrep12, [], $sqlStr12, $internalTests12, false], - [$select13, $sqlPrep13, [], $sqlStr13, $internalTests13, false], - [$select14, $sqlPrep14, [], $sqlStr14, $internalTests14, false], - [$select15, $sqlPrep15, [], $sqlStr15, $internalTests15, false], - [$select16, $sqlPrep16, $params16, $sqlStr16, $internalTests16, false], - [$select17, $sqlPrep17, [], $sqlStr17, $internalTests17, false], - [$select18, $sqlPrep18, [], $sqlStr18, $internalTests18, false], - [$select19, $sqlPrep19, [], $sqlStr19, $internalTests19, false], - [$select20, $sqlPrep20, [], $sqlStr20, $internalTests20, false], - [$select21, $sqlPrep21, $params21, $sqlStr21, $internalTests21, false], - [$select22, $sqlPrep22, [], $sqlStr22, $internalTests22, false], - [$select23, $sqlPrep23, [], $sqlStr23, $internalTests23, false], - [$select24, $sqlPrep24, [], $sqlStr24, $internalTests24, false], - [$select25, $sqlPrep25, [], $sqlStr25, $internalTests25, false], - [$select26, $sqlPrep26, $params26, $sqlStr26, $internalTests26, false], - [$select27, $sqlPrep27, $params27, $sqlStr27, $internalTests27, false], - [$select28, $sqlPrep28, [], $sqlStr28, $internalTests28, false], - [$select29, $sqlPrep29, [], $sqlStr29, $internalTests29, false], - [$select30, $sqlPrep30, [], $sqlStr30, $internalTests30, false], - [$select31, $sqlPrep31, [], $sqlStr31, $internalTests31, false], - [$select32, $sqlPrep32, [], $sqlStr32, $internalTests32, false], - [$select33, $sqlPrep33, [], $sqlStr33, $internalTests33, false], - [$select34, $sqlPrep34, [], $sqlStr34, $internalTests34, false], - [$select35, $sqlPrep35, [], $sqlStr35, $internalTests35, false], - [$select36, $sqlPrep36, [], $sqlStr36, $internalTests36, true], - [$select37, $sqlPrep37, [], $sqlStr37, $internalTests37, false], - [$select38, $sqlPrep38, [], $sqlStr38, $internalTests38, false], - [$select39, $sqlPrep39, [], $sqlStr39, $internalTests39, false], - [$select40, $sqlPrep40, [], $sqlStr40, $internalTests40, false], - [$select41, $sqlPrep41, [], $sqlStr41, $internalTests41, false], - [$select42, $sqlPrep42, [], $sqlStr42, $internalTests42, false], - [$select43, $sqlPrep43, [], $sqlStr43, $internalTests43, false], - [$select44, $sqlPrep44, [], $sqlStr44, $internalTests44, false], - [$select45, $sqlPrep45, $params45, $sqlStr45, $internalTests45, false], - [$select46, $sqlPrep46, $params46, $sqlStr46, $internalTests46, false], - [$select47, $sqlPrep47, $params47, $sqlStr47, $internalTests47, false], - [$select48, $sqlPrep48, [], $sqlStr48, $internalTests48, false], - [$select49, $sqlPrep49, [], $sqlStr49, $internalTests49, false], - [$select50, $sqlPrep50, [], $sqlStr50, $internalTests50, false], - [$select51, $sqlPrep51, [], $sqlStr51, $internalTests51, false], - [$select52, $sqlPrep52, [], $sqlStr52, $internalTests52, false], - [$select53, $sqlPrep53, $params53, $sqlStr53, $internalTests53, true], - [$select54, $sqlPrep54, [], $sqlStr54, $internalTests54, false], - ]; - // phpcs:enable Generic.Files.LineLength.TooLong + // Second mutation to verify mutability + $select->offset(20); + + // Verify the instance was actually mutated + self::assertEquals(20, $select->getRawState(Select::OFFSET)); + } + + #[TestDox(': unit test: test offset() throws exception when invalid parameter passed')] + public function testOffsetExceptionOnInvalidParameter(): void + { + $select = new Select(); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(Select::class . '::offset expects parameter to be numeric'); + $select->offset('foobar'); } - public function testFromThrowsExceptionWhenTableReadOnly(): void + /** + * @throws ReflectionException + */ + #[TestDox('unit test: Test order()')] + public function testOrder(): void { - $select = new Select('foo'); // Creating with table makes it read-only + $select = new Select(); + $return = $select->order('id DESC'); + self::assertSame($select, $return); // test fluent interface + self::assertEquals(['id DESC'], $select->getRawState('order')); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage( - 'Since this object was created with a table and/or schema in the constructor, it is read only.' + $select = new Select(); + $select->order('id DESC') + ->order('name ASC, age DESC'); + self::assertEquals(['id DESC', 'name ASC', 'age DESC'], $select->getRawState('order')); + + $select = new Select(); + $select->order(['name ASC', 'age DESC']); + self::assertEquals(['name ASC', 'age DESC'], $select->getRawState('order')); + + $select = new Select(); + $select->order(new Expression('RAND()')); + + $sr = new ReflectionObject($select); + $method = $sr->getMethod('processOrder'); + self::assertEquals( + [[['RAND()']]], + $method->invokeArgs($select, [new TrustingSql92Platform()]), + ); + + $select = new Select(); + /** @psalm-suppress InvalidArgument - mocked Operator */ + $select->order( + $this->getMockBuilder(Operator::class) + ->onlyMethods([]) + ->setConstructorArgs(['rating', '<', '10']) + ->getMock(), + ); + $sr = new ReflectionObject($select); + $method = $sr->getMethod('processOrder'); + self::assertEquals( + [[['"rating" < \'10\'']]], + $method->invokeArgs($select, [new TrustingSql92Platform()]), ); - $select->from('bar'); } - public function testFromThrowsExceptionForInvalidTableType(): void + #[TestDox('unit test: Test order() correctly splits parameters.')] + public function testOrderCorrectlySplitsParameter(): void + { + $select = new Select(); + $select->order('name desc'); + self::assertEquals( + 'SELECT * ORDER BY "name" DESC', + $select->getSqlString(new TrustingSql92Platform()), + ); + } + + public function testOrderWithAssociativeArray(): void { $select = new Select(); + $select->from('foo')->order(['name' => 'DESC']); - $this->expectException(TypeError::class); - /** @noinspection ALL */ - $select->from(123); + $sql = $select->getSqlString(new TrustingSql92Platform()); + + self::assertStringContainsString('ORDER BY "name" DESC', $sql); } - public function testFromThrowsExceptionForInvalidArrayFormat(): void + public function testOrderWithExpressionObject(): void { $select = new Select(); + $select->from('foo')->order(new Expression('RAND()')); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('from() expects $table as an array is a single element associative array'); - $select->from(['foo', 'bar']); // Numeric array instead of associative + $sql = $select->getSqlString(new TrustingSql92Platform()); + + self::assertStringContainsString('ORDER BY RAND()', $sql); } - public function testSetSpecificationThrowsExceptionForInvalidName(): void + public function testOrderWithStringContainingDirection(): void { $select = new Select(); + $select->from('foo')->order('name DESC'); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Not a valid specification name.'); - $select->setSpecification('invalid_spec', 'some spec'); + $sql = $select->getSqlString(new TrustingSql92Platform()); + + self::assertStringContainsString('ORDER BY "name" DESC', $sql); } - public function testGetThrowsExceptionForInvalidProperty(): void + /** @noinspection PhpUnusedParameterInspection */ + #[DataProvider('providerData')] + #[TestDox('unit test: Test prepareStatement() will produce expected sql and parameters based on + a variety of provided arguments [uses data provider]')] + public function testPrepareStatement( + Select $select, + string $expectedSqlString, + array $expectedParameters, + mixed $unused1, + mixed $unused2, + bool $useNamedParameters = false, + ): void { + $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + $mockDriver->expects($this->any()) + ->method('formatParameterName') + ->willReturnCallback(static fn(string $name): string => $useNamedParameters ? ":{$name}" : '?'); + + $mockAdapter = $this->createMockAdapter($mockDriver); + + $parameterContainer = new ParameterContainer(); + + $mockStatement = $this->getMockBuilder(StatementInterface::class)->getMock(); + $mockStatement->expects($this->any())->method('getParameterContainer')->willReturn($parameterContainer); + $mockStatement->expects($this->any())->method('setSql')->with($this->equalTo($expectedSqlString)); + + $select->prepareStatement($mockAdapter, $mockStatement); + + if ([] !== $expectedParameters) { + self::assertEquals($expectedParameters, $parameterContainer->getNamedArray()); + } + } + + /** + * @throws ReflectionException + * @noinspection PhpUnusedParameterInspection + */ + #[DataProvider('providerData')] + #[TestDox('unit test: Text process*() methods will return proper array when internally called, + part of extension API')] + public function testProcessMethods( + Select $select, + mixed $unused, + mixed $unused2, + mixed $unused3, + array $internalTests, + ): void { + if ([] === $internalTests) { + $this->expectNotToPerformAssertions(); + return; + } + + $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + $mockDriver->expects($this->any())->method('formatParameterName')->willReturn('?'); + $parameterContainer = new ParameterContainer(); + + $sr = new ReflectionObject($select); + + /** + * @var string $method + * @var array $expected + */ + foreach ($internalTests as $method => $expected) { + $mr = $sr->getMethod($method); + /** @psalm-suppress MixedAssignment */ + $return = $mr->invokeArgs($select, [new Sql92(), $mockDriver, $parameterContainer]); + self::assertEquals($expected, $return); + } + } + + #[TestDox('unit test: Test quantifier() returns Select object (is chainable)')] + public function testQuantifier(): void { $select = new Select(); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Not a valid magic property for this object'); - /** @noinspection ALL */ - $value = $select->invalidProperty; /** @phpstan-ignore-line */ + // First mutation + $result = $select->quantifier(Select::QUANTIFIER_DISTINCT); + + // Verify fluent interface + self::assertSame($select, $result); + + // Verify the first mutation occurred + self::assertEquals(Select::QUANTIFIER_DISTINCT, $select->getRawState('quantifier')); + + // Second mutation to verify mutability + $select->quantifier(Select::QUANTIFIER_ALL); + + // Verify the instance was actually mutated + self::assertEquals(Select::QUANTIFIER_ALL, $select->getRawState('quantifier')); } - public function testCombineWrapsStatementInParentheses(): void + #[TestDox('unit test: Test quantifier() accepts expression')] + public function testQuantifierParameterExpressionInterface(): void { - $select1 = new Select(); - $select1->from('t1'); + $expr = $this->getMockBuilder(ExpressionInterface::class)->onlyMethods([])->getMock(); + $select = new Select(); + /** @psalm-suppress InvalidArgument */ + $select->quantifier($expr); + self::assertSame( + $expr, + $select->getRawState(Select::QUANTIFIER), + ); + } - $select2 = new Select(); - $select2->from('t2'); + #[TestDox('unit test: Test reset() resets internal stat of Select object, based on input')] + public function testReset(): void + { + $select = new Select(); - $select1->combine($select2); + // table + $select->from('foo'); + self::assertEquals('foo', $select->getRawState(Select::TABLE)); + $select->reset(Select::TABLE); + self::assertNull($select->getRawState(Select::TABLE)); - $sql = $select1->getSqlString(new TrustingSql92Platform()); + // columns + $select->columns(['foo']); + self::assertEquals(['foo'], $select->getRawState(Select::COLUMNS)); + $select->reset(Select::COLUMNS); + self::assertEmpty($select->getRawState(Select::COLUMNS)); - self::assertStringContainsString('( SELECT', $sql); - self::assertStringContainsString('UNION', $sql); - self::assertStringContainsString(') UNION (', $sql); + // joins + $select->join('foo', 'id = boo'); + $joins = $select->getRawState(Select::JOINS); + self::assertInstanceOf(Join::class, $joins); + self::assertEquals( + [['name' => 'foo', 'on' => 'id = boo', 'columns' => ['*'], 'type' => 'inner']], + $joins->getJoins(), + ); + $select->reset(Select::JOINS); + $emptyJoins = $select->getRawState(Select::JOINS); + self::assertInstanceOf(Join::class, $emptyJoins); + self::assertEmpty($emptyJoins->getJoins()); + + // where + $select->where('foo = bar'); + /** @var Where $where1 */ + $where1 = $select->getRawState(Select::WHERE); + self::assertEquals(1, $where1->count()); + $select->reset(Select::WHERE); + /** @var Where $where2 */ + $where2 = $select->getRawState(Select::WHERE); + self::assertEquals(0, $where2->count()); + self::assertNotSame($where1, $where2); + + // group + $select->group(['foo']); + self::assertEquals(['foo'], $select->getRawState(Select::GROUP)); + $select->reset(Select::GROUP); + self::assertEmpty($select->getRawState(Select::GROUP)); + + // having + $select->having('foo = bar'); + /** @var Having $having1 */ + $having1 = $select->getRawState(Select::HAVING); + self::assertEquals(1, $having1->count()); + $select->reset(Select::HAVING); + /** @var Having $having2 */ + $having2 = $select->getRawState(Select::HAVING); + self::assertEquals(0, $having2->count()); + self::assertNotSame($having1, $having2); + + // limit + $select->limit(5); + self::assertEquals(5, $select->getRawState(Select::LIMIT)); + $select->reset(Select::LIMIT); + self::assertNull($select->getRawState(Select::LIMIT)); + + // offset + $select->offset(10); + self::assertEquals(10, $select->getRawState(Select::OFFSET)); + $select->reset(Select::OFFSET); + self::assertNull($select->getRawState(Select::OFFSET)); + + // order + $select->order('foo asc'); + self::assertEquals(['foo asc'], $select->getRawState(Select::ORDER)); + $select->reset(Select::ORDER); + self::assertEmpty($select->getRawState(Select::ORDER)); } - public function testOrderWithStringContainingDirection(): void + public function testResetCombine(): void { $select = new Select(); - $select->from('foo')->order('name DESC'); - - $sql = $select->getSqlString(new TrustingSql92Platform()); + $select->from('t1'); + $select->combine(new Select('t2')); + $select->reset(Select::COMBINE); - self::assertStringContainsString('ORDER BY "name" DESC', $sql); + self::assertEmpty($select->getRawState(Select::COMBINE)); } - public function testOrderWithExpressionObject(): void + public function testResetQuantifier(): void { $select = new Select(); - $select->from('foo')->order(new Expression('RAND()')); + $select->from('foo')->quantifier(Select::QUANTIFIER_DISTINCT); + $select->reset(Select::QUANTIFIER); - $sql = $select->getSqlString(new TrustingSql92Platform()); + self::assertNull($select->getRawState(Select::QUANTIFIER)); + } - self::assertStringContainsString('ORDER BY RAND()', $sql); + public function testResetTableThrowsWhenTableReadOnly(): void + { + $select = new Select('foo'); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('read only'); + $select->reset(Select::TABLE); } public function testResetThrowsOnInvalidPart(): void @@ -1600,131 +1569,224 @@ public function testResetThrowsOnInvalidPart(): void self::assertSame($select, $result); } - public function testColumnsWithPrefixDisabled(): void + #[Group('Laminas-5192')] + public function testSelectUsingTableIdentifierWithEmptyScheme(): void { $select = new Select(); - $select->from('foo')->columns(['col'], false); - - $sql = $select->getSqlString(new TrustingSql92Platform()); + $select->from(new TableIdentifier('foo')); + $select->join(new TableIdentifier('bar'), 'foo.id = bar.fooid'); - self::assertStringNotContainsString('"foo"."col"', $sql); - self::assertStringContainsString('"col"', $sql); + self::assertEquals( + 'SELECT "foo".*, "bar".* FROM "foo" INNER JOIN "bar" ON "foo"."id" = "bar"."fooid"', + $select->getSqlString(new TrustingSql92Platform()), + ); } - public function testGetRawStateInitializesLazyProperties(): void + public function testSetSpecificationStoresValidSpecification(): void { - $select = new Select(); - $rawState = $select->getRawState(); + $select = new Select(); + $select->setSpecification('Select', 'CUSTOM %1$s FROM %2$s'); - self::assertInstanceOf(Where::class, $rawState[Select::WHERE]); - self::assertInstanceOf(Join::class, $rawState[Select::JOINS]); - self::assertInstanceOf(Having::class, $rawState[Select::HAVING]); + $rawState = (new ReflectionObject($select))->getProperty('specifications'); + self::assertSame('CUSTOM %1$s FROM %2$s', $rawState->getValue($select)['Select']); } - public function testCombineThrowsWhenAlreadyCombined(): void + public function testSetSpecificationThrowsExceptionForInvalidName(): void { $select = new Select(); - $select->from('t1'); - $select->combine(new Select('t2')); $this->expectException(InvalidArgumentException::class); - $select->combine(new Select('t3')); + $this->expectExceptionMessage('Not a valid specification name.'); + $select->setSpecification('invalid_spec', 'some spec'); } - public function testResetTableThrowsWhenTableReadOnly(): void + #[TestDox('unit test: Test where() accepts Expression (ExpressionInterface) in array')] + public function testWhereAcceptsExpressionInterface(): void { - $select = new Select('foo'); + $select = new Select(); + $select->from('foo') + ->where([ + new Expression('COUNT(?) > ?', [new Identifier('id'), Argument::value(5)]), + ]); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('read only'); - $select->reset(Select::TABLE); + $where = $select->getRawState('where'); + self::assertInstanceOf(Where::class, $where); + self::assertEquals(1, $where->count()); } - public function testResetQuantifier(): void + #[TestDox('unit test: Test where() will accept an array with a string key (containing ?) used as an + expression with placeholder')] + public function testWhereArgument1IsAssociativeArrayContainingReplacementCharacter(): void { $select = new Select(); - $select->from('foo')->quantifier(Select::QUANTIFIER_DISTINCT); - $select->reset(Select::QUANTIFIER); + $select->where(['foo > ?' => 5]); - self::assertNull($select->getRawState(Select::QUANTIFIER)); + /** @var Where $where */ + $where = $select->getRawState('where'); + $predicates = $where->getPredicates(); + $expression = new Value(5); + + self::assertCount(1, $predicates); + self::assertIsArray($predicates[0]); + self::assertInstanceOf(Predicate\Expression::class, $predicates[0][1]); + self::assertEquals(Predicate\PredicateSet::OP_AND, $predicates[0][0]); + self::assertEquals('foo > ?', $predicates[0][1]->getExpression()); + self::assertEquals([$expression], $predicates[0][1]->getParameters()); } - public function testResetCombine(): void + #[TestDox(' + unit test: Test where() will accept any array with string key (without ?) with Predicate throw Exception + ')] + public function testWhereArgument1IsAssociativeArrayIsPredicate(): void { $select = new Select(); - $select->from('t1'); - $select->combine(new Select('t2')); - $select->reset(Select::COMBINE); - - self::assertEmpty($select->getRawState(Select::COMBINE)); + $where = [ + 'name' => new Predicate\Literal("name = 'Ralph'"), + 'age' => new Predicate\Expression('age = ?', 33), + ]; + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Using Predicate must not use string keys'); + $select->where($where); } - public function testSetSpecificationStoresValidSpecification(): void + #[TestDox('unit test: Test where() will accept any array with string key (without ?) to be used + as Operator predicate')] + public function testWhereArgument1IsAssociativeArrayNotContainingReplacementCharacter(): void { $select = new Select(); - $select->setSpecification('Select', 'CUSTOM %1$s FROM %2$s'); + $select->where(['name' => 'Ralph', 'age' => 33]); - $rawState = (new ReflectionObject($select))->getProperty('specifications'); - self::assertSame('CUSTOM %1$s FROM %2$s', $rawState->getValue($select)['Select']); + $identifier1 = new Identifier('name'); + $expression1 = new Value('Ralph'); + $identifier2 = new Identifier('age'); + $expression2 = new Value(33); + + /** @var Where $where */ + $where = $select->getRawState('where'); + $predicates = $where->getPredicates(); + self::assertCount(2, $predicates); + self::assertIsArray($predicates[0]); + self::assertIsArray($predicates[1]); + + self::assertInstanceOf(Operator::class, $predicates[0][1]); + self::assertEquals(Predicate\PredicateSet::OP_AND, $predicates[0][0]); + self::assertEquals($identifier1, $predicates[0][1]->getLeft()); + self::assertEquals($expression1, $predicates[0][1]->getRight()); + + self::assertInstanceOf(Operator::class, $predicates[1][1]); + self::assertEquals(Predicate\PredicateSet::OP_AND, $predicates[1][0]); + self::assertEquals($identifier2, $predicates[1][1]->getLeft()); + self::assertEquals($expression2, $predicates[1][1]->getRight()); + + $select = new Select(); + $select->where(['x = y']); + + /** @var Where $where */ + $where = $select->getRawState('where'); + $predicates = $where->getPredicates(); + self::assertIsArray($predicates[0]); + self::assertInstanceOf(Literal::class, $predicates[0][1]); } - public function testMagicGetJoinsReturnsJoinInstance(): void + #[TestDox('unit test: Test where() will accept a closure to be executed with Where object as argument')] + public function testWhereArgument1IsClosure(): void { $select = new Select(); + /** @var Where $where */ + $where = $select->getRawState('where'); - self::assertInstanceOf(Join::class, $select->joins); + $select->where(static function (Where $what) use ($where): void { + self::assertSame($where, $what); + }); } - public function testCloneDeepCopiesAllSubObjects(): void + #[TestDox('unit test: Test where() will accept an indexed array to be used by joining string expressions')] + public function testWhereArgument1IsIndexedArray(): void { $select = new Select(); - $select->from('foo'); - $select->where('id = 1'); - $select->having('cnt > 0'); - $select->join('bar', 'foo.id = bar.foo_id'); - - $clone = clone $select; - $clone->where('id = 2'); - $clone->having('cnt > 1'); - $clone->join('baz', 'foo.id = baz.foo_id'); + $select->where(['name = "Ralph"']); - self::assertCount(1, $select->where); - self::assertCount(2, $clone->where); - self::assertCount(1, $select->having); - self::assertCount(2, $clone->having); - self::assertCount(1, $select->joins); - self::assertCount(2, $clone->joins); + /** @var Where $where */ + $where = $select->getRawState('where'); + $predicates = $where->getPredicates(); + self::assertCount(1, $predicates); + self::assertIsArray($predicates[0]); + self::assertInstanceOf(Literal::class, $predicates[0][1]); + self::assertEquals(Predicate\PredicateSet::OP_AND, $predicates[0][0]); + self::assertEquals('name = "Ralph"', $predicates[0][1]->getLiteral()); } - public function testOrderWithAssociativeArray(): void + #[TestDox('unit test: Test where() will accept an indexed array to be used by joining string expressions, + combined by OR')] + public function testWhereArgument1IsIndexedArrayArgument2IsOr(): void { $select = new Select(); - $select->from('foo')->order(['name' => 'DESC']); + $select->where(['name = "Ralph"'], Predicate\PredicateSet::OP_OR); - $sql = $select->getSqlString(new TrustingSql92Platform()); + /** @var Where $where */ + $where = $select->getRawState('where'); + $predicates = $where->getPredicates(); + self::assertCount(1, $predicates); + self::assertIsArray($predicates[0]); + self::assertInstanceOf(Literal::class, $predicates[0][1]); + self::assertEquals(Predicate\PredicateSet::OP_OR, $predicates[0][0]); + self::assertEquals('name = "Ralph"', $predicates[0][1]->getLiteral()); + } - self::assertStringContainsString('ORDER BY "name" DESC', $sql); + #[TestDox('unit test: Test where() will accept any Predicate object as-is')] + public function testWhereArgument1IsPredicate(): void + { + $select = new Select(); + $predicate = new Predicate\Predicate([ + new Predicate\Expression('name = ?', 'Ralph'), + new Predicate\Expression('age = ?', 33), + ]); + $select->where($predicate); + + /** @var Where $where */ + $where = $select->getRawState('where'); + $predicates = $where->getPredicates(); + self::assertIsArray($predicates[0]); + self::assertSame($predicate, $predicates[0][1]); } - public function testFromWithAliasArrayResolvesTableAndAlias(): void + #[TestDox('unit test: Test where() will accept a string for the predicate to create an expression predicate')] + public function testWhereArgument1IsString(): void { $select = new Select(); - $select->from(['a' => 'users'])->columns(['id']); + $select->where('x = ?'); - $sql = $select->getSqlString(new TrustingSql92Platform()); + /** @var Where $where */ + $where = $select->getRawState('where'); + $predicates = $where->getPredicates(); + self::assertCount(1, $predicates); + self::assertIsArray($predicates[0]); + self::assertInstanceOf(Predicate\Expression::class, $predicates[0][1]); + self::assertEquals(Predicate\PredicateSet::OP_AND, $predicates[0][0]); + self::assertEquals('x = ?', $predicates[0][1]->getExpression()); - self::assertStringContainsString('"users" AS "a"', $sql); - self::assertStringContainsString('"a"."id"', $sql); + $select = new Select(); + $select->where('x = y'); + + /** @var Where $where */ + $where = $select->getRawState('where'); + $predicates = $where->getPredicates(); + self::assertIsArray($predicates[0]); + self::assertInstanceOf(Literal::class, $predicates[0][1]); } - public function testColumnsWithPrefixDisabledOmitsTablePrefix(): void + #[TestDox('unit test: Test where() will accept a Where object')] + public function testWhereArgument1IsWhereObject(): void { $select = new Select(); - $select->from(['a' => 'users'])->columns(['id'], false); - - $sql = $select->getSqlString(new TrustingSql92Platform()); + $select->where($newWhere = new Where()); + self::assertSame($newWhere, $select->getRawState('where')); + } - self::assertStringContainsString('"id"', $sql); - self::assertStringNotContainsString('"a"."id"', $sql); + #[TestDox('unit test: Test where() returns Select object (is chainable)')] + public function testWhereReturnsSameSelectObject(): void + { + $select = new Select(); + self::assertSame($select, $select->where('x = y')); } } diff --git a/test/unit/Sql/SqlTest.php b/test/unit/Sql/SqlTest.php index c83b8a3ee..3ea0e9fa7 100644 --- a/test/unit/Sql/SqlTest.php +++ b/test/unit/Sql/SqlTest.php @@ -48,38 +48,6 @@ final class SqlTest extends TestCase */ protected Sql $sql; - /** - * @throws Exception - */ - #[Override] - protected function setUp(): void - { - // mock the adapter, driver, and parts - $mockResult = $this->createMock(ResultInterface::class); - - $mockStatement = $this->createMock(StatementInterface::class); - $mockStatement->expects($this->any())->method('execute')->willReturn($mockResult); - - $mockConnection = $this->getMockBuilder(ConnectionInterface::class)->onlyMethods([])->getMock(); - - $mockDriver = $this->getMockBuilder(DriverInterface::class)->onlyMethods([])->getMock(); - $mockDriver->expects($this->any())->method('createStatement')->willReturn($mockStatement); - $mockDriver->expects($this->any())->method('getConnection')->willReturn($mockConnection); - $mockDriver->expects($this->any())->method('formatParameterName')->willReturn('?'); - - // setup mock adapter - $this->mockAdapter = $this->getMockBuilder(Adapter::class) - ->onlyMethods([]) - ->setConstructorArgs([ - $mockDriver, - new TestAsset\TrustingSql92Platform(), - new TestAsset\TemporaryResultSet(), - ]) - ->getMock(); - - $this->sql = new Sql($this->mockAdapter, 'foo'); - } - // @codingStandardsIgnoreStart public function test__construct(): void { @@ -96,43 +64,32 @@ public function test__construct(): void $sql->setTable(null); } - public function testSelect(): void + public function testBuildSqlString(): void { - $select = $this->sql->select(); - self::assertInstanceOf(Select::class, $select); - self::assertSame('foo', $select->getRawState('table')); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage( - 'This Sql object is intended to work with only the table "foo" provided at construction time.' - ); - $this->sql->select('bar'); + $select = $this->sql->select()->where(['bar' => 'baz']); + $sqlString = $this->sql->buildSqlString($select); + self::assertEquals('SELECT "foo".* FROM "foo" WHERE "bar" = \'baz\'', $sqlString); } - public function testInsert(): void + public function testBuildSqlStringThrowsWhenPlatformNotSqlInterface(): void { - $insert = $this->sql->insert(); - self::assertInstanceOf(Insert::class, $insert); - self::assertSame('foo', $insert->getRawState('table')); + $decorator = $this->createMock(PlatformDecoratorInterface::class); + $platform = $this->createMock(PlatformInterface::class); + $platform->method('getSqlPlatformDecorator')->willReturn($decorator); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage( - 'This Sql object is intended to work with only the table "foo" provided at construction time.' - ); - $this->sql->insert('bar'); - } + $adapter = $this->getMockBuilder(Adapter::class) + ->setConstructorArgs([ + $this->createMock(DriverInterface::class), + $platform, + ]) + ->getMock(); + $adapter->method('getPlatform')->willReturn($platform); - public function testUpdate(): void - { - $update = $this->sql->update(); - self::assertInstanceOf(Update::class, $update); - self::assertSame('foo', $update->getRawState('table')); + $sql = new Sql($adapter); - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage( - 'This Sql object is intended to work with only the table "foo" provided at construction time.' - ); - $this->sql->update('bar'); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('does not implement SqlInterface'); + $sql->buildSqlString($this->sql->select()); } public function testDelete(): void @@ -144,64 +101,52 @@ public function testDelete(): void $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - 'This Sql object is intended to work with only the table "foo" provided at construction time.' + 'This Sql object is intended to work with only the table "foo" provided at construction time.', ); $this->sql->delete('bar'); } - public function testPrepareStatementForSqlObject(): void - { - $insert = $this->sql->insert()->columns(['foo'])->values(['foo' => 'bar']); - $stmt = $this->sql->prepareStatementForSqlObject($insert); - self::assertInstanceOf(StatementInterface::class, $stmt); - } - - public function testBuildSqlString(): void - { - $select = $this->sql->select()->where(['bar' => 'baz']); - $sqlString = $this->sql->buildSqlString($select); - self::assertEquals('SELECT "foo".* FROM "foo" WHERE "bar" = \'baz\'', $sqlString); - } - - public function testSelectThrowsWhenTableConflicts(): void + public function testDeleteThrowsWhenTableConflicts(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - 'This Sql object is intended to work with only the table "foo" provided at construction time.' + 'This Sql object is intended to work with only the table "foo" provided at construction time.', ); - $this->sql->select(new TableIdentifier('bar')); + $this->sql->delete(new TableIdentifier('bar')); } - public function testInsertThrowsWhenTableConflicts(): void + public function testGetSqlPlatformReturnsPlatformDecorator(): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage( - 'This Sql object is intended to work with only the table "foo" provided at construction time.' - ); - $this->sql->insert(new TableIdentifier('bar')); + self::assertInstanceOf(PlatformDecoratorInterface::class, $this->sql->getSqlPlatform()); } - public function testUpdateThrowsWhenTableConflicts(): void + public function testInsert(): void { + $insert = $this->sql->insert(); + self::assertInstanceOf(Insert::class, $insert); + self::assertSame('foo', $insert->getRawState('table')); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - 'This Sql object is intended to work with only the table "foo" provided at construction time.' + 'This Sql object is intended to work with only the table "foo" provided at construction time.', ); - $this->sql->update(new TableIdentifier('bar')); + $this->sql->insert('bar'); } - public function testDeleteThrowsWhenTableConflicts(): void + public function testInsertThrowsWhenTableConflicts(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - 'This Sql object is intended to work with only the table "foo" provided at construction time.' + 'This Sql object is intended to work with only the table "foo" provided at construction time.', ); - $this->sql->delete(new TableIdentifier('bar')); + $this->sql->insert(new TableIdentifier('bar')); } - public function testGetSqlPlatformReturnsPlatformDecorator(): void + public function testPrepareStatementForSqlObject(): void { - self::assertInstanceOf(PlatformDecoratorInterface::class, $this->sql->getSqlPlatform()); + $insert = $this->sql->insert()->columns(['foo'])->values(['foo' => 'bar']); + $stmt = $this->sql->prepareStatementForSqlObject($insert); + self::assertInstanceOf(StatementInterface::class, $stmt); } public function testPrepareStatementThrowsWhenPlatformNotPreparable(): void @@ -225,24 +170,79 @@ public function testPrepareStatementThrowsWhenPlatformNotPreparable(): void $sql->prepareStatementForSqlObject($this->sql->select()); } - public function testBuildSqlStringThrowsWhenPlatformNotSqlInterface(): void + public function testSelect(): void { - $decorator = $this->createMock(PlatformDecoratorInterface::class); - $platform = $this->createMock(PlatformInterface::class); - $platform->method('getSqlPlatformDecorator')->willReturn($decorator); + $select = $this->sql->select(); + self::assertInstanceOf(Select::class, $select); + self::assertSame('foo', $select->getRawState('table')); - $adapter = $this->getMockBuilder(Adapter::class) + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage( + 'This Sql object is intended to work with only the table "foo" provided at construction time.', + ); + $this->sql->select('bar'); + } + + public function testSelectThrowsWhenTableConflicts(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage( + 'This Sql object is intended to work with only the table "foo" provided at construction time.', + ); + $this->sql->select(new TableIdentifier('bar')); + } + + public function testUpdate(): void + { + $update = $this->sql->update(); + self::assertInstanceOf(Update::class, $update); + self::assertSame('foo', $update->getRawState('table')); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage( + 'This Sql object is intended to work with only the table "foo" provided at construction time.', + ); + $this->sql->update('bar'); + } + + public function testUpdateThrowsWhenTableConflicts(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage( + 'This Sql object is intended to work with only the table "foo" provided at construction time.', + ); + $this->sql->update(new TableIdentifier('bar')); + } + + /** + * @throws Exception + */ + #[Override] + protected function setUp(): void + { + // mock the adapter, driver, and parts + $mockResult = $this->createMock(ResultInterface::class); + + $mockStatement = $this->createMock(StatementInterface::class); + $mockStatement->expects($this->any())->method('execute')->willReturn($mockResult); + + $mockConnection = $this->getMockBuilder(ConnectionInterface::class)->onlyMethods([])->getMock(); + + $mockDriver = $this->getMockBuilder(DriverInterface::class)->onlyMethods([])->getMock(); + $mockDriver->expects($this->any())->method('createStatement')->willReturn($mockStatement); + $mockDriver->expects($this->any())->method('getConnection')->willReturn($mockConnection); + $mockDriver->expects($this->any())->method('formatParameterName')->willReturn('?'); + + // setup mock adapter + $this->mockAdapter = $this->getMockBuilder(Adapter::class) + ->onlyMethods([]) ->setConstructorArgs([ - $this->createMock(DriverInterface::class), - $platform, + $mockDriver, + new TestAsset\TrustingSql92Platform(), + new TestAsset\TemporaryResultSet(), ]) ->getMock(); - $adapter->method('getPlatform')->willReturn($platform); - $sql = new Sql($adapter); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('does not implement SqlInterface'); - $sql->buildSqlString($this->sql->select()); + $this->sql = new Sql($this->mockAdapter, 'foo'); } } diff --git a/test/unit/Sql/TableIdentifierTest.php b/test/unit/Sql/TableIdentifierTest.php index 2e6c717ec..c0b85f0c6 100644 --- a/test/unit/Sql/TableIdentifierTest.php +++ b/test/unit/Sql/TableIdentifierTest.php @@ -13,19 +13,37 @@ use stdClass; use TypeError; -use function array_merge; - /** * Tests for {@see TableIdentifier} */ #[CoversClass(TableIdentifier::class)] class TableIdentifierTest extends TestCase { - public function testGetTable(): void + /** + * Data provider + * + * @return array[] + */ + public static function invalidSchemaProvider(): array { - $tableIdentifier = new TableIdentifier('foo'); + return [ + [''], + [new stdClass()], + [[]], + ]; + } - self::assertSame('foo', $tableIdentifier->getTable()); + /** + * Data provider + * + * @return array[] + */ + public static function invalidTableProvider(): array + { + return [ + [null], + ...self::invalidSchemaProvider(), + ]; } public function testGetDefaultSchema(): void @@ -42,15 +60,6 @@ public function testGetSchema(): void self::assertSame('bar', $tableIdentifier->getSchema()); } - public function testGetTableFromObjectStringCast(): void - { - $table = new ObjectToString('castResult'); - $tableIdentifier = new TableIdentifier((string) $table); - - self::assertSame('castResult', $tableIdentifier->getTable()); - self::assertSame('castResult', $tableIdentifier->getTable()); - } - /** * @todo Review test to see if relevant? */ @@ -63,46 +72,35 @@ public function testGetSchemaFromObjectStringCast(): void self::assertSame('castResult', $tableIdentifier->getSchema()); } - #[DataProvider('invalidTableProvider')] - public function testRejectsInvalidTable(mixed $invalidTable): void + public function testGetTable(): void { - $this->expectException($invalidTable === '' ? InvalidArgumentException::class : TypeError::class); - /** @psalm-suppress MixedArgument */ - new TableIdentifier($invalidTable); + $tableIdentifier = new TableIdentifier('foo'); + + self::assertSame('foo', $tableIdentifier->getTable()); + } + + public function testGetTableFromObjectStringCast(): void + { + $table = new ObjectToString('castResult'); + $tableIdentifier = new TableIdentifier((string) $table); + + self::assertSame('castResult', $tableIdentifier->getTable()); + self::assertSame('castResult', $tableIdentifier->getTable()); } #[DataProvider('invalidSchemaProvider')] public function testRejectsInvalidSchema(mixed $invalidSchema): void { - $this->expectException($invalidSchema === '' ? InvalidArgumentException::class : TypeError::class); + $this->expectException('' === $invalidSchema ? InvalidArgumentException::class : TypeError::class); /** @psalm-suppress MixedArgument */ new TableIdentifier('foo', $invalidSchema); } - /** - * Data provider - * - * @return array[] - */ - public static function invalidTableProvider(): array - { - return array_merge( - [[null]], - self::invalidSchemaProvider() - ); - } - - /** - * Data provider - * - * @return array[] - */ - public static function invalidSchemaProvider(): array + #[DataProvider('invalidTableProvider')] + public function testRejectsInvalidTable(mixed $invalidTable): void { - return [ - [''], - [new stdClass()], - [[]], - ]; + $this->expectException('' === $invalidTable ? InvalidArgumentException::class : TypeError::class); + /** @psalm-suppress MixedArgument */ + new TableIdentifier($invalidTable); } } diff --git a/test/unit/Sql/UpdateTest.php b/test/unit/Sql/UpdateTest.php index d653fb69d..1e23e4b7b 100644 --- a/test/unit/Sql/UpdateTest.php +++ b/test/unit/Sql/UpdateTest.php @@ -63,27 +63,51 @@ final class UpdateTest extends TestCase protected Update $update; - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ - #[Override] - protected function setUp(): void + public function testCloneDeepCopiesSetWhereAndJoins(): void { - $this->update = new Update(); + $this->update + ->table('foo') + ->set(['bar' => 'baz']) + ->where('x = y') + ->join('other', 'foo.id = other.id'); + + $clone = clone $this->update; + + $clone->set(['bar' => 'changed']); + $clone->where('z = w'); + $clone->join('another', 'foo.id = another.id'); + + self::assertEquals(['bar' => 'baz'], $this->update->getRawState('set')); + self::assertEquals(['bar' => 'changed'], $clone->getRawState('set')); + + self::assertNotSame( + $this->update->getRawState('where'), + $clone->getRawState('where'), + ); + + self::assertNotSame( + $this->update->getRawState('joins'), + $clone->getRawState('joins'), + ); } - /** - * @throws ReflectionException - */ - public function testTable(): void + public function testCloneUpdate(): void { - $this->update->table('foo'); - self::assertEquals('foo', $this->readAttribute($this->update, 'table')); + $update1 = clone $this->update; + $update1->table('foo') + ->set(['bar' => 'baz']) + ->where('x = y'); - $tableIdentifier = new TableIdentifier('foo', 'bar'); - $this->update->table($tableIdentifier); - self::assertEquals($tableIdentifier, $this->readAttribute($this->update, 'table')); + $update2 = clone $this->update; + $update2->table('foo') + ->set(['bar' => 'baz']) + ->where([ + 'id = ?' => 1, + ]); + self::assertEquals( + 'UPDATE "foo" SET "bar" = \'baz\' WHERE id = \'1\'', + $update2->getSqlString(new TrustingSql92Platform()), + ); } /** @@ -95,81 +119,180 @@ public function testConstruct(): void self::assertEquals('foo', $this->readAttribute($update, 'table')); } - public function testSet(): void + public function testConstructWithTableIdentifier(): void { - $this->update->set(['foo' => 'bar']); - self::assertEquals(['foo' => 'bar'], $this->update->getRawState('set')); + $tableIdentifier = new TableIdentifier('foo', 'bar'); + $update = new Update($tableIdentifier); + + self::assertEquals($tableIdentifier, $update->getRawState('table')); } - public function testSortableSet(): void + public function testGetRawState(): void { - $this->update->set([ - 'two' => 'с_two', - 'three' => 'с_three', - ]); - $this->update->set(['one' => 'с_one'], '10'); + $this->update + ->table('foo') + ->set(['bar' => 'baz']) + ->where('x = y'); - self::assertEquals( - [ - 'one' => 'с_one', - 'two' => 'с_two', - 'three' => 'с_three', - ], - $this->update->getRawState('set') - ); + self::assertEquals('foo', $this->update->getRawState('table')); + self::assertTrue($this->update->getRawState('emptyWhereProtection')); + self::assertEquals(['bar' => 'baz'], $this->update->getRawState('set')); + self::assertInstanceOf(Where::class, $this->update->getRawState('where')); } - /** - * @throws ReflectionException - */ - public function testWhere(): void + public function testGetRawStateReturnsAllState(): void { - $this->update->where('x = y'); - $this->update->where(['foo > ?' => 5]); - $this->update->where(['id' => 2]); - $this->update->where(['a = b'], PredicateSet::OP_OR); - $this->update->where(['c1' => null]); - $this->update->where(['c2' => [1, 2, 3]]); - $this->update->where([new IsNotNull('c3')]); + $this->update + ->table('foo') + ->set(['bar' => 'baz']) + ->where('x = y'); - $where = $this->update->where; + $rawState = $this->update->getRawState(); - $predicates = $this->readAttribute($where, 'predicates'); + self::assertIsArray($rawState); + self::assertArrayHasKey('table', $rawState); + self::assertArrayHasKey('set', $rawState); + self::assertArrayHasKey('where', $rawState); + self::assertArrayHasKey('emptyWhereProtection', $rawState); + self::assertArrayHasKey('joins', $rawState); - self::assertIsArray($predicates); + self::assertEquals('foo', $rawState['table']); + self::assertEquals(['bar' => 'baz'], $rawState['set']); + self::assertInstanceOf(Where::class, $rawState['where']); + self::assertInstanceOf(Join::class, $rawState['joins']); + self::assertTrue($rawState['emptyWhereProtection']); + } - self::assertEquals('AND', $predicates[0][0] ?? ''); - self::assertInstanceOf(Literal::class, $predicates[0][1] ?? null); + public function testGetSqlString(): void + { + $this->update + ->table('foo') + ->set(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]) + ->where('x = y'); - self::assertEquals('AND', $predicates[1][0] ?? ''); - self::assertInstanceOf(\PhpDb\Sql\Predicate\Expression::class, $predicates[1][1] ?? null); + self::assertEquals( + 'UPDATE "foo" SET "bar" = \'baz\', "boo" = NOW(), "bam" = NULL WHERE x = y', + $this->update->getSqlString(new TrustingSql92Platform()), + ); - self::assertEquals('AND', $predicates[2][0] ?? ''); - self::assertInstanceOf(Operator::class, $predicates[2][1] ?? null); + // with TableIdentifier + $this->update = new Update(); + $this->update + ->table(new TableIdentifier('foo', 'sch')) + ->set(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]) + ->where('x = y'); - self::assertEquals('OR', $predicates[3][0] ?? ''); - self::assertInstanceOf(Literal::class, $predicates[3][1] ?? null); + self::assertEquals( + 'UPDATE "sch"."foo" SET "bar" = \'baz\', "boo" = NOW(), "bam" = NULL WHERE x = y', + $this->update->getSqlString(new TrustingSql92Platform()), + ); + } - self::assertEquals('AND', $predicates[4][0] ?? ''); - self::assertInstanceOf(IsNull::class, $predicates[4][1] ?? null); + #[Group('6768')] + #[Group('6773')] + public function testGetSqlStringForFalseUpdateValueParameter(): void + { + $this->update = new Update(); + $this->update + ->table(new TableIdentifier('foo', 'sch')) + ->set(['bar' => false, 'boo' => 'test', 'bam' => true]) + ->where('x = y'); + self::assertEquals( + 'UPDATE "sch"."foo" SET "bar" = \'\', "boo" = \'test\', "bam" = \'1\' WHERE x = y', + $this->update->getSqlString(new TrustingSql92Platform()), + ); + } - self::assertEquals('AND', $predicates[5][0] ?? ''); - self::assertInstanceOf(In::class, $predicates[5][1] ?? null); + public function testGetSqlStringWithEmptyWhere(): void + { + $this->update->table('foo') + ->set(['bar' => 'baz']); - self::assertEquals('AND', $predicates[6][0] ?? ''); - self::assertInstanceOf(IsNotNull::class, $predicates[6][1] ?? null); + self::assertEquals( + 'UPDATE "foo" SET "bar" = \'baz\'', + $this->update->getSqlString(new TrustingSql92Platform()), + ); + } - $where = new Where(); - $this->update->where($where); - self::assertSame($where, $this->update->where); + public function testGetUpdate(): void + { + $getWhere = $this->update->__get('where'); + self::assertInstanceOf(Where::class, $getWhere); + } - $this->update->where(function (Where $what) use ($where): void { - self::assertSame($where, $what); - }); + public function testGetUpdateFails(): void + { + /** @psalm-suppress UndefinedThisPropertyFetch - Ensure non-existent property returns null */ + $getWhat = $this->update->__get('what'); + self::assertNull($getWhat); + } - $this->expectException(TypeError::class); - /** @noinspection PhpStrictTypeCheckingInspection */ - $this->update->where(null); + public function testJoin(): void + { + $this->update->table('Document'); + $this->update + ->set(['x' => 'y']) + ->join( + 'User', // table name + 'User.UserId = Document.UserId', // expression to join on + // default JOIN INNER + ) + ->join( + 'Category', + 'Category.CategoryId = Document.CategoryId', + Join::JOIN_LEFT, // (optional), one of inner, outer, left, right + ); + + self::assertEquals( + 'UPDATE "Document" INNER JOIN "User" ON "User"."UserId" = "Document"."UserId" ' + . 'LEFT JOIN "Category" ON "Category"."CategoryId" = "Document"."CategoryId" SET "x" = \'y\'', + $this->update->getSqlString(new TrustingSql92Platform()), + ); + } + + #[TestDox('unit test: Test join() returns Update object (is chainable)')] + public function testJoinChainable(): void + { + $return = $this->update->join('baz', 'foo.fooId = baz.fooId', Join::JOIN_LEFT); + self::assertSame($this->update, $return); + } + + /** + * Here test if we want update fields from specific table. + * Important when we're updating fields that are existing in several tables in one query. + * The same test as above but here we will specify table in update params + */ + public function testJoinMultiUpdate(): void + { + $this->update->table('Document'); + $this->update + ->set(['Documents.x' => 'y']) + ->join( + 'User', + 'User.UserId = Document.UserId', + ) + ->join( + 'Category', + 'Category.CategoryId = Document.CategoryId', + Join::JOIN_LEFT, + ); + + self::assertEquals( + 'UPDATE "Document" INNER JOIN "User" ON "User"."UserId" = "Document"."UserId" ' + . 'LEFT JOIN "Category" ON "Category"."CategoryId" = "Document"."CategoryId" SET "Documents"."x" = \'y\'', + $this->update->getSqlString(new TrustingSql92Platform()), + ); + } + + public function testJoinWithTableIdentifier(): void + { + $this->update + ->table('foo') + ->set(['x' => 'y']) + ->join(new TableIdentifier('bar', 'schema'), 'foo.id = bar.foo_id'); + + $sql = $this->update->getSqlString(new TrustingSql92Platform()); + self::assertStringContainsString('JOIN "schema"."bar"', $sql); } #[Group('Laminas-240')] @@ -181,22 +304,10 @@ public function testPassingMultipleKeyValueInWhereClause(): void $update->where(['id1' => 'val1', 'id2' => 'val2']); self::assertEquals( 'UPDATE "table" SET "fld1" = \'val1\' WHERE "id1" = \'val1\' AND "id2" = \'val2\'', - $update->getSqlString(new TrustingSql92Platform()) + $update->getSqlString(new TrustingSql92Platform()), ); } - public function testGetRawState(): void - { - $this->update->table('foo') - ->set(['bar' => 'baz']) - ->where('x = y'); - - self::assertEquals('foo', $this->update->getRawState('table')); - self::assertTrue($this->update->getRawState('emptyWhereProtection')); - self::assertEquals(['bar' => 'baz'], $this->update->getRawState('set')); - self::assertInstanceOf(Where::class, $this->update->getRawState('where')); - } - public function testPrepareStatement(): void { $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); @@ -212,7 +323,8 @@ public function testPrepareStatement(): void ->method('setSql') ->with($this->equalTo('UPDATE "foo" SET "bar" = ?, "boo" = NOW() WHERE x = y')); - $this->update->table('foo') + $this->update + ->table('foo') ->set(['bar' => 'baz', 'boo' => new Expression('NOW()')]) ->where('x = y'); @@ -234,105 +346,85 @@ public function testPrepareStatement(): void ->method('setSql') ->with($this->equalTo('UPDATE "sch"."foo" SET "bar" = ?, "boo" = NOW() WHERE x = y')); - $this->update->table(new TableIdentifier('foo', 'sch')) + $this->update + ->table(new TableIdentifier('foo', 'sch')) ->set(['bar' => 'baz', 'boo' => new Expression('NOW()')]) ->where('x = y'); $this->update->prepareStatement($mockAdapter, $mockStatement); } - public function testGetSqlString(): void + public function testProcessSetWithPdoDriverUsesDriverColumnQuoting(): void { - $this->update->table('foo') - ->set(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]) - ->where('x = y'); + $mockDriver = $this->getMockBuilder(PdoDriverInterface::class)->getMock(); + $mockDriver->expects($this->any())->method('getPrepareType')->willReturn('positional'); + $mockDriver->expects($this->any()) + ->method('formatParameterName') + ->willReturnCallback(static fn(string $name): string => ":{$name}"); + $mockAdapter = $this->createMockAdapter($mockDriver); - self::assertEquals( - 'UPDATE "foo" SET "bar" = \'baz\', "boo" = NOW(), "bam" = NULL WHERE x = y', - $this->update->getSqlString(new TrustingSql92Platform()) - ); + $mockStatement = new StatementContainer(); - // with TableIdentifier - $this->update = new Update(); - $this->update->table(new TableIdentifier('foo', 'sch')) - ->set(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]) - ->where('x = y'); + $this->update->table('foo') + ->set(['bar' => 'baz', 'boo' => 'qux']); - self::assertEquals( - 'UPDATE "sch"."foo" SET "bar" = \'baz\', "boo" = NOW(), "bam" = NULL WHERE x = y', - $this->update->getSqlString(new TrustingSql92Platform()) - ); - } + $this->update->prepareStatement($mockAdapter, $mockStatement); - #[Group('6768')] - #[Group('6773')] - public function testGetSqlStringForFalseUpdateValueParameter(): void - { - $this->update = new Update(); - $this->update->table(new TableIdentifier('foo', 'sch')) - ->set(['bar' => false, 'boo' => 'test', 'bam' => true]) - ->where('x = y'); - self::assertEquals( - 'UPDATE "sch"."foo" SET "bar" = \'\', "boo" = \'test\', "bam" = \'1\' WHERE x = y', - $this->update->getSqlString(new TrustingSql92Platform()) - ); + $sql = $mockStatement->getSql(); + self::assertStringContainsString(':c_0', $sql); + self::assertStringContainsString(':c_1', $sql); } - public function testGetUpdate(): void + public function testSet(): void { - $getWhere = $this->update->__get('where'); - self::assertInstanceOf(Where::class, $getWhere); + $this->update->set(['foo' => 'bar']); + self::assertEquals(['foo' => 'bar'], $this->update->getRawState('set')); } - public function testGetUpdateFails(): void + public function testSetWithMergeFlag(): void { - /** @psalm-suppress UndefinedThisPropertyFetch - Ensure non-existent property returns null */ - $getWhat = $this->update->__get('what'); - self::assertNull($getWhat); + $this->update->set(['foo' => 'bar']); + $this->update->set(['baz' => 'qux'], Update::VALUES_MERGE); + + $set = $this->update->getRawState('set'); + self::assertEquals(['foo' => 'bar', 'baz' => 'qux'], $set); } - public function testCloneUpdate(): void + public function testSetWithNonStringKeyThrowsException(): void { - $update1 = clone $this->update; - $update1->table('foo') - ->set(['bar' => 'baz']) - ->where('x = y'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('set() expects a string for the value key'); - $update2 = clone $this->update; - $update2->table('foo') - ->set(['bar' => 'baz']) - ->where([ - 'id = ?' => 1, - ]); - self::assertEquals( - 'UPDATE "foo" SET "bar" = \'baz\' WHERE id = \'1\'', - $update2->getSqlString(new TrustingSql92Platform()) - ); + /** @psalm-suppress InvalidArgument - Testing invalid argument handling */ + $this->update->set([0 => 'value']); } - #[CoversNothing] - public function testSpecificationconstantsCouldBeOverridedByExtensionInPrepareStatement(): void + public function testSetWithNumericPriority(): void { - $updateIgnore = new UpdateIgnore(); - - $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); - $mockDriver->expects($this->any())->method('getPrepareType')->willReturn('positional'); - $mockDriver->expects($this->any())->method('formatParameterName')->willReturn('?'); - $mockAdapter = $this->createMockAdapter($mockDriver); - - $mockStatement = $this->getMockBuilder(StatementInterface::class)->getMock(); - $pContainer = new ParameterContainer([]); - $mockStatement->expects($this->any())->method('getParameterContainer')->willReturn($pContainer); + $this->update->set(['three' => 'c'], 30); + $this->update->set(['one' => 'a'], 10); + $this->update->set(['two' => 'b'], 20); - $mockStatement->expects($this->once()) - ->method('setSql') - ->with($this->equalTo('UPDATE IGNORE "foo" SET "bar" = ?, "boo" = NOW() WHERE x = y')); + $set = $this->update->getRawState('set'); + self::assertEquals(['one' => 'a', 'two' => 'b', 'three' => 'c'], $set); + } - $updateIgnore->table('foo') - ->set(['bar' => 'baz', 'boo' => new Expression('NOW()')]) - ->where('x = y'); + public function testSortableSet(): void + { + $this->update->set([ + 'two' => 'с_two', + 'three' => 'с_three', + ]); + $this->update->set(['one' => 'с_one'], '10'); - $updateIgnore->prepareStatement($mockAdapter, $mockStatement); + self::assertEquals( + [ + 'one' => 'с_one', + 'two' => 'с_two', + 'three' => 'с_three', + ], + $this->update->getRawState('set'), + ); } #[CoversNothing] @@ -340,165 +432,125 @@ public function testSpecificationconstantsCouldBeOverridedByExtensionInGetSqlStr { $this->update = new UpdateIgnore(); - $this->update->table('foo') + $this->update + ->table('foo') ->set(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]) ->where('x = y'); self::assertEquals( 'UPDATE IGNORE "foo" SET "bar" = \'baz\', "boo" = NOW(), "bam" = NULL WHERE x = y', - $this->update->getSqlString(new TrustingSql92Platform()) + $this->update->getSqlString(new TrustingSql92Platform()), ); // with TableIdentifier $this->update = new UpdateIgnore(); - $this->update->table(new TableIdentifier('foo', 'sch')) + $this->update + ->table(new TableIdentifier('foo', 'sch')) ->set(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]) ->where('x = y'); self::assertEquals( 'UPDATE IGNORE "sch"."foo" SET "bar" = \'baz\', "boo" = NOW(), "bam" = NULL WHERE x = y', - $this->update->getSqlString(new TrustingSql92Platform()) + $this->update->getSqlString(new TrustingSql92Platform()), ); } - public function testJoin(): void + #[CoversNothing] + public function testSpecificationconstantsCouldBeOverridedByExtensionInPrepareStatement(): void { - $this->update->table('Document'); - $this->update->set(['x' => 'y']) - ->join( - 'User', // table name - 'User.UserId = Document.UserId' // expression to join on - // default JOIN INNER - ) - ->join( - 'Category', - 'Category.CategoryId = Document.CategoryId', - Join::JOIN_LEFT // (optional), one of inner, outer, left, right - ); + $updateIgnore = new UpdateIgnore(); - self::assertEquals( - 'UPDATE "Document" INNER JOIN "User" ON "User"."UserId" = "Document"."UserId" ' - . 'LEFT JOIN "Category" ON "Category"."CategoryId" = "Document"."CategoryId" SET "x" = \'y\'', - $this->update->getSqlString(new TrustingSql92Platform()) - ); - } + $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + $mockDriver->expects($this->any())->method('getPrepareType')->willReturn('positional'); + $mockDriver->expects($this->any())->method('formatParameterName')->willReturn('?'); + $mockAdapter = $this->createMockAdapter($mockDriver); - /** - * Here test if we want update fields from specific table. - * Important when we're updating fields that are existing in several tables in one query. - * The same test as above but here we will specify table in update params - */ - public function testJoinMultiUpdate(): void - { - $this->update->table('Document'); - $this->update->set(['Documents.x' => 'y']) - ->join( - 'User', - 'User.UserId = Document.UserId' - ) - ->join( - 'Category', - 'Category.CategoryId = Document.CategoryId', - Join::JOIN_LEFT - ); + $mockStatement = $this->getMockBuilder(StatementInterface::class)->getMock(); + $pContainer = new ParameterContainer([]); + $mockStatement->expects($this->any())->method('getParameterContainer')->willReturn($pContainer); - self::assertEquals( - 'UPDATE "Document" INNER JOIN "User" ON "User"."UserId" = "Document"."UserId" ' - . 'LEFT JOIN "Category" ON "Category"."CategoryId" = "Document"."CategoryId" SET "Documents"."x" = \'y\'', - $this->update->getSqlString(new TrustingSql92Platform()) - ); - } + $mockStatement->expects($this->once()) + ->method('setSql') + ->with($this->equalTo('UPDATE IGNORE "foo" SET "bar" = ?, "boo" = NOW() WHERE x = y')); - #[TestDox('unit test: Test join() returns Update object (is chainable)')] - public function testJoinChainable(): void - { - $return = $this->update->join('baz', 'foo.fooId = baz.fooId', Join::JOIN_LEFT); - self::assertSame($this->update, $return); + $updateIgnore->table('foo') + ->set(['bar' => 'baz', 'boo' => new Expression('NOW()')]) + ->where('x = y'); + + $updateIgnore->prepareStatement($mockAdapter, $mockStatement); } - public function testSetWithNonStringKeyThrowsException(): void + /** + * @throws ReflectionException + */ + public function testTable(): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('set() expects a string for the value key'); + $this->update->table('foo'); + self::assertEquals('foo', $this->readAttribute($this->update, 'table')); - /** @psalm-suppress InvalidArgument - Testing invalid argument handling */ - $this->update->set([0 => 'value']); + $tableIdentifier = new TableIdentifier('foo', 'bar'); + $this->update->table($tableIdentifier); + self::assertEquals($tableIdentifier, $this->readAttribute($this->update, 'table')); } - public function testSetWithMergeFlag(): void + /** + * @throws ReflectionException + */ + public function testWhere(): void { - $this->update->set(['foo' => 'bar']); - $this->update->set(['baz' => 'qux'], Update::VALUES_MERGE); + $this->update->where('x = y'); + $this->update->where(['foo > ?' => 5]); + $this->update->where(['id' => 2]); + $this->update->where(['a = b'], PredicateSet::OP_OR); + $this->update->where(['c1' => null]); + $this->update->where(['c2' => [1, 2, 3]]); + $this->update->where([new IsNotNull('c3')]); - $set = $this->update->getRawState('set'); - self::assertEquals(['foo' => 'bar', 'baz' => 'qux'], $set); - } + $where = $this->update->where; - public function testSetWithNumericPriority(): void - { - $this->update->set(['three' => 'c'], 30); - $this->update->set(['one' => 'a'], 10); - $this->update->set(['two' => 'b'], 20); + $predicates = $this->readAttribute($where, 'predicates'); - $set = $this->update->getRawState('set'); - self::assertEquals(['one' => 'a', 'two' => 'b', 'three' => 'c'], $set); - } + self::assertIsArray($predicates); - public function testConstructWithTableIdentifier(): void - { - $tableIdentifier = new TableIdentifier('foo', 'bar'); - $update = new Update($tableIdentifier); + self::assertEquals('AND', $predicates[0][0] ?? ''); + self::assertInstanceOf(Literal::class, $predicates[0][1] ?? null); - self::assertEquals($tableIdentifier, $update->getRawState('table')); - } + self::assertEquals('AND', $predicates[1][0] ?? ''); + self::assertInstanceOf(\PhpDb\Sql\Predicate\Expression::class, $predicates[1][1] ?? null); - public function testGetSqlStringWithEmptyWhere(): void - { - $this->update->table('foo') - ->set(['bar' => 'baz']); + self::assertEquals('AND', $predicates[2][0] ?? ''); + self::assertInstanceOf(Operator::class, $predicates[2][1] ?? null); - self::assertEquals( - 'UPDATE "foo" SET "bar" = \'baz\'', - $this->update->getSqlString(new TrustingSql92Platform()) - ); - } + self::assertEquals('OR', $predicates[3][0] ?? ''); + self::assertInstanceOf(Literal::class, $predicates[3][1] ?? null); - public function testGetRawStateReturnsAllState(): void - { - $this->update->table('foo') - ->set(['bar' => 'baz']) - ->where('x = y'); + self::assertEquals('AND', $predicates[4][0] ?? ''); + self::assertInstanceOf(IsNull::class, $predicates[4][1] ?? null); - $rawState = $this->update->getRawState(); + self::assertEquals('AND', $predicates[5][0] ?? ''); + self::assertInstanceOf(In::class, $predicates[5][1] ?? null); - self::assertIsArray($rawState); - self::assertArrayHasKey('table', $rawState); - self::assertArrayHasKey('set', $rawState); - self::assertArrayHasKey('where', $rawState); - self::assertArrayHasKey('emptyWhereProtection', $rawState); - self::assertArrayHasKey('joins', $rawState); + self::assertEquals('AND', $predicates[6][0] ?? ''); + self::assertInstanceOf(IsNotNull::class, $predicates[6][1] ?? null); - self::assertEquals('foo', $rawState['table']); - self::assertEquals(['bar' => 'baz'], $rawState['set']); - self::assertInstanceOf(Where::class, $rawState['where']); - self::assertInstanceOf(Join::class, $rawState['joins']); - self::assertTrue($rawState['emptyWhereProtection']); - } + $where = new Where(); + $this->update->where($where); + self::assertSame($where, $this->update->where); - public function testJoinWithTableIdentifier(): void - { - $this->update->table('foo') - ->set(['x' => 'y']) - ->join(new TableIdentifier('bar', 'schema'), 'foo.id = bar.foo_id'); + $this->update->where(static function (Where $what) use ($where): void { + self::assertSame($where, $what); + }); - $sql = $this->update->getSqlString(new TrustingSql92Platform()); - self::assertStringContainsString('JOIN "schema"."bar"', $sql); + $this->expectException(TypeError::class); + /** @noinspection PhpStrictTypeCheckingInspection */ + $this->update->where(null); } #[TestDox('unit test: Test where() accepts Expression (ExpressionInterface) in array')] public function testWhereAcceptsExpressionInterface(): void { - $this->update->table('foo') + $this->update + ->table('foo') ->set(['bar' => 'baz']) ->where([ new Expression('COUNT(?) > ?', [new Identifier('id'), new Value(5)]), @@ -509,51 +561,13 @@ public function testWhereAcceptsExpressionInterface(): void self::assertEquals(1, $where->count()); } - public function testProcessSetWithPdoDriverUsesDriverColumnQuoting(): void - { - $mockDriver = $this->getMockBuilder(PdoDriverInterface::class)->getMock(); - $mockDriver->expects($this->any())->method('getPrepareType')->willReturn('positional'); - $mockDriver->expects($this->any()) - ->method('formatParameterName') - ->willReturnCallback(fn(string $name): string => ':' . $name); - $mockAdapter = $this->createMockAdapter($mockDriver); - - $mockStatement = new StatementContainer(); - - $this->update->table('foo') - ->set(['bar' => 'baz', 'boo' => 'qux']); - - $this->update->prepareStatement($mockAdapter, $mockStatement); - - $sql = $mockStatement->getSql(); - self::assertStringContainsString(':c_0', $sql); - self::assertStringContainsString(':c_1', $sql); - } - - public function testCloneDeepCopiesSetWhereAndJoins(): void + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + #[Override] + protected function setUp(): void { - $this->update->table('foo') - ->set(['bar' => 'baz']) - ->where('x = y') - ->join('other', 'foo.id = other.id'); - - $clone = clone $this->update; - - $clone->set(['bar' => 'changed']); - $clone->where('z = w'); - $clone->join('another', 'foo.id = another.id'); - - self::assertEquals(['bar' => 'baz'], $this->update->getRawState('set')); - self::assertEquals(['bar' => 'changed'], $clone->getRawState('set')); - - self::assertNotSame( - $this->update->getRawState('where'), - $clone->getRawState('where') - ); - - self::assertNotSame( - $this->update->getRawState('joins'), - $clone->getRawState('joins') - ); + $this->update = new Update(); } } diff --git a/test/unit/TableGateway/AbstractTableGatewayTest.php b/test/unit/TableGateway/AbstractTableGatewayTest.php index ad845d768..42ed03cd5 100644 --- a/test/unit/TableGateway/AbstractTableGatewayTest.php +++ b/test/unit/TableGateway/AbstractTableGatewayTest.php @@ -72,277 +72,337 @@ final class AbstractTableGatewayTest extends TestCase protected MockObject&Update $mockUpdate; protected MockObject&Delete $mockDelete; - #[Override] - protected function setUp(): void + // @codingStandardsIgnoreStart + public function test__callThrowsExceptionForInvalidMethod(): void { - $mockResult = $this->getMockBuilder(ResultInterface::class)->getMock(); - $mockResult->expects($this->any())->method('getAffectedRows')->willReturn(5); - - $mockPlatform = $this->getMockBuilder(PlatformInterface::class)->getMock(); - $mockPlatform->expects($this->any())->method('getName')->willReturn('sql92'); - $mockPlatform->expects($this->any()) - ->method('getSqlPlatformDecorator') - ->willReturn(new Sql\Platform\Platform($mockPlatform)); + // @codingStandardsIgnoreEnd + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid method (invalidMethod) called'); - $mockResultSet = $this->getMockBuilder(ResultSetInterface::class)->getMock(); + /** @phpstan-ignore method.notFound */ + $this->table->invalidMethod(); + } - $mockStatement = $this->getMockBuilder(StatementInterface::class)->getMock(); - $mockStatement->expects($this->any())->method('execute')->willReturn($mockResult); + // @codingStandardsIgnoreStart + public function test__callWithFeatureSetMagicCall(): void + { + // @codingStandardsIgnoreEnd + // Create a FeatureSet mock that returns true for canCallMagicCall + $featureSet = $this->getMockBuilder(FeatureSet::class) + ->onlyMethods(['canCallMagicCall', 'callMagicCall']) + ->getMock(); + $featureSet->expects($this->once()) + ->method('canCallMagicCall') + ->with('customMethod') + ->willReturn(true); + $featureSet->expects($this->once()) + ->method('callMagicCall') + ->with('customMethod', ['arg1', 'arg2']) + ->willReturn('customResult'); - $mockConnection = $this->getMockBuilder(ConnectionInterface::class)->getMock(); - $mockConnection->expects($this->any())->method('getLastGeneratedValue')->willReturn(10); + $tgReflection = new ReflectionClass(AbstractTableGateway::class); + $featureSetProp = $tgReflection->getProperty('featureSet'); + $featureSetProp->setValue($this->table, $featureSet); - $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); - $mockDriver->expects($this->any())->method('createStatement')->willReturn($mockStatement); - $mockDriver->expects($this->any())->method('getConnection')->willReturn($mockConnection); + /** @phpstan-ignore method.notFound */ + $result = $this->table->customMethod('arg1', 'arg2'); - $this->mockSelect = $this - ->getMockBuilder(Select::class) - ->onlyMethods(['where', 'getRawState']) - ->setConstructorArgs(['foo']) - ->getMock(); + self::assertEquals('customResult', $result); + } - $this->mockInsert = $this - ->getMockBuilder(Insert::class) - ->onlyMethods(['prepareStatement', 'values']) - ->setConstructorArgs(['foo']) - ->getMock(); + // @codingStandardsIgnoreStart + public function test__clone(): void + { + // @codingStandardsIgnoreEnd + $cTable = clone $this->table; + self::assertSame($this->mockAdapter, $cTable->getAdapter()); + } - $this->mockUpdate = $this - ->getMockBuilder(Update::class) - ->onlyMethods(['where', 'join']) - ->setConstructorArgs(['foo']) - ->getMock(); + // @codingStandardsIgnoreStart + public function test__cloneWithAliasedTableIdentifier(): void + { + // @codingStandardsIgnoreEnd + $tableIdentifier = new Sql\TableIdentifier('bar', 'schema'); + $aliasedTable = ['alias' => $tableIdentifier]; - $this->mockDelete = $this->getMockBuilder(Delete::class) - ->onlyMethods(['where']) - ->setConstructorArgs(['foo']) - ->getMock(); + $tgReflection = new ReflectionClass(AbstractTableGateway::class); + $tableProp = $tgReflection->getProperty('table'); + $tableProp->setValue($this->table, $aliasedTable); - $this->mockAdapter = $this->getMockBuilder(Adapter::class) - ->onlyMethods([]) - ->setConstructorArgs([$mockDriver, $mockPlatform, $mockResultSet]) - ->getMock(); - $this->mockSql = $this->getMockBuilder(Sql\Sql::class) - ->onlyMethods(['select', 'insert', 'update', 'delete']) - ->setConstructorArgs([$this->mockAdapter, 'foo']) - ->getMock(); - $this->mockSql->expects($this->any())->method('select')->willReturn($this->mockSelect); - $this->mockSql->expects($this->any())->method('insert')->willReturn($this->mockInsert); - $this->mockSql->expects($this->any())->method('update')->willReturn($this->mockUpdate); - $this->mockSql->expects($this->any())->method('delete')->willReturn($this->mockDelete); + $cloned = clone $this->table; - $this->mockFeatureSet = $this->getMockBuilder(FeatureSet::class)->getMock(); + $clonedTable = $cloned->getTable(); + self::assertIsArray($clonedTable); + // The TableIdentifier inside the array should be cloned + self::assertNotSame($tableIdentifier, $clonedTable['alias']); + } - $this->table = $this - ->getMockBuilder(AbstractTableGateway::class) - ->onlyMethods([]) - ->getMock(); + // @codingStandardsIgnoreStart + public function test__cloneWithTableIdentifier(): void + { + // @codingStandardsIgnoreEnd + $tableIdentifier = new Sql\TableIdentifier('bar', 'schema'); $tgReflection = new ReflectionClass(AbstractTableGateway::class); - foreach ($tgReflection->getProperties() as $tgPropReflection) { - switch ($tgPropReflection->getName()) { - case 'table': - $tgPropReflection->setValue($this->table, 'foo'); - break; - case 'adapter': - $tgPropReflection->setValue($this->table, $this->mockAdapter); - break; - case 'resultSetPrototype': - $tgPropReflection->setValue($this->table, new ResultSet()); - break; - case 'sql': - $tgPropReflection->setValue($this->table, $this->mockSql); - break; - case 'featureSet': - $tgPropReflection->setValue($this->table, $this->mockFeatureSet); - break; - } - } + $tableProp = $tgReflection->getProperty('table'); + $tableProp->setValue($this->table, $tableIdentifier); + + $cloned = clone $this->table; + + // The table should be cloned, not the same instance + self::assertNotSame($tableIdentifier, $cloned->getTable()); + self::assertEquals($tableIdentifier->getTable(), $cloned->getTable()->getTable()); } - public function testGetTable(): void + // @codingStandardsIgnoreStart + public function test__get(): void { - self::assertEquals('foo', $this->table->getTable()); + // @codingStandardsIgnoreEnd + $this->table->insert(['foo']); // trigger last insert id update + + self::assertEquals(10, $this->table->lastInsertValue); + self::assertSame($this->mockAdapter, $this->table->adapter); + + //self::assertEquals('foo', $this->table->table); } - public function testGetAdapter(): void + // @codingStandardsIgnoreStart + public function test__getAdapter(): void { - self::assertSame($this->mockAdapter, $this->table->getAdapter()); + // @codingStandardsIgnoreEnd + self::assertSame($this->mockAdapter, $this->table->adapter); } - public function testGetSql(): void + // @codingStandardsIgnoreStart + public function test__getLastInsertValue(): void { - self::assertInstanceOf(Sql\Sql::class, $this->table->getSql()); + // @codingStandardsIgnoreEnd + self::assertNull($this->table->lastInsertValue); } - public function testGetSelectResultPrototype(): void + // @codingStandardsIgnoreStart + public function test__getTable(): void { - self::assertInstanceOf(ResultSet::class, $this->table->getResultSetPrototype()); + // @codingStandardsIgnoreEnd + self::assertEquals('foo', $this->table->table); } - public function testSelectWithNoWhere(): void + // @codingStandardsIgnoreStart + public function test__getThrowsExceptionForInvalidProperty(): void { - $resultSet = $this->table->select(); + // @codingStandardsIgnoreEnd + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid magic property access'); - // check return types - self::assertInstanceOf(ResultSet::class, $resultSet); - self::assertNotSame($this->table->getResultSetPrototype(), $resultSet); + /** @phpstan-ignore expr.resultUnused, property.notFound */ + $this->table->invalidProperty; } - public function testSelectWithWhereString(): void + // @codingStandardsIgnoreStart + public function test__getWithFeatureSetMagicGet(): void { - $mockSelect = $this->mockSelect; - $mockSelect->expects($this->any()) - ->method('getRawState') - ->willReturn([ - 'table' => $this->table->getTable(), - 'columns' => [], - ]); + // @codingStandardsIgnoreEnd + // Create a custom feature that can handle magic get + $feature = new TestTableGatewayFeature(); + $feature->magicMethodSpecs = ['get' => ['customProperty']]; - // assert select::from() is called - $mockSelect->expects($this->once()) - ->method('where') - ->with($this->equalTo('foo')); + // Create a FeatureSet mock that returns true for canCallMagicGet + $featureSet = $this->getMockBuilder(FeatureSet::class) + ->onlyMethods(['canCallMagicGet', 'callMagicGet']) + ->getMock(); + $featureSet->expects($this->once()) + ->method('canCallMagicGet') + ->with('customProperty') + ->willReturn(true); + $featureSet->expects($this->once()) + ->method('callMagicGet') + ->with('customProperty') + ->willReturn('customValue'); - $this->table->select('foo'); + $tgReflection = new ReflectionClass(AbstractTableGateway::class); + $featureSetProp = $tgReflection->getProperty('featureSet'); + $featureSetProp->setValue($this->table, $featureSet); + + /** @phpstan-ignore property.notFound */ + $result = $this->table->customProperty; + + self::assertEquals('customValue', $result); } - public function testSelectWithArrayTable(): void + // @codingStandardsIgnoreStart + public function test__setThrowsExceptionForInvalidProperty(): void { - // Case 1 - $select1 = $this->getMockBuilder(Select::class)->onlyMethods(['getRawState'])->getMock(); - $select1->expects($this->once()) - ->method('getRawState') - ->willReturn([ - 'table' => 'foo', // Standard table name format, valid according to Select::from() - 'columns' => null, - ]); - $return = $this->table->selectWith($select1); - $this->assertInstanceOf(ResultSet::class, $return); + // @codingStandardsIgnoreEnd + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid magic property access'); - // Case 2 - $select1 = $this->getMockBuilder(Select::class)->onlyMethods(['getRawState'])->getMock(); - $select1->expects($this->once()) - ->method('getRawState') - ->willReturn([ - 'table' => ['f' => 'foo'], // Alias table name format, valid according to Select::from() - 'columns' => null, - ]); - $return = $this->table->selectWith($select1); - $this->assertInstanceOf(ResultSet::class, $return); + /** @phpstan-ignore property.notFound */ + $this->table->invalidProperty = 'value'; } - public function testInsert(): void + // @codingStandardsIgnoreStart + public function test__setWithFeatureSetMagicSet(): void { - $mockInsert = $this->mockInsert; - - $mockInsert->expects($this->once()) - ->method('prepareStatement') - ->with($this->mockAdapter); + // @codingStandardsIgnoreEnd + // Create a FeatureSet mock that returns true for canCallMagicSet + $featureSet = $this->getMockBuilder(FeatureSet::class) + ->onlyMethods(['canCallMagicSet', 'callMagicSet']) + ->getMock(); + $featureSet->expects($this->once()) + ->method('canCallMagicSet') + ->with('customProperty') + ->willReturn(true); + $featureSet->expects($this->once()) + ->method('callMagicSet') + ->with('customProperty', 'customValue'); - $mockInsert->expects($this->once()) - ->method('values') - ->with($this->equalTo(['foo' => 'bar'])); + $tgReflection = new ReflectionClass(AbstractTableGateway::class); + $featureSetProp = $tgReflection->getProperty('featureSet'); + $featureSetProp->setValue($this->table, $featureSet); - $affectedRows = $this->table->insert(['foo' => 'bar']); - self::assertEquals(5, $affectedRows); + /** @phpstan-ignore property.notFound */ + $this->table->customProperty = 'customValue'; } - public function testUpdate(): void + public function testDelete(): void { - $mockUpdate = $this->mockUpdate; + $mockDelete = $this->mockDelete; // assert select::from() is called - $mockUpdate->expects($this->once()) + $mockDelete->expects($this->once()) ->method('where') - ->with($this->equalTo('id = 2')); + ->with($this->equalTo('foo')); - $affectedRows = $this->table->update(['foo' => 'bar'], 'id = 2'); + $affectedRows = $this->table->delete('foo'); self::assertEquals(5, $affectedRows); } - public function testUpdateWithJoin(): void + public function testDeleteWith(): void { - $mockUpdate = $this->mockUpdate; + $delete = $this->getMockBuilder(Delete::class) + ->onlyMethods(['getRawState']) + ->setConstructorArgs(['foo']) + ->getMock(); - $joins = [ - [ - 'name' => 'baz', - 'on' => 'foo.fooId = baz.fooId', - 'type' => Sql\Join::JOIN_LEFT, - ], - ]; + $delete->expects($this->any()) + ->method('getRawState') + ->willReturn(['table' => 'foo']); - // assert select::from() is called - $mockUpdate->expects($this->once()) - ->method('where') - ->with($this->equalTo('id = 2')); + $affectedRows = $this->table->deleteWith($delete); + self::assertEquals(5, $affectedRows); + } - $mockUpdate->expects($this->once()) - ->method('join') - ->with($joins[0]['name'], $joins[0]['on'], $joins[0]['type']); + public function testDeleteWithClosure(): void + { + // The closure receives the Delete object created by $this->sql->delete() + // We verify that the closure is called with a Delete instance + $closureCalled = false; + $affectedRows = $this->table->delete(static function ($delete) use (&$closureCalled) { + $closureCalled = true; + self::assertInstanceOf(Delete::class, $delete); + }); - $affectedRows = $this->table->update(['foo.field' => 'bar'], 'id = 2', $joins); + self::assertTrue($closureCalled); self::assertEquals(5, $affectedRows); } - public function testUpdateWithJoinDefaultType(): void + public function testExecuteDeleteThrowsExceptionWhenTableDoesNotMatch(): void { - $mockUpdate = $this->mockUpdate; + $delete = new Delete('bar'); + $delete->where(['id' => 1]); - $joins = [ - [ - 'name' => 'baz', - 'on' => 'foo.fooId = baz.fooId', - ], - ]; + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('The table name of the provided Delete object must match that of the table'); - // assert select::from() is called - $mockUpdate->expects($this->once()) - ->method('where') - ->with($this->equalTo('id = 2')); + $this->table->deleteWith($delete); + } - $mockUpdate->expects($this->once()) - ->method('join') - ->with($joins[0]['name'], $joins[0]['on'], Sql\Join::JOIN_INNER); + public function testExecuteInsertThrowsExceptionWhenTableDoesNotMatch(): void + { + $insert = new Insert('bar'); + $insert->values(['name' => 'test']); - $affectedRows = $this->table->update(['foo.field' => 'bar'], 'id = 2', $joins); - self::assertEquals(5, $affectedRows); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('The table name of the provided Insert object must match that of the table'); + + $this->table->insertWith($insert); } - public function testUpdateWithNoCriteria(): void + public function testExecuteSelectThrowsExceptionWhenArrayTableDoesNotMatch(): void { - /** @phpstan-ignore expr.resultUnused */ - $this->mockUpdate; + $select = $this->getMockBuilder(Select::class) + ->onlyMethods(['getRawState']) + ->setConstructorArgs(['bar']) + ->getMock(); - $affectedRows = $this->table->update(['foo' => 'bar']); - self::assertEquals(5, $affectedRows); + // With an array table that doesn't end with 'foo', exception should be thrown + $select->expects($this->any()) + ->method('getRawState') + ->willReturn([ + 'table' => ['alias' => 'bar'], + 'columns' => [Select::SQL_STAR], + ]); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('The table name of the provided Select object must match that of the table'); + + $this->table->selectWith($select); } - public function testDelete(): void + public function testExecuteUpdateThrowsExceptionWhenTableDoesNotMatch(): void { - $mockDelete = $this->mockDelete; + $update = new Update('bar'); + $update->set(['name' => 'test']); - // assert select::from() is called - $mockDelete->expects($this->once()) - ->method('where') - ->with($this->equalTo('foo')); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('The table name of the provided Update object must match that of the table'); - $affectedRows = $this->table->delete('foo'); - self::assertEquals(5, $affectedRows); + $this->table->updateWith($update); + } + + public function testGetAdapter(): void + { + self::assertSame($this->mockAdapter, $this->table->getAdapter()); + } + + public function testGetColumns(): void + { + $tgReflection = new ReflectionClass(AbstractTableGateway::class); + $columnsProp = $tgReflection->getProperty('columns'); + $columnsProp->setValue($this->table, ['id', 'name', 'email']); + + self::assertEquals(['id', 'name', 'email'], $this->table->getColumns()); + } + + public function testGetFeatureSet(): void + { + self::assertSame($this->mockFeatureSet, $this->table->getFeatureSet()); + } + + public function testGetLastInsertValue(): void + { + $this->table->insert(['foo' => 'bar']); + self::assertEquals(10, $this->table->getLastInsertValue()); + } + + public function testGetSelectResultPrototype(): void + { + self::assertInstanceOf(ResultSet::class, $this->table->getResultSetPrototype()); + } + + public function testGetSql(): void + { + self::assertInstanceOf(Sql\Sql::class, $this->table->getSql()); } - public function testGetLastInsertValue(): void + public function testGetTable(): void { - $this->table->insert(['foo' => 'bar']); - self::assertEquals(10, $this->table->getLastInsertValue()); + self::assertEquals('foo', $this->table->getTable()); } public function testInitializeBuildsAResultSet(): void { - $stub = $this - ->getMockBuilder(AbstractTableGateway::class) + $stub = $this->getMockBuilder(AbstractTableGateway::class) ->onlyMethods([]) ->getMock(); @@ -365,48 +425,6 @@ public function testInitializeBuildsAResultSet(): void $this->assertInstanceOf(ResultSet::class, $stub->getResultSetPrototype()); } - // @codingStandardsIgnoreStart - public function test__get(): void - { - // @codingStandardsIgnoreEnd - $this->table->insert(['foo']); // trigger last insert id update - - self::assertEquals(10, $this->table->lastInsertValue); - self::assertSame($this->mockAdapter, $this->table->adapter); - //self::assertEquals('foo', $this->table->table); - } - - // @codingStandardsIgnoreStart - public function test__clone(): void - { - // @codingStandardsIgnoreEnd - $cTable = clone $this->table; - self::assertSame($this->mockAdapter, $cTable->getAdapter()); - } - - public function testIsInitialized(): void - { - // Create a fresh mock without initialization - $stub = $this->getMockBuilder(AbstractTableGateway::class) - ->onlyMethods([]) - ->getMock(); - - self::assertFalse($stub->isInitialized()); - - // Set required properties for initialization - $tgReflection = new ReflectionClass(AbstractTableGateway::class); - - $tableProp = $tgReflection->getProperty('table'); - $tableProp->setValue($stub, 'foo'); - - $adapterProp = $tgReflection->getProperty('adapter'); - $adapterProp->setValue($stub, $this->mockAdapter); - - $stub->initialize(); - - self::assertTrue($stub->isInitialized()); - } - public function testInitializeEarlyReturnWhenAlreadyInitialized(): void { // Create a fresh mock without initialization @@ -470,38 +488,20 @@ public function testInitializeThrowsExceptionWithoutTable(): void $stub->initialize(); } - public function testGetColumns(): void - { - $tgReflection = new ReflectionClass(AbstractTableGateway::class); - $columnsProp = $tgReflection->getProperty('columns'); - $columnsProp->setValue($this->table, ['id', 'name', 'email']); - - self::assertEquals(['id', 'name', 'email'], $this->table->getColumns()); - } - - public function testGetFeatureSet(): void + public function testInsert(): void { - self::assertSame($this->mockFeatureSet, $this->table->getFeatureSet()); - } + $mockInsert = $this->mockInsert; - public function testSelectWithClosure(): void - { - $mockSelect = $this->mockSelect; - $mockSelect->expects($this->any()) - ->method('getRawState') - ->willReturn([ - 'table' => $this->table->getTable(), - 'columns' => [], - ]); + $mockInsert->expects($this->once()) + ->method('prepareStatement') + ->with($this->mockAdapter); - $closureCalled = false; - $result = $this->table->select(function ($select) use (&$closureCalled) { - $closureCalled = true; - self::assertInstanceOf(Select::class, $select); - }); + $mockInsert->expects($this->once()) + ->method('values') + ->with($this->equalTo(['foo' => 'bar'])); - self::assertTrue($closureCalled); - self::assertInstanceOf(ResultSet::class, $result); + $affectedRows = $this->table->insert(['foo' => 'bar']); + self::assertEquals(5, $affectedRows); } public function testInsertWith(): void @@ -513,299 +513,295 @@ public function testInsertWith(): void self::assertEquals(5, $affectedRows); } - public function testUpdateWith(): void + public function testIsInitialized(): void { - $update = $this->getMockBuilder(Update::class) - ->onlyMethods(['getRawState']) - ->setConstructorArgs(['foo']) + // Create a fresh mock without initialization + $stub = $this->getMockBuilder(AbstractTableGateway::class) + ->onlyMethods([]) ->getMock(); - $update->expects($this->any()) - ->method('getRawState') - ->willReturn(['table' => 'foo']); - - $affectedRows = $this->table->updateWith($update); - self::assertEquals(5, $affectedRows); - } + self::assertFalse($stub->isInitialized()); - public function testDeleteWith(): void - { - $delete = $this->getMockBuilder(Delete::class) - ->onlyMethods(['getRawState']) - ->setConstructorArgs(['foo']) - ->getMock(); + // Set required properties for initialization + $tgReflection = new ReflectionClass(AbstractTableGateway::class); - $delete->expects($this->any()) - ->method('getRawState') - ->willReturn(['table' => 'foo']); + $tableProp = $tgReflection->getProperty('table'); + $tableProp->setValue($stub, 'foo'); - $affectedRows = $this->table->deleteWith($delete); - self::assertEquals(5, $affectedRows); - } + $adapterProp = $tgReflection->getProperty('adapter'); + $adapterProp->setValue($stub, $this->mockAdapter); - public function testDeleteWithClosure(): void - { - // The closure receives the Delete object created by $this->sql->delete() - // We verify that the closure is called with a Delete instance - $closureCalled = false; - $affectedRows = $this->table->delete(function ($delete) use (&$closureCalled) { - $closureCalled = true; - self::assertInstanceOf(Delete::class, $delete); - }); + $stub->initialize(); - self::assertTrue($closureCalled); - self::assertEquals(5, $affectedRows); + self::assertTrue($stub->isInitialized()); } - // @codingStandardsIgnoreStart - public function test__getTable(): void + public function testSelectAppliesColumnsWhenStarSelected(): void { - // @codingStandardsIgnoreEnd - self::assertEquals('foo', $this->table->table); - } + // Set up columns on the table + $tgReflection = new ReflectionClass(AbstractTableGateway::class); + $columnsProp = $tgReflection->getProperty('columns'); + $columnsProp->setValue($this->table, ['id', 'name', 'email']); - // @codingStandardsIgnoreStart - public function test__getThrowsExceptionForInvalidProperty(): void - { - // @codingStandardsIgnoreEnd - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid magic property access'); + $select = $this->getMockBuilder(Select::class) + ->onlyMethods(['getRawState', 'columns']) + ->setConstructorArgs(['foo']) + ->getMock(); - /** @phpstan-ignore expr.resultUnused, property.notFound */ - $this->table->invalidProperty; - } + $select->expects($this->any()) + ->method('getRawState') + ->willReturn([ + 'table' => 'foo', + 'columns' => [Select::SQL_STAR], + ]); - // @codingStandardsIgnoreStart - public function test__setThrowsExceptionForInvalidProperty(): void - { - // @codingStandardsIgnoreEnd - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid magic property access'); + $select->expects($this->once()) + ->method('columns') + ->with(['id', 'name', 'email']); - /** @phpstan-ignore property.notFound */ - $this->table->invalidProperty = 'value'; + $this->table->selectWith($select); } - // @codingStandardsIgnoreStart - public function test__callThrowsExceptionForInvalidMethod(): void + public function testSelectWithArrayTable(): void { - // @codingStandardsIgnoreEnd - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid method (invalidMethod) called'); + // Case 1 + $select1 = $this->getMockBuilder(Select::class)->onlyMethods(['getRawState'])->getMock(); + $select1->expects($this->once()) + ->method('getRawState') + ->willReturn([ + 'table' => 'foo', // Standard table name format, valid according to Select::from() + 'columns' => null, + ]); + $return = $this->table->selectWith($select1); + $this->assertInstanceOf(ResultSet::class, $return); - /** @phpstan-ignore method.notFound */ - $this->table->invalidMethod(); + // Case 2 + $select1 = $this->getMockBuilder(Select::class)->onlyMethods(['getRawState'])->getMock(); + $select1->expects($this->once()) + ->method('getRawState') + ->willReturn([ + 'table' => ['f' => 'foo'], // Alias table name format, valid according to Select::from() + 'columns' => null, + ]); + $return = $this->table->selectWith($select1); + $this->assertInstanceOf(ResultSet::class, $return); } - // @codingStandardsIgnoreStart - public function test__cloneWithTableIdentifier(): void + public function testSelectWithClosure(): void { - // @codingStandardsIgnoreEnd - $tableIdentifier = new Sql\TableIdentifier('bar', 'schema'); - - $tgReflection = new ReflectionClass(AbstractTableGateway::class); - $tableProp = $tgReflection->getProperty('table'); - $tableProp->setValue($this->table, $tableIdentifier); + $mockSelect = $this->mockSelect; + $mockSelect->expects($this->any()) + ->method('getRawState') + ->willReturn([ + 'table' => $this->table->getTable(), + 'columns' => [], + ]); - $cloned = clone $this->table; + $closureCalled = false; + $result = $this->table->select(static function ($select) use (&$closureCalled) { + $closureCalled = true; + self::assertInstanceOf(Select::class, $select); + }); - // The table should be cloned, not the same instance - self::assertNotSame($tableIdentifier, $cloned->getTable()); - self::assertEquals($tableIdentifier->getTable(), $cloned->getTable()->getTable()); + self::assertTrue($closureCalled); + self::assertInstanceOf(ResultSet::class, $result); } - // @codingStandardsIgnoreStart - public function test__cloneWithAliasedTableIdentifier(): void + public function testSelectWithNoWhere(): void { - // @codingStandardsIgnoreEnd - $tableIdentifier = new Sql\TableIdentifier('bar', 'schema'); - $aliasedTable = ['alias' => $tableIdentifier]; - - $tgReflection = new ReflectionClass(AbstractTableGateway::class); - $tableProp = $tgReflection->getProperty('table'); - $tableProp->setValue($this->table, $aliasedTable); - - $cloned = clone $this->table; - - $clonedTable = $cloned->getTable(); - self::assertIsArray($clonedTable); - // The TableIdentifier inside the array should be cloned - self::assertNotSame($tableIdentifier, $clonedTable['alias']); - } + $resultSet = $this->table->select(); - public function testExecuteSelectThrowsExceptionWhenArrayTableDoesNotMatch(): void - { - $select = $this->getMockBuilder(Select::class) - ->onlyMethods(['getRawState']) - ->setConstructorArgs(['bar']) - ->getMock(); + // check return types + self::assertInstanceOf(ResultSet::class, $resultSet); + self::assertNotSame($this->table->getResultSetPrototype(), $resultSet); + } - // With an array table that doesn't end with 'foo', exception should be thrown - $select->expects($this->any()) + public function testSelectWithWhereString(): void + { + $mockSelect = $this->mockSelect; + $mockSelect->expects($this->any()) ->method('getRawState') ->willReturn([ - 'table' => ['alias' => 'bar'], - 'columns' => [Select::SQL_STAR], + 'table' => $this->table->getTable(), + 'columns' => [], ]); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The table name of the provided Select object must match that of the table'); + // assert select::from() is called + $mockSelect->expects($this->once()) + ->method('where') + ->with($this->equalTo('foo')); - $this->table->selectWith($select); + $this->table->select('foo'); } - public function testExecuteInsertThrowsExceptionWhenTableDoesNotMatch(): void + public function testUpdate(): void { - $insert = new Insert('bar'); - $insert->values(['name' => 'test']); + $mockUpdate = $this->mockUpdate; - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The table name of the provided Insert object must match that of the table'); + // assert select::from() is called + $mockUpdate->expects($this->once()) + ->method('where') + ->with($this->equalTo('id = 2')); - $this->table->insertWith($insert); + $affectedRows = $this->table->update(['foo' => 'bar'], 'id = 2'); + self::assertEquals(5, $affectedRows); } - public function testExecuteUpdateThrowsExceptionWhenTableDoesNotMatch(): void + public function testUpdateWith(): void { - $update = new Update('bar'); - $update->set(['name' => 'test']); + $update = $this->getMockBuilder(Update::class) + ->onlyMethods(['getRawState']) + ->setConstructorArgs(['foo']) + ->getMock(); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The table name of the provided Update object must match that of the table'); + $update->expects($this->any()) + ->method('getRawState') + ->willReturn(['table' => 'foo']); - $this->table->updateWith($update); + $affectedRows = $this->table->updateWith($update); + self::assertEquals(5, $affectedRows); } - public function testExecuteDeleteThrowsExceptionWhenTableDoesNotMatch(): void + public function testUpdateWithJoin(): void { - $delete = new Delete('bar'); - $delete->where(['id' => 1]); + $mockUpdate = $this->mockUpdate; - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The table name of the provided Delete object must match that of the table'); + $joins = [ + [ + 'name' => 'baz', + 'on' => 'foo.fooId = baz.fooId', + 'type' => Sql\Join::JOIN_LEFT, + ], + ]; - $this->table->deleteWith($delete); + // assert select::from() is called + $mockUpdate->expects($this->once()) + ->method('where') + ->with($this->equalTo('id = 2')); + + $mockUpdate->expects($this->once()) + ->method('join') + ->with($joins[0]['name'], $joins[0]['on'], $joins[0]['type']); + + $affectedRows = $this->table->update(['foo.field' => 'bar'], 'id = 2', $joins); + self::assertEquals(5, $affectedRows); } - public function testSelectAppliesColumnsWhenStarSelected(): void + public function testUpdateWithJoinDefaultType(): void { - // Set up columns on the table - $tgReflection = new ReflectionClass(AbstractTableGateway::class); - $columnsProp = $tgReflection->getProperty('columns'); - $columnsProp->setValue($this->table, ['id', 'name', 'email']); + $mockUpdate = $this->mockUpdate; - $select = $this->getMockBuilder(Select::class) - ->onlyMethods(['getRawState', 'columns']) - ->setConstructorArgs(['foo']) - ->getMock(); + $joins = [ + [ + 'name' => 'baz', + 'on' => 'foo.fooId = baz.fooId', + ], + ]; - $select->expects($this->any()) - ->method('getRawState') - ->willReturn([ - 'table' => 'foo', - 'columns' => [Select::SQL_STAR], - ]); + // assert select::from() is called + $mockUpdate->expects($this->once()) + ->method('where') + ->with($this->equalTo('id = 2')); - $select->expects($this->once()) - ->method('columns') - ->with(['id', 'name', 'email']); + $mockUpdate->expects($this->once()) + ->method('join') + ->with($joins[0]['name'], $joins[0]['on'], Sql\Join::JOIN_INNER); - $this->table->selectWith($select); + $affectedRows = $this->table->update(['foo.field' => 'bar'], 'id = 2', $joins); + self::assertEquals(5, $affectedRows); } - // @codingStandardsIgnoreStart - public function test__getLastInsertValue(): void + public function testUpdateWithNoCriteria(): void { - // @codingStandardsIgnoreEnd - self::assertNull($this->table->lastInsertValue); - } + /** @phpstan-ignore expr.resultUnused */ + $this->mockUpdate; - // @codingStandardsIgnoreStart - public function test__getAdapter(): void - { - // @codingStandardsIgnoreEnd - self::assertSame($this->mockAdapter, $this->table->adapter); + $affectedRows = $this->table->update(['foo' => 'bar']); + self::assertEquals(5, $affectedRows); } - // @codingStandardsIgnoreStart - public function test__getWithFeatureSetMagicGet(): void + #[Override] + protected function setUp(): void { - // @codingStandardsIgnoreEnd - // Create a custom feature that can handle magic get - $feature = new TestTableGatewayFeature(); - $feature->magicMethodSpecs = ['get' => ['customProperty']]; + $mockResult = $this->getMockBuilder(ResultInterface::class)->getMock(); + $mockResult->expects($this->any())->method('getAffectedRows')->willReturn(5); - // Create a FeatureSet mock that returns true for canCallMagicGet - $featureSet = $this->getMockBuilder(FeatureSet::class) - ->onlyMethods(['canCallMagicGet', 'callMagicGet']) - ->getMock(); - $featureSet->expects($this->once()) - ->method('canCallMagicGet') - ->with('customProperty') - ->willReturn(true); - $featureSet->expects($this->once()) - ->method('callMagicGet') - ->with('customProperty') - ->willReturn('customValue'); + $mockPlatform = $this->getMockBuilder(PlatformInterface::class)->getMock(); + $mockPlatform->expects($this->any())->method('getName')->willReturn('sql92'); + $mockPlatform->expects($this->any()) + ->method('getSqlPlatformDecorator') + ->willReturn(new Sql\Platform\Platform($mockPlatform)); - $tgReflection = new ReflectionClass(AbstractTableGateway::class); - $featureSetProp = $tgReflection->getProperty('featureSet'); - $featureSetProp->setValue($this->table, $featureSet); + $mockResultSet = $this->getMockBuilder(ResultSetInterface::class)->getMock(); - /** @phpstan-ignore property.notFound */ - $result = $this->table->customProperty; + $mockStatement = $this->getMockBuilder(StatementInterface::class)->getMock(); + $mockStatement->expects($this->any())->method('execute')->willReturn($mockResult); - self::assertEquals('customValue', $result); - } + $mockConnection = $this->getMockBuilder(ConnectionInterface::class)->getMock(); + $mockConnection->expects($this->any())->method('getLastGeneratedValue')->willReturn(10); - // @codingStandardsIgnoreStart - public function test__setWithFeatureSetMagicSet(): void - { - // @codingStandardsIgnoreEnd - // Create a FeatureSet mock that returns true for canCallMagicSet - $featureSet = $this->getMockBuilder(FeatureSet::class) - ->onlyMethods(['canCallMagicSet', 'callMagicSet']) + $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + $mockDriver->expects($this->any())->method('createStatement')->willReturn($mockStatement); + $mockDriver->expects($this->any())->method('getConnection')->willReturn($mockConnection); + + $this->mockSelect = $this->getMockBuilder(Select::class) + ->onlyMethods(['where', 'getRawState']) + ->setConstructorArgs(['foo']) ->getMock(); - $featureSet->expects($this->once()) - ->method('canCallMagicSet') - ->with('customProperty') - ->willReturn(true); - $featureSet->expects($this->once()) - ->method('callMagicSet') - ->with('customProperty', 'customValue'); - $tgReflection = new ReflectionClass(AbstractTableGateway::class); - $featureSetProp = $tgReflection->getProperty('featureSet'); - $featureSetProp->setValue($this->table, $featureSet); + $this->mockInsert = $this->getMockBuilder(Insert::class) + ->onlyMethods(['prepareStatement', 'values']) + ->setConstructorArgs(['foo']) + ->getMock(); - /** @phpstan-ignore property.notFound */ - $this->table->customProperty = 'customValue'; - } + $this->mockUpdate = $this->getMockBuilder(Update::class) + ->onlyMethods(['where', 'join']) + ->setConstructorArgs(['foo']) + ->getMock(); - // @codingStandardsIgnoreStart - public function test__callWithFeatureSetMagicCall(): void - { - // @codingStandardsIgnoreEnd - // Create a FeatureSet mock that returns true for canCallMagicCall - $featureSet = $this->getMockBuilder(FeatureSet::class) - ->onlyMethods(['canCallMagicCall', 'callMagicCall']) + $this->mockDelete = $this->getMockBuilder(Delete::class) + ->onlyMethods(['where']) + ->setConstructorArgs(['foo']) ->getMock(); - $featureSet->expects($this->once()) - ->method('canCallMagicCall') - ->with('customMethod') - ->willReturn(true); - $featureSet->expects($this->once()) - ->method('callMagicCall') - ->with('customMethod', ['arg1', 'arg2']) - ->willReturn('customResult'); - $tgReflection = new ReflectionClass(AbstractTableGateway::class); - $featureSetProp = $tgReflection->getProperty('featureSet'); - $featureSetProp->setValue($this->table, $featureSet); + $this->mockAdapter = $this->getMockBuilder(Adapter::class) + ->onlyMethods([]) + ->setConstructorArgs([$mockDriver, $mockPlatform, $mockResultSet]) + ->getMock(); + $this->mockSql = $this->getMockBuilder(Sql\Sql::class) + ->onlyMethods(['select', 'insert', 'update', 'delete']) + ->setConstructorArgs([$this->mockAdapter, 'foo']) + ->getMock(); + $this->mockSql->expects($this->any())->method('select')->willReturn($this->mockSelect); + $this->mockSql->expects($this->any())->method('insert')->willReturn($this->mockInsert); + $this->mockSql->expects($this->any())->method('update')->willReturn($this->mockUpdate); + $this->mockSql->expects($this->any())->method('delete')->willReturn($this->mockDelete); - /** @phpstan-ignore method.notFound */ - $result = $this->table->customMethod('arg1', 'arg2'); + $this->mockFeatureSet = $this->getMockBuilder(FeatureSet::class)->getMock(); - self::assertEquals('customResult', $result); + $this->table = $this->getMockBuilder(AbstractTableGateway::class) + ->onlyMethods([]) + ->getMock(); + + $tgReflection = new ReflectionClass(AbstractTableGateway::class); + foreach ($tgReflection->getProperties() as $tgPropReflection) { + switch ($tgPropReflection->getName()) { + case 'table': + $tgPropReflection->setValue($this->table, 'foo'); + break; + case 'adapter': + $tgPropReflection->setValue($this->table, $this->mockAdapter); + break; + case 'resultSetPrototype': + $tgPropReflection->setValue($this->table, new ResultSet()); + break; + case 'sql': + $tgPropReflection->setValue($this->table, $this->mockSql); + break; + case 'featureSet': + $tgPropReflection->setValue($this->table, $this->mockFeatureSet); + break; + } + } } } diff --git a/test/unit/TableGateway/Feature/AbstractFeatureTest.php b/test/unit/TableGateway/Feature/AbstractFeatureTest.php index cd604fd13..fe896a975 100644 --- a/test/unit/TableGateway/Feature/AbstractFeatureTest.php +++ b/test/unit/TableGateway/Feature/AbstractFeatureTest.php @@ -14,12 +14,11 @@ class AbstractFeatureTest extends TestCase { private AbstractFeature&MockObject $feature; - protected function setUp(): void + public function testGetMagicMethodSpecificationsReturnsEmptyArray(): void { - $this->feature = $this->getMockBuilder(AbstractFeature::class) - ->disableOriginalConstructor() - ->onlyMethods([]) - ->getMock(); + $result = $this->feature->getMagicMethodSpecifications(); + + self::assertEmpty($result); } public function testGetNameReturnsClassName(): void @@ -29,6 +28,15 @@ public function testGetNameReturnsClassName(): void self::assertNotEmpty($name); } + public function testInitializeDoesNothing(): void + { + // initialize() is a no-op, just verify it doesn't throw + $this->feature->initialize(); + + /** @phpstan-ignore staticMethod.alreadyNarrowedType */ + self::assertTrue(true); + } + public function testSetTableGateway(): void { /** @var AbstractTableGateway&MockObject $tableGateway */ @@ -44,19 +52,11 @@ public function testSetTableGateway(): void self::assertSame($tableGateway, $value); } - public function testInitializeDoesNothing(): void - { - // initialize() is a no-op, just verify it doesn't throw - $this->feature->initialize(); - - /** @phpstan-ignore staticMethod.alreadyNarrowedType */ - self::assertTrue(true); - } - - public function testGetMagicMethodSpecificationsReturnsEmptyArray(): void + protected function setUp(): void { - $result = $this->feature->getMagicMethodSpecifications(); - - self::assertEmpty($result); + $this->feature = $this->getMockBuilder(AbstractFeature::class) + ->disableOriginalConstructor() + ->onlyMethods([]) + ->getMock(); } } diff --git a/test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php b/test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php index 0c0fd556d..62d93496c 100644 --- a/test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php +++ b/test/unit/TableGateway/Feature/EventFeature/TableGatewayEventTest.php @@ -14,9 +14,23 @@ class TableGatewayEventTest extends TestCase { private TableGatewayEvent $event; - protected function setUp(): void + public function testGetParamWithDefault(): void { - $this->event = new TableGatewayEvent(); + $result = $this->event->getParam('nonExistent', 'defaultValue'); + + self::assertEquals('defaultValue', $result); + } + + public function testPropagationIsStoppedAlwaysReturnsFalse(): void + { + /** @phpstan-ignore staticMethod.impossibleType */ + self::assertFalse($this->event->propagationIsStopped()); + + $this->event->stopPropagation(true); + + // Still returns false as per implementation + /** @phpstan-ignore staticMethod.impossibleType */ + self::assertFalse($this->event->propagationIsStopped()); } public function testSetNameAndGetName(): void @@ -28,16 +42,14 @@ public function testSetNameAndGetName(): void self::assertEquals('test.event', $this->event->getName()); } - public function testSetTargetAndGetTarget(): void + public function testSetParamAndGetParam(): void { - /** @var AbstractTableGateway&MockObject $tableGateway */ - $tableGateway = $this->getMockBuilder(AbstractTableGateway::class) - ->disableOriginalConstructor() - ->getMock(); + self::assertNull($this->event->getParam('unknown')); + self::assertEquals('default', $this->event->getParam('unknown', 'default')); - $this->event->setTarget($tableGateway); + $this->event->setParam('myParam', 'myValue'); - self::assertSame($tableGateway, $this->event->getTarget()); + self::assertEquals('myValue', $this->event->getParam('myParam')); } public function testSetParamsAndGetParams(): void @@ -60,21 +72,16 @@ public function testSetParamsWithObject(): void self::assertSame($params, $this->event->getParams()); } - public function testSetParamAndGetParam(): void + public function testSetTargetAndGetTarget(): void { - self::assertNull($this->event->getParam('unknown')); - self::assertEquals('default', $this->event->getParam('unknown', 'default')); - - $this->event->setParam('myParam', 'myValue'); - - self::assertEquals('myValue', $this->event->getParam('myParam')); - } + /** @var AbstractTableGateway&MockObject $tableGateway */ + $tableGateway = $this->getMockBuilder(AbstractTableGateway::class) + ->disableOriginalConstructor() + ->getMock(); - public function testGetParamWithDefault(): void - { - $result = $this->event->getParam('nonExistent', 'defaultValue'); + $this->event->setTarget($tableGateway); - self::assertEquals('defaultValue', $result); + self::assertSame($tableGateway, $this->event->getTarget()); } public function testStopPropagation(): void @@ -87,15 +94,8 @@ public function testStopPropagation(): void self::assertFalse($this->event->propagationIsStopped()); } - public function testPropagationIsStoppedAlwaysReturnsFalse(): void + protected function setUp(): void { - /** @phpstan-ignore staticMethod.impossibleType */ - self::assertFalse($this->event->propagationIsStopped()); - - $this->event->stopPropagation(true); - - // Still returns false as per implementation - /** @phpstan-ignore staticMethod.impossibleType */ - self::assertFalse($this->event->propagationIsStopped()); + $this->event = new TableGatewayEvent(); } } diff --git a/test/unit/TableGateway/Feature/EventFeatureTest.php b/test/unit/TableGateway/Feature/EventFeatureTest.php index da86cf083..d47ded98a 100644 --- a/test/unit/TableGateway/Feature/EventFeatureTest.php +++ b/test/unit/TableGateway/Feature/EventFeatureTest.php @@ -32,53 +32,47 @@ final class EventFeatureTest extends TestCase protected TableGateway&MockObject $tableGateway; - /** - * @throws Exception - */ - #[Override] - protected function setUp(): void + public function testConstructorWithDefaults(): void { - $this->eventManager = new EventManager(); - $this->event = new EventFeature\TableGatewayEvent(); - $this->feature = new EventFeature($this->eventManager, $this->event); - $this->tableGateway = $this->getMockBuilder(TableGateway::class) - ->disableOriginalConstructor() - ->onlyMethods([]) - ->getMock(); - $this->feature->setTableGateway($this->tableGateway); + $feature = new EventFeature(); - // typically runs before everything else - $this->feature->preInitialize(); + self::assertInstanceOf(EventManagerInterface::class, $feature->getEventManager()); + self::assertInstanceOf(EventFeature\TableGatewayEvent::class, $feature->getEvent()); } - public function testGetEventManager(): void + public function testGetEvent(): void { - self::assertSame($this->eventManager, $this->feature->getEventManager()); + self::assertSame($this->event, $this->feature->getEvent()); } - public function testGetEvent(): void + public function testGetEventManager(): void { - self::assertSame($this->event, $this->feature->getEvent()); + self::assertSame($this->eventManager, $this->feature->getEventManager()); } - public function testPreInitialize(): void + public function testPostDelete(): void { $closureHasRun = false; /** @var EventFeature\TableGatewayEvent $event */ $event = new EventFeature\TableGatewayEvent(); $this->eventManager->attach( - EventFeatureEventsInterface::EVENT_PRE_INITIALIZE, - function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { + EventFeatureEventsInterface::EVENT_POST_DELETE, + static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { $event = $e; $closureHasRun = true; - } + }, ); - $this->feature->preInitialize(); + $this->feature->postDelete( + $stmt = $this->getMockBuilder(StatementInterface::class)->getMock(), + $result = $this->getMockBuilder(ResultInterface::class)->getMock(), + ); self::assertTrue($closureHasRun); self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_PRE_INITIALIZE, $event->getName()); + self::assertEquals(EventFeatureEventsInterface::EVENT_POST_DELETE, $event->getName()); + self::assertSame($stmt, $event->getParam('statement')); + self::assertSame($result, $event->getParam('result')); } public function testPostInitialize(): void @@ -89,10 +83,10 @@ public function testPostInitialize(): void $event = new EventFeature\TableGatewayEvent(); $this->eventManager->attach( EventFeatureEventsInterface::EVENT_POST_INITIALIZE, - function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { + static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { $event = $e; $closureHasRun = true; - } + }, ); $this->feature->postInitialize(); @@ -101,25 +95,29 @@ function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): voi self::assertEquals(EventFeatureEventsInterface::EVENT_POST_INITIALIZE, $event->getName()); } - public function testPreSelect(): void + public function testPostInsert(): void { $closureHasRun = false; /** @var EventFeature\TableGatewayEvent $event */ $event = new EventFeature\TableGatewayEvent(); $this->eventManager->attach( - EventFeatureEventsInterface::EVENT_PRE_SELECT, - function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { + EventFeatureEventsInterface::EVENT_POST_INSERT, + static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { $event = $e; $closureHasRun = true; - } + }, ); - $this->feature->preSelect($select = $this->getMockBuilder(Select::class)->getMock()); + $this->feature->postInsert( + $stmt = $this->getMockBuilder(StatementInterface::class)->getMock(), + $result = $this->getMockBuilder(ResultInterface::class)->getMock(), + ); self::assertTrue($closureHasRun); self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_PRE_SELECT, $event->getName()); - self::assertSame($select, $event->getParam('select')); + self::assertEquals(EventFeatureEventsInterface::EVENT_POST_INSERT, $event->getName()); + self::assertSame($stmt, $event->getParam('statement')); + self::assertSame($result, $event->getParam('result')); } public function testPostSelect(): void @@ -130,16 +128,16 @@ public function testPostSelect(): void $event = new EventFeature\TableGatewayEvent(); $this->eventManager->attach( EventFeatureEventsInterface::EVENT_POST_SELECT, - function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { + static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { $event = $e; $closureHasRun = true; - } + }, ); $this->feature->postSelect( - $stmt = $this->getMockBuilder(StatementInterface::class)->getMock(), - $result = $this->getMockBuilder(ResultInterface::class)->getMock(), - $resultset = $this->getMockBuilder(ResultSet::class)->getMock() + $stmt = $this->getMockBuilder(StatementInterface::class)->getMock(), + $result = $this->getMockBuilder(ResultInterface::class)->getMock(), + $resultset = $this->getMockBuilder(ResultSet::class)->getMock(), ); self::assertTrue($closureHasRun); self::assertInstanceOf(TableGateway::class, $event->getTarget()); @@ -149,172 +147,174 @@ function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): voi self::assertSame($resultset, $event->getParam('result_set')); } - public function testPreInsert(): void + public function testPostUpdate(): void { $closureHasRun = false; /** @var EventFeature\TableGatewayEvent $event */ $event = new EventFeature\TableGatewayEvent(); $this->eventManager->attach( - EventFeatureEventsInterface::EVENT_PRE_INSERT, - function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { + EventFeatureEventsInterface::EVENT_POST_UPDATE, + static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { $event = $e; $closureHasRun = true; - } + }, ); - $this->feature->preInsert($insert = $this->getMockBuilder(Insert::class)->getMock()); + $this->feature->postUpdate( + $stmt = $this->getMockBuilder(StatementInterface::class)->getMock(), + $result = $this->getMockBuilder(ResultInterface::class)->getMock(), + ); self::assertTrue($closureHasRun); self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_PRE_INSERT, $event->getName()); - self::assertSame($insert, $event->getParam('insert')); + self::assertEquals(EventFeatureEventsInterface::EVENT_POST_UPDATE, $event->getName()); + self::assertSame($stmt, $event->getParam('statement')); + self::assertSame($result, $event->getParam('result')); } - public function testPostInsert(): void + public function testPreDelete(): void { $closureHasRun = false; /** @var EventFeature\TableGatewayEvent $event */ $event = new EventFeature\TableGatewayEvent(); $this->eventManager->attach( - EventFeatureEventsInterface::EVENT_POST_INSERT, - function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { + EventFeatureEventsInterface::EVENT_PRE_DELETE, + static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { $event = $e; $closureHasRun = true; - } + }, ); - $this->feature->postInsert( - $stmt = $this->getMockBuilder(StatementInterface::class)->getMock(), - $result = $this->getMockBuilder(ResultInterface::class)->getMock() - ); + $this->feature->preDelete($delete = $this->getMockBuilder(Delete::class)->getMock()); self::assertTrue($closureHasRun); self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_POST_INSERT, $event->getName()); - self::assertSame($stmt, $event->getParam('statement')); - self::assertSame($result, $event->getParam('result')); + self::assertEquals(EventFeatureEventsInterface::EVENT_PRE_DELETE, $event->getName()); + self::assertSame($delete, $event->getParam('delete')); } - public function testPreUpdate(): void + public function testPreInitialize(): void { $closureHasRun = false; /** @var EventFeature\TableGatewayEvent $event */ $event = new EventFeature\TableGatewayEvent(); $this->eventManager->attach( - EventFeatureEventsInterface::EVENT_PRE_UPDATE, - function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { + EventFeatureEventsInterface::EVENT_PRE_INITIALIZE, + static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { $event = $e; $closureHasRun = true; - } + }, ); - $this->feature->preUpdate($update = $this->getMockBuilder(Update::class)->getMock()); + $this->feature->preInitialize(); self::assertTrue($closureHasRun); self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_PRE_UPDATE, $event->getName()); - self::assertSame($update, $event->getParam('update')); + self::assertEquals(EventFeatureEventsInterface::EVENT_PRE_INITIALIZE, $event->getName()); } - public function testPostUpdate(): void + /** + * @throws Exception + */ + public function testPreInitializeAddsIdentifiersForCustomTableGatewayClass(): void + { + // Create a custom subclass of TableGateway (using anonymous class) + $customTableGateway = new TestTableGateway(); + + $eventManager = new EventManager(); + $feature = new EventFeature($eventManager); + $feature->setTableGateway($customTableGateway); + + // The custom class name should be added as an identifier + $feature->preInitialize(); + + // Get the identifiers from the event manager + $identifiers = $eventManager->getIdentifiers(); + + // Should contain both TableGateway::class and the anonymous class name + self::assertContains(TableGateway::class, $identifiers); + self::assertContains($customTableGateway::class, $identifiers); + } + + public function testPreInsert(): void { $closureHasRun = false; /** @var EventFeature\TableGatewayEvent $event */ $event = new EventFeature\TableGatewayEvent(); $this->eventManager->attach( - EventFeatureEventsInterface::EVENT_POST_UPDATE, - function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { + EventFeatureEventsInterface::EVENT_PRE_INSERT, + static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { $event = $e; $closureHasRun = true; - } + }, ); - $this->feature->postUpdate( - $stmt = $this->getMockBuilder(StatementInterface::class)->getMock(), - $result = $this->getMockBuilder(ResultInterface::class)->getMock() - ); + $this->feature->preInsert($insert = $this->getMockBuilder(Insert::class)->getMock()); self::assertTrue($closureHasRun); self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_POST_UPDATE, $event->getName()); - self::assertSame($stmt, $event->getParam('statement')); - self::assertSame($result, $event->getParam('result')); + self::assertEquals(EventFeatureEventsInterface::EVENT_PRE_INSERT, $event->getName()); + self::assertSame($insert, $event->getParam('insert')); } - public function testPreDelete(): void + public function testPreSelect(): void { $closureHasRun = false; /** @var EventFeature\TableGatewayEvent $event */ $event = new EventFeature\TableGatewayEvent(); $this->eventManager->attach( - EventFeatureEventsInterface::EVENT_PRE_DELETE, - function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { + EventFeatureEventsInterface::EVENT_PRE_SELECT, + static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { $event = $e; $closureHasRun = true; - } + }, ); - $this->feature->preDelete($delete = $this->getMockBuilder(Delete::class)->getMock()); + $this->feature->preSelect($select = $this->getMockBuilder(Select::class)->getMock()); self::assertTrue($closureHasRun); self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_PRE_DELETE, $event->getName()); - self::assertSame($delete, $event->getParam('delete')); + self::assertEquals(EventFeatureEventsInterface::EVENT_PRE_SELECT, $event->getName()); + self::assertSame($select, $event->getParam('select')); } - public function testPostDelete(): void + public function testPreUpdate(): void { $closureHasRun = false; /** @var EventFeature\TableGatewayEvent $event */ $event = new EventFeature\TableGatewayEvent(); $this->eventManager->attach( - EventFeatureEventsInterface::EVENT_POST_DELETE, - function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { + EventFeatureEventsInterface::EVENT_PRE_UPDATE, + static function (EventFeature\TableGatewayEvent $e) use (&$closureHasRun, &$event): void { $event = $e; $closureHasRun = true; - } + }, ); - $this->feature->postDelete( - $stmt = $this->getMockBuilder(StatementInterface::class)->getMock(), - $result = $this->getMockBuilder(ResultInterface::class)->getMock() - ); + $this->feature->preUpdate($update = $this->getMockBuilder(Update::class)->getMock()); self::assertTrue($closureHasRun); self::assertInstanceOf(TableGateway::class, $event->getTarget()); - self::assertEquals(EventFeatureEventsInterface::EVENT_POST_DELETE, $event->getName()); - self::assertSame($stmt, $event->getParam('statement')); - self::assertSame($result, $event->getParam('result')); - } - - public function testConstructorWithDefaults(): void - { - $feature = new EventFeature(); - - self::assertInstanceOf(EventManagerInterface::class, $feature->getEventManager()); - self::assertInstanceOf(EventFeature\TableGatewayEvent::class, $feature->getEvent()); + self::assertEquals(EventFeatureEventsInterface::EVENT_PRE_UPDATE, $event->getName()); + self::assertSame($update, $event->getParam('update')); } /** * @throws Exception */ - public function testPreInitializeAddsIdentifiersForCustomTableGatewayClass(): void + #[Override] + protected function setUp(): void { - // Create a custom subclass of TableGateway (using anonymous class) - $customTableGateway = new TestTableGateway(); - - $eventManager = new EventManager(); - $feature = new EventFeature($eventManager); - $feature->setTableGateway($customTableGateway); - - // The custom class name should be added as an identifier - $feature->preInitialize(); - - // Get the identifiers from the event manager - $identifiers = $eventManager->getIdentifiers(); + $this->eventManager = new EventManager(); + $this->event = new EventFeature\TableGatewayEvent(); + $this->feature = new EventFeature($this->eventManager, $this->event); + $this->tableGateway = $this->getMockBuilder(TableGateway::class) + ->disableOriginalConstructor() + ->onlyMethods([]) + ->getMock(); + $this->feature->setTableGateway($this->tableGateway); - // Should contain both TableGateway::class and the anonymous class name - self::assertContains(TableGateway::class, $identifiers); - self::assertContains($customTableGateway::class, $identifiers); + // typically runs before everything else + $this->feature->preInitialize(); } } diff --git a/test/unit/TableGateway/Feature/FeatureSetTest.php b/test/unit/TableGateway/Feature/FeatureSetTest.php index 95665c70c..ac2fd43a6 100644 --- a/test/unit/TableGateway/Feature/FeatureSetTest.php +++ b/test/unit/TableGateway/Feature/FeatureSetTest.php @@ -39,6 +39,17 @@ #[CoversMethod(FeatureSet::class, 'callMagicCall')] class FeatureSetTest extends TestCase { + public function testAddFeaturesReturnsFluentInterface(): void + { + $feature1 = new SequenceFeature('id', 'seq1'); + $feature2 = new SequenceFeature('id', 'seq2'); + + $featureSet = new FeatureSet(); + $result = $featureSet->addFeatures([$feature1, $feature2]); + + self::assertSame($featureSet, $result); + } + /** * @cover FeatureSet::addFeature * @throws Exception @@ -97,190 +108,179 @@ public function testAddFeatureThatFeatureHasTableGatewayButFeatureSetDoesNotHave self::assertInstanceOf(FeatureSet::class, $featureSet->addFeature($feature)); } - public function testCanCallMagicCallReturnsTrueForAddedMethodOfAddedFeature(): void + public function testApplyCallsAllFeaturesWhenNoHalt(): void { - $feature = new SequenceFeature('id', 'table_sequence'); - $featureSet = new FeatureSet(); - $featureSet->addFeature($feature); + $feature1 = new TestTableGatewayFeature(); + $feature2 = new TestTableGatewayFeature(); - self::assertTrue( - $featureSet->canCallMagicCall('lastSequenceId'), - 'Should have been able to call lastSequenceId from the Sequence Feature' - ); + $featureSet = new FeatureSet([$feature1, $feature2]); + $featureSet->apply('testMethod', []); + + self::assertTrue($feature1->called); + self::assertTrue($feature2->called); } - public function testCanCallMagicCallReturnsFalseForAddedMethodOfAddedFeature(): void + public function testApplyCallsMethodOnFeatures(): void { - $feature = new SequenceFeature('id', 'table_sequence'); - $featureSet = new FeatureSet(); - $featureSet->addFeature($feature); + $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class) + ->disableOriginalConstructor() + ->getMock(); - self::assertFalse( - $featureSet->canCallMagicCall('postInitialize'), - 'Should have been able to call postInitialize from the MetaData Feature' + $feature = new MasterSlaveFeature( + $this->getMockBuilder(AdapterInterface::class)->getMock(), ); - } - public function testCanCallMagicCallReturnsFalseWhenNoFeaturesHaveBeenAdded(): void - { - $featureSet = new FeatureSet(); - self::assertFalse( - $featureSet->canCallMagicCall('lastSequenceId') - ); + $featureSet = new FeatureSet([$feature]); + $featureSet->setTableGateway($tableGatewayMock); + + $featureSet->apply('preSelect', []); + + /** @phpstan-ignore staticMethod.alreadyNarrowedType */ + self::assertTrue(true); } - public function testCallMagicCallSucceedsForValidMethodOfAddedFeature(): void + public function testApplyHaltsWhenFeatureReturnsHalt(): void { - $feature = new TestTableGatewayFeature(); + $feature1 = new TestTableGatewayFeature(); + $feature1->returnValue = FeatureSet::APPLY_HALT; - $featureSet = new FeatureSet(); - $featureSet->addFeature($feature); + $feature2 = new TestTableGatewayFeature(); - $result = $featureSet->callMagicCall('customMethod', ['test_value']); + $featureSet = new FeatureSet([$feature1, $feature2]); + $featureSet->apply('testMethod', []); - self::assertEquals('result: test_value', $result); + self::assertTrue($feature1->called); + self::assertFalse($feature2->called); } - public function testConstructorWithFeatures(): void + public function testApplyPassesArgumentsToFeatures(): void { - $feature = new SequenceFeature('id', 'table_sequence'); + $feature = new TestTableGatewayFeature(); + $featureSet = new FeatureSet([$feature]); + $featureSet->apply('testMethod', ['test value']); - self::assertSame($feature, $featureSet->getFeatureByClassName(SequenceFeature::class)); + self::assertEquals(['test value'], $feature->receivedArgs); } - public function testSetTableGateway(): void + public function testApplySkipsFeatureWithoutMethod(): void { - $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class) - ->disableOriginalConstructor() - ->getMock(); - $feature = new SequenceFeature('id', 'table_sequence'); $featureSet = new FeatureSet([$feature]); - $result = $featureSet->setTableGateway($tableGatewayMock); + $featureSet->apply('nonExistentMethod', []); - self::assertSame($featureSet, $result); + /** @phpstan-ignore staticMethod.alreadyNarrowedType */ + self::assertTrue(true); } - public function testGetFeatureByClassNameReturnsNullWhenNotFound(): void + public function testCallMagicCallReturnsNullWhenNoFeatureHasMethod(): void { $featureSet = new FeatureSet(); - $result = $featureSet->getFeatureByClassName(SequenceFeature::class); - - self::assertNull($result); + self::assertNull($featureSet->callMagicCall('nonExistentMethod', [])); } - public function testAddFeaturesReturnsFluentInterface(): void + public function testCallMagicCallSucceedsForValidMethodOfAddedFeature(): void { - $feature1 = new SequenceFeature('id', 'seq1'); - $feature2 = new SequenceFeature('id', 'seq2'); + $feature = new TestTableGatewayFeature(); $featureSet = new FeatureSet(); - $result = $featureSet->addFeatures([$feature1, $feature2]); + $featureSet->addFeature($feature); - self::assertSame($featureSet, $result); + $result = $featureSet->callMagicCall('customMethod', ['test_value']); + + self::assertEquals('result: test_value', $result); } - public function testApplyCallsMethodOnFeatures(): void + public function testCallMagicGetReturnsNull(): void { - $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class) - ->disableOriginalConstructor() - ->getMock(); - - $feature = new MasterSlaveFeature( - $this->getMockBuilder(AdapterInterface::class)->getMock() - ); - - $featureSet = new FeatureSet([$feature]); - $featureSet->setTableGateway($tableGatewayMock); - - $featureSet->apply('preSelect', []); + $featureSet = new FeatureSet(); - /** @phpstan-ignore staticMethod.alreadyNarrowedType */ - self::assertTrue(true); + self::assertNull($featureSet->callMagicGet('property')); } - public function testApplySkipsFeatureWithoutMethod(): void + public function testCallMagicSetReturnsNull(): void { - $feature = new SequenceFeature('id', 'table_sequence'); - $featureSet = new FeatureSet([$feature]); - - $featureSet->apply('nonExistentMethod', []); + $featureSet = new FeatureSet(); - /** @phpstan-ignore staticMethod.alreadyNarrowedType */ - self::assertTrue(true); + self::assertNull($featureSet->callMagicSet('property', 'value')); } - public function testCanCallMagicGetReturnsFalse(): void + public function testCanCallMagicCallReturnsFalseForAddedMethodOfAddedFeature(): void { + $feature = new SequenceFeature('id', 'table_sequence'); $featureSet = new FeatureSet(); + $featureSet->addFeature($feature); - self::assertFalse($featureSet->canCallMagicGet('property')); + self::assertFalse( + $featureSet->canCallMagicCall('postInitialize'), + 'Should have been able to call postInitialize from the MetaData Feature', + ); } - public function testCallMagicGetReturnsNull(): void + public function testCanCallMagicCallReturnsFalseWhenNoFeaturesHaveBeenAdded(): void { $featureSet = new FeatureSet(); - - self::assertNull($featureSet->callMagicGet('property')); + self::assertFalse( + $featureSet->canCallMagicCall('lastSequenceId'), + ); } - public function testCanCallMagicSetReturnsFalse(): void + public function testCanCallMagicCallReturnsTrueForAddedMethodOfAddedFeature(): void { + $feature = new SequenceFeature('id', 'table_sequence'); $featureSet = new FeatureSet(); + $featureSet->addFeature($feature); - self::assertFalse($featureSet->canCallMagicSet('property')); + self::assertTrue( + $featureSet->canCallMagicCall('lastSequenceId'), + 'Should have been able to call lastSequenceId from the Sequence Feature', + ); } - public function testCallMagicSetReturnsNull(): void + public function testCanCallMagicGetReturnsFalse(): void { $featureSet = new FeatureSet(); - self::assertNull($featureSet->callMagicSet('property', 'value')); + self::assertFalse($featureSet->canCallMagicGet('property')); } - public function testCallMagicCallReturnsNullWhenNoFeatureHasMethod(): void + public function testCanCallMagicSetReturnsFalse(): void { $featureSet = new FeatureSet(); - self::assertNull($featureSet->callMagicCall('nonExistentMethod', [])); + self::assertFalse($featureSet->canCallMagicSet('property')); } - public function testApplyHaltsWhenFeatureReturnsHalt(): void + public function testConstructorWithFeatures(): void { - $feature1 = new TestTableGatewayFeature(); - $feature1->returnValue = FeatureSet::APPLY_HALT; - - $feature2 = new TestTableGatewayFeature(); - - $featureSet = new FeatureSet([$feature1, $feature2]); - $featureSet->apply('testMethod', []); + $feature = new SequenceFeature('id', 'table_sequence'); + $featureSet = new FeatureSet([$feature]); - self::assertTrue($feature1->called); - self::assertFalse($feature2->called); + self::assertSame($feature, $featureSet->getFeatureByClassName(SequenceFeature::class)); } - public function testApplyCallsAllFeaturesWhenNoHalt(): void + public function testGetFeatureByClassNameReturnsNullWhenNotFound(): void { - $feature1 = new TestTableGatewayFeature(); - $feature2 = new TestTableGatewayFeature(); + $featureSet = new FeatureSet(); - $featureSet = new FeatureSet([$feature1, $feature2]); - $featureSet->apply('testMethod', []); + $result = $featureSet->getFeatureByClassName(SequenceFeature::class); - self::assertTrue($feature1->called); - self::assertTrue($feature2->called); + self::assertNull($result); } - public function testApplyPassesArgumentsToFeatures(): void + public function testSetTableGateway(): void { - $feature = new TestTableGatewayFeature(); + $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class) + ->disableOriginalConstructor() + ->getMock(); + $feature = new SequenceFeature('id', 'table_sequence'); $featureSet = new FeatureSet([$feature]); - $featureSet->apply('testMethod', ['test value']); - self::assertEquals(['test value'], $feature->receivedArgs); + $result = $featureSet->setTableGateway($tableGatewayMock); + + self::assertSame($featureSet, $result); } } diff --git a/test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php b/test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php index fb576cb82..de9b53067 100644 --- a/test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php +++ b/test/unit/TableGateway/Feature/GlobalAdapterFeatureTest.php @@ -15,27 +15,16 @@ class GlobalAdapterFeatureTest extends TestCase { - protected function setUp(): void - { - // Reset the static adapters before each test - $reflection = new ReflectionProperty(GlobalAdapterFeature::class, 'staticAdapters'); - $reflection->setValue(null, []); - } - - protected function tearDown(): void - { - // Clean up static adapters after each test - $reflection = new ReflectionProperty(GlobalAdapterFeature::class, 'staticAdapters'); - $reflection->setValue(null, []); - } - - public function testSetStaticAdapter(): void + public function testGetStaticAdapterReturnsDefaultAdapterWhenClassSpecificNotSet(): void { $adapter = $this->createMock(AdapterInterface::class); + // Set adapter on the base class GlobalAdapterFeature::setStaticAdapter($adapter); + // Get adapter should return the default adapter $result = GlobalAdapterFeature::getStaticAdapter(); + self::assertSame($adapter, $result); } @@ -69,16 +58,13 @@ public function testPreInitializeSetsAdapterOnTableGateway(): void self::assertSame($adapter, $result); } - public function testGetStaticAdapterReturnsDefaultAdapterWhenClassSpecificNotSet(): void + public function testSetStaticAdapter(): void { $adapter = $this->createMock(AdapterInterface::class); - // Set adapter on the base class GlobalAdapterFeature::setStaticAdapter($adapter); - // Get adapter should return the default adapter $result = GlobalAdapterFeature::getStaticAdapter(); - self::assertSame($adapter, $result); } @@ -120,4 +106,18 @@ public function testSubclassThrowsExceptionWhenNoAdaptersSet(): void TestGlobalAdapterFeatureSubclass::getStaticAdapter(); } + + protected function setUp(): void + { + // Reset the static adapters before each test + $reflection = new ReflectionProperty(GlobalAdapterFeature::class, 'staticAdapters'); + $reflection->setValue(null, []); + } + + protected function tearDown(): void + { + // Clean up static adapters after each test + $reflection = new ReflectionProperty(GlobalAdapterFeature::class, 'staticAdapters'); + $reflection->setValue(null, []); + } } diff --git a/test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php b/test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php index 7fed1e33a..efc845a9c 100644 --- a/test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php +++ b/test/unit/TableGateway/Feature/MasterSlaveFeatureTest.php @@ -25,24 +25,20 @@ final class MasterSlaveFeatureTest extends TestCase protected MasterSlaveFeature $feature; protected TableGateway&MockObject $table; - #[Override] - protected function setUp(): void + /** + * @throws Exception + */ + public function testConstructorWithSlaveSql(): void { - $this->mockMasterAdapter = $this->getMockBuilder(AdapterInterface::class)->onlyMethods([])->getMock(); - $this->mockSlaveAdapter = $this->getMockBuilder(AdapterInterface::class)->onlyMethods([])->getMock(); - $this->mockStatement = $this->getMockBuilder(StatementInterface::class)->onlyMethods([])->getMock(); - - $mockDriver = $this->getMockBuilder(DriverInterface::class)->onlyMethods([])->getMock(); - $mockDriver->expects($this->any())->method('createStatement')->willReturn(clone $this->mockStatement); - $this->mockMasterAdapter->expects($this->any())->method('getDriver')->willReturn($mockDriver); - $this->mockMasterAdapter->expects($this->any())->method('getPlatform')->willReturn(new Sql92()); + $slaveSql = new Sql($this->mockSlaveAdapter, 'foo'); + $feature = new MasterSlaveFeature($this->mockSlaveAdapter, $slaveSql); - $mockDriver = $this->getMockBuilder(DriverInterface::class)->onlyMethods([])->getMock(); - $mockDriver->expects($this->any())->method('createStatement')->willReturn(clone $this->mockStatement); - $this->mockSlaveAdapter->expects($this->any())->method('getDriver')->willReturn($mockDriver); - $this->mockSlaveAdapter->expects($this->any())->method('getPlatform')->willReturn(new Sql92()); + self::assertSame($slaveSql, $feature->getSlaveSql()); + } - $this->feature = new MasterSlaveFeature($this->mockSlaveAdapter); + public function testGetSlaveAdapter(): void + { + self::assertSame($this->mockSlaveAdapter, $this->feature->getSlaveAdapter()); } /** @@ -61,26 +57,18 @@ public function testPostInitialize(): void /** * @throws Exception */ - public function testPreSelect(): void + public function testPostInitializeWithProvidedSlaveSql(): void { - $this->expectNotToPerformAssertions(); - - $table = $this - ->getMockBuilder(TableGateway::class) - ->setConstructorArgs(['foo', $this->mockMasterAdapter, $this->feature]) - ->onlyMethods([])->getMock(); + $slaveSql = new Sql($this->mockSlaveAdapter, 'foo'); + $feature = new MasterSlaveFeature($this->mockSlaveAdapter, $slaveSql); - /** @var MockObject&StatementInterface $stmt */ - $stmt = $this - ->mockSlaveAdapter - ->getDriver() - ->createStatement(); + $this->getMockBuilder(TableGateway::class) + ->setConstructorArgs(['foo', $this->mockMasterAdapter, $feature]) + ->onlyMethods([]) + ->getMock(); - $stmt - ->expects($this->once()) - ->method('execute') - ->willReturn($this->getMockBuilder(ResultInterface::class)->onlyMethods([])->getMock()); - $table->select('foo = bar'); + // The provided slaveSql should be used instead of creating a new one + self::assertSame($slaveSql, $feature->getSlaveSql()); } /** @@ -94,18 +82,16 @@ public function testPostSelect(): void ->getMock(); /** @var MockObject&StatementInterface $stmt */ - $stmt = $this - ->mockSlaveAdapter + $stmt = $this->mockSlaveAdapter ->getDriver() ->createStatement(); - $stmt - ->expects($this->once()) + $stmt->expects($this->once()) ->method('execute') ->willReturn( $this->getMockBuilder(ResultInterface::class) ->onlyMethods([]) - ->getMock() + ->getMock(), ); $masterSql = $table->getSql(); @@ -115,36 +101,50 @@ public function testPostSelect(): void self::assertSame($masterSql, $table->getSql()); } - public function testGetSlaveAdapter(): void - { - self::assertSame($this->mockSlaveAdapter, $this->feature->getSlaveAdapter()); - } - /** * @throws Exception */ - public function testConstructorWithSlaveSql(): void + public function testPreSelect(): void { - $slaveSql = new Sql($this->mockSlaveAdapter, 'foo'); - $feature = new MasterSlaveFeature($this->mockSlaveAdapter, $slaveSql); + $this->expectNotToPerformAssertions(); - self::assertSame($slaveSql, $feature->getSlaveSql()); + $table = $this->getMockBuilder(TableGateway::class) + ->setConstructorArgs(['foo', $this->mockMasterAdapter, $this->feature]) + ->onlyMethods([]) + ->getMock(); + + /** @var MockObject&StatementInterface $stmt */ + $stmt = $this->mockSlaveAdapter + ->getDriver() + ->createStatement(); + + $stmt->expects($this->once()) + ->method('execute') + ->willReturn($this->getMockBuilder(ResultInterface::class)->onlyMethods([])->getMock()); + $table->select('foo = bar'); } - /** - * @throws Exception - */ - public function testPostInitializeWithProvidedSlaveSql(): void + #[Override] + protected function setUp(): void { - $slaveSql = new Sql($this->mockSlaveAdapter, 'foo'); - $feature = new MasterSlaveFeature($this->mockSlaveAdapter, $slaveSql); + $this->mockMasterAdapter = $this->getMockBuilder(AdapterInterface::class)->onlyMethods([])->getMock(); + $this->mockSlaveAdapter = $this->getMockBuilder(AdapterInterface::class)->onlyMethods([])->getMock(); + $this->mockStatement = $this->getMockBuilder(StatementInterface::class)->onlyMethods([])->getMock(); - $this->getMockBuilder(TableGateway::class) - ->setConstructorArgs(['foo', $this->mockMasterAdapter, $feature]) - ->onlyMethods([]) - ->getMock(); + $mockDriver = $this->getMockBuilder(DriverInterface::class)->onlyMethods([])->getMock(); + $mockDriver->expects($this->any()) + ->method('createStatement') + ->willReturn(clone $this->mockStatement); + $this->mockMasterAdapter->expects($this->any())->method('getDriver')->willReturn($mockDriver); + $this->mockMasterAdapter->expects($this->any())->method('getPlatform')->willReturn(new Sql92()); - // The provided slaveSql should be used instead of creating a new one - self::assertSame($slaveSql, $feature->getSlaveSql()); + $mockDriver = $this->getMockBuilder(DriverInterface::class)->onlyMethods([])->getMock(); + $mockDriver->expects($this->any()) + ->method('createStatement') + ->willReturn(clone $this->mockStatement); + $this->mockSlaveAdapter->expects($this->any())->method('getDriver')->willReturn($mockDriver); + $this->mockSlaveAdapter->expects($this->any())->method('getPlatform')->willReturn(new Sql92()); + + $this->feature = new MasterSlaveFeature($this->mockSlaveAdapter); } } diff --git a/test/unit/TableGateway/Feature/MetadataFeatureTest.php b/test/unit/TableGateway/Feature/MetadataFeatureTest.php index 66b929b85..71443c453 100644 --- a/test/unit/TableGateway/Feature/MetadataFeatureTest.php +++ b/test/unit/TableGateway/Feature/MetadataFeatureTest.php @@ -23,15 +23,25 @@ #[RequiresPhp('<= 8.6')] class MetadataFeatureTest extends TestCase { + public function testConstructorSetsInitialSharedData(): void + { + $metadataMock = $this->getMockBuilder(MetadataInterface::class)->getMock(); + $feature = new MetadataFeature($metadataMock); + + $r = new ReflectionProperty(MetadataFeature::class, 'sharedData'); + $sharedData = $r->getValue($feature); + + self::assertIsArray($sharedData); + self::assertArrayHasKey('metadata', $sharedData); + self::assertNull($sharedData['metadata']['primaryKey']); + self::assertEquals([], $sharedData['metadata']['columns']); + } + /** * @throws Exception * @throws \Exception */ - /** - * @throws Exception - * @throws \Exception - */ - public function testPostInitializeRecordsPrimaryKeyColumnToSharedMetadata(): void + public function testPostInitializeRecordsListOfColumnsInPrimaryKeyToSharedMetadata(): void { /** @var AbstractTableGateway&MockObject $tableGatewayMock */ $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class)->onlyMethods([])->getMock(); @@ -47,7 +57,7 @@ public function testPostInitializeRecordsPrimaryKeyColumnToSharedMetadata(): voi ->willReturn(new TableObject('foo')); $constraintObject = new ConstraintObject('id_pk', 'table'); - $constraintObject->setColumns(['id']); + $constraintObject->setColumns(['composite', 'id']); $constraintObject->setType('PRIMARY KEY'); $metadataMock->expects($this->any())->method('getConstraints')->willReturn([$constraintObject]); @@ -62,16 +72,20 @@ public function testPostInitializeRecordsPrimaryKeyColumnToSharedMetadata(): voi self::assertIsArray($sharedData); self::assertTrue( isset($sharedData['metadata']['primaryKey']), - 'Shared data must have metadata entry for primary key' + 'Shared data must have metadata entry for primary key', ); - self::assertSame('id', $sharedData['metadata']['primaryKey']); + self::assertEquals(['composite', 'id'], $sharedData['metadata']['primaryKey']); } /** * @throws Exception * @throws \Exception */ - public function testPostInitializeRecordsListOfColumnsInPrimaryKeyToSharedMetadata(): void + /** + * @throws Exception + * @throws \Exception + */ + public function testPostInitializeRecordsPrimaryKeyColumnToSharedMetadata(): void { /** @var AbstractTableGateway&MockObject $tableGatewayMock */ $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class)->onlyMethods([])->getMock(); @@ -87,7 +101,7 @@ public function testPostInitializeRecordsListOfColumnsInPrimaryKeyToSharedMetada ->willReturn(new TableObject('foo')); $constraintObject = new ConstraintObject('id_pk', 'table'); - $constraintObject->setColumns(['composite', 'id']); + $constraintObject->setColumns(['id']); $constraintObject->setType('PRIMARY KEY'); $metadataMock->expects($this->any())->method('getConstraints')->willReturn([$constraintObject]); @@ -102,9 +116,9 @@ public function testPostInitializeRecordsListOfColumnsInPrimaryKeyToSharedMetada self::assertIsArray($sharedData); self::assertTrue( isset($sharedData['metadata']['primaryKey']), - 'Shared data must have metadata entry for primary key' + 'Shared data must have metadata entry for primary key', ); - self::assertEquals(['composite', 'id'], $sharedData['metadata']['primaryKey']); + self::assertSame('id', $sharedData['metadata']['primaryKey']); } /** @@ -168,34 +182,29 @@ public function testPostInitializeThrowsExceptionWhenNoPrimaryKeyFound(): void * @throws Exception * @throws \Exception */ - public function testPostInitializeWithTableIdentifier(): void + public function testPostInitializeWithArrayTable(): void { /** @var AbstractTableGateway&MockObject $tableGatewayMock */ $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class)->onlyMethods([])->getMock(); - // Set the table property as a TableIdentifier - $tableIdentifier = new TableIdentifier('foo', 'myschema'); - $tableProperty = new ReflectionProperty(AbstractTableGateway::class, 'table'); - $tableProperty->setValue($tableGatewayMock, $tableIdentifier); + // Set the table property as an array (aliased table) + $tableProperty = new ReflectionProperty(AbstractTableGateway::class, 'table'); + $tableProperty->setValue($tableGatewayMock, ['t' => 'foo']); $metadataMock = $this->getMockBuilder(MetadataInterface::class)->getMock(); $metadataMock->expects($this->any()) ->method('getColumnNames') - ->with('foo', 'myschema') + ->with('foo', null) ->willReturn(['id', 'name']); $metadataMock->expects($this->any()) ->method('getTable') - ->with('foo', 'myschema') ->willReturn(new TableObject('foo')); $constraintObject = new ConstraintObject('id_pk', 'foo'); $constraintObject->setColumns(['id']); $constraintObject->setType('PRIMARY KEY'); - $metadataMock->expects($this->any()) - ->method('getConstraints') - ->with('foo', 'myschema') - ->willReturn([$constraintObject]); + $metadataMock->expects($this->any())->method('getConstraints')->willReturn([$constraintObject]); $feature = new MetadataFeature($metadataMock); $feature->setTableGateway($tableGatewayMock); @@ -211,29 +220,34 @@ public function testPostInitializeWithTableIdentifier(): void * @throws Exception * @throws \Exception */ - public function testPostInitializeWithArrayTable(): void + public function testPostInitializeWithTableIdentifier(): void { /** @var AbstractTableGateway&MockObject $tableGatewayMock */ $tableGatewayMock = $this->getMockBuilder(AbstractTableGateway::class)->onlyMethods([])->getMock(); - // Set the table property as an array (aliased table) - $tableProperty = new ReflectionProperty(AbstractTableGateway::class, 'table'); - $tableProperty->setValue($tableGatewayMock, ['t' => 'foo']); + // Set the table property as a TableIdentifier + $tableIdentifier = new TableIdentifier('foo', 'myschema'); + $tableProperty = new ReflectionProperty(AbstractTableGateway::class, 'table'); + $tableProperty->setValue($tableGatewayMock, $tableIdentifier); $metadataMock = $this->getMockBuilder(MetadataInterface::class)->getMock(); $metadataMock->expects($this->any()) ->method('getColumnNames') - ->with('foo', null) + ->with('foo', 'myschema') ->willReturn(['id', 'name']); $metadataMock->expects($this->any()) ->method('getTable') + ->with('foo', 'myschema') ->willReturn(new TableObject('foo')); $constraintObject = new ConstraintObject('id_pk', 'foo'); $constraintObject->setColumns(['id']); $constraintObject->setType('PRIMARY KEY'); - $metadataMock->expects($this->any())->method('getConstraints')->willReturn([$constraintObject]); + $metadataMock->expects($this->any()) + ->method('getConstraints') + ->with('foo', 'myschema') + ->willReturn([$constraintObject]); $feature = new MetadataFeature($metadataMock); $feature->setTableGateway($tableGatewayMock); @@ -244,18 +258,4 @@ public function testPostInitializeWithArrayTable(): void self::assertSame('id', $sharedData['metadata']['primaryKey']); } - - public function testConstructorSetsInitialSharedData(): void - { - $metadataMock = $this->getMockBuilder(MetadataInterface::class)->getMock(); - $feature = new MetadataFeature($metadataMock); - - $r = new ReflectionProperty(MetadataFeature::class, 'sharedData'); - $sharedData = $r->getValue($feature); - - self::assertIsArray($sharedData); - self::assertArrayHasKey('metadata', $sharedData); - self::assertNull($sharedData['metadata']['primaryKey']); - self::assertEquals([], $sharedData['metadata']['columns']); - } } diff --git a/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php b/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php index 97ae43253..7fdbb033a 100644 --- a/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php +++ b/test/unit/TableGateway/Feature/RowGatewayFeatureTest.php @@ -19,63 +19,40 @@ class RowGatewayFeatureTest extends TestCase { - private function createTableGatewayMock( - ResultSetInterface $resultSetPrototype, - ?FeatureSet $featureSet = null - ): AbstractTableGateway&MockObject { - /** @var AbstractTableGateway&MockObject $tableGateway */ - $tableGateway = $this->getMockBuilder(AbstractTableGateway::class) - ->disableOriginalConstructor() - ->getMock(); - - $adapter = $this->createMock(AdapterInterface::class); - - $tableProperty = new ReflectionProperty(AbstractTableGateway::class, 'table'); - $tableProperty->setValue($tableGateway, 'test_table'); - - $adapterProperty = new ReflectionProperty(AbstractTableGateway::class, 'adapter'); - $adapterProperty->setValue($tableGateway, $adapter); - - $resultSetProperty = new ReflectionProperty(AbstractTableGateway::class, 'resultSetPrototype'); - $resultSetProperty->setValue($tableGateway, $resultSetPrototype); - - if ($featureSet !== null) { - $featureSetProperty = new ReflectionProperty(AbstractTableGateway::class, 'featureSet'); - $featureSetProperty->setValue($tableGateway, $featureSet); - } - - return $tableGateway; - } - - public function testPostInitializeWithStringPrimaryKey(): void + public function testConstructorStoresArguments(): void { - $resultSet = new ResultSet(); - $tableGateway = $this->createTableGatewayMock($resultSet); - $feature = new RowGatewayFeature('id'); - $feature->setTableGateway($tableGateway); - $feature->postInitialize(); + // Use reflection to check the constructorArguments property + $property = new ReflectionProperty(RowGatewayFeature::class, 'constructorArguments'); + $args = $property->getValue($feature); - $prototype = $resultSet->getRowPrototype(); - self::assertInstanceOf(RowGatewayInterface::class, $prototype); + self::assertEquals(['id'], $args); } - public function testPostInitializeWithRowGatewayInstance(): void + public function testConstructorStoresRowGatewayInstance(): void { - $resultSet = new ResultSet(); - /** @var RowGatewayInterface&MockObject $rowGateway */ $rowGateway = $this->createMock(RowGatewayInterface::class); - $tableGateway = $this->createTableGatewayMock($resultSet); - $feature = new RowGatewayFeature($rowGateway); - $feature->setTableGateway($tableGateway); - $feature->postInitialize(); + // Use reflection to check the constructorArguments property + $property = new ReflectionProperty(RowGatewayFeature::class, 'constructorArguments'); + $args = $property->getValue($feature); - self::assertSame($rowGateway, $resultSet->getRowPrototype()); + self::assertSame($rowGateway, $args[0]); + } + + public function testConstructorWithNoArguments(): void + { + $feature = new RowGatewayFeature(); + + // Use reflection to check the constructorArguments property + $property = new ReflectionProperty(RowGatewayFeature::class, 'constructorArguments'); + $args = $property->getValue($feature); + + self::assertEquals([], $args); } public function testPostInitializeThrowsExceptionForNonResultSet(): void @@ -92,20 +69,18 @@ public function testPostInitializeThrowsExceptionForNonResultSet(): void $feature->postInitialize(); } - public function testPostInitializeWithMetadataFeature(): void + public function testPostInitializeThrowsExceptionWhenMetadataHasNoMetadataKey(): void { $resultSet = new ResultSet(); - // Create a MetadataFeature mock with primary key in sharedData + // Create a MetadataFeature mock without the metadata key in sharedData $metadataFeature = $this->getMockBuilder(MetadataFeature::class) ->disableOriginalConstructor() ->getMock(); - // Set sharedData with metadata containing primaryKey + // Set empty sharedData on the metadata feature $sharedDataProperty = new ReflectionProperty(MetadataFeature::class, 'sharedData'); - $sharedDataProperty->setValue($metadataFeature, [ - 'metadata' => ['primaryKey' => 'id'], - ]); + $sharedDataProperty->setValue($metadataFeature, []); $featureSet = $this->createMock(FeatureSet::class); $featureSet->expects($this->once()) @@ -118,10 +93,10 @@ public function testPostInitializeWithMetadataFeature(): void $feature = new RowGatewayFeature(); $feature->setTableGateway($tableGateway); - $feature->postInitialize(); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('No information was provided to the RowGatewayFeature'); - $prototype = $resultSet->getRowPrototype(); - self::assertInstanceOf(RowGatewayInterface::class, $prototype); + $feature->postInitialize(); } public function testPostInitializeThrowsExceptionWhenNoMetadataAndNoPrimaryKey(): void @@ -145,18 +120,20 @@ public function testPostInitializeThrowsExceptionWhenNoMetadataAndNoPrimaryKey() $feature->postInitialize(); } - public function testPostInitializeThrowsExceptionWhenMetadataHasNoMetadataKey(): void + public function testPostInitializeWithMetadataFeature(): void { $resultSet = new ResultSet(); - // Create a MetadataFeature mock without the metadata key in sharedData + // Create a MetadataFeature mock with primary key in sharedData $metadataFeature = $this->getMockBuilder(MetadataFeature::class) ->disableOriginalConstructor() ->getMock(); - // Set empty sharedData on the metadata feature + // Set sharedData with metadata containing primaryKey $sharedDataProperty = new ReflectionProperty(MetadataFeature::class, 'sharedData'); - $sharedDataProperty->setValue($metadataFeature, []); + $sharedDataProperty->setValue($metadataFeature, [ + 'metadata' => ['primaryKey' => 'id'], + ]); $featureSet = $this->createMock(FeatureSet::class); $featureSet->expects($this->once()) @@ -169,45 +146,68 @@ public function testPostInitializeThrowsExceptionWhenMetadataHasNoMetadataKey(): $feature = new RowGatewayFeature(); $feature->setTableGateway($tableGateway); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('No information was provided to the RowGatewayFeature'); - $feature->postInitialize(); - } - - public function testConstructorStoresArguments(): void - { - $feature = new RowGatewayFeature('id'); - - // Use reflection to check the constructorArguments property - $property = new ReflectionProperty(RowGatewayFeature::class, 'constructorArguments'); - $args = $property->getValue($feature); - self::assertEquals(['id'], $args); + $prototype = $resultSet->getRowPrototype(); + self::assertInstanceOf(RowGatewayInterface::class, $prototype); } - public function testConstructorStoresRowGatewayInstance(): void + public function testPostInitializeWithRowGatewayInstance(): void { + $resultSet = new ResultSet(); + /** @var RowGatewayInterface&MockObject $rowGateway */ $rowGateway = $this->createMock(RowGatewayInterface::class); + $tableGateway = $this->createTableGatewayMock($resultSet); + $feature = new RowGatewayFeature($rowGateway); + $feature->setTableGateway($tableGateway); - // Use reflection to check the constructorArguments property - $property = new ReflectionProperty(RowGatewayFeature::class, 'constructorArguments'); - $args = $property->getValue($feature); + $feature->postInitialize(); - self::assertSame($rowGateway, $args[0]); + self::assertSame($rowGateway, $resultSet->getRowPrototype()); } - public function testConstructorWithNoArguments(): void + public function testPostInitializeWithStringPrimaryKey(): void { - $feature = new RowGatewayFeature(); + $resultSet = new ResultSet(); + $tableGateway = $this->createTableGatewayMock($resultSet); - // Use reflection to check the constructorArguments property - $property = new ReflectionProperty(RowGatewayFeature::class, 'constructorArguments'); - $args = $property->getValue($feature); + $feature = new RowGatewayFeature('id'); + $feature->setTableGateway($tableGateway); - self::assertEquals([], $args); + $feature->postInitialize(); + + $prototype = $resultSet->getRowPrototype(); + self::assertInstanceOf(RowGatewayInterface::class, $prototype); + } + + private function createTableGatewayMock( + ResultSetInterface $resultSetPrototype, + ?FeatureSet $featureSet = null, + ): AbstractTableGateway&MockObject { + /** @var AbstractTableGateway&MockObject $tableGateway */ + $tableGateway = $this->getMockBuilder(AbstractTableGateway::class) + ->disableOriginalConstructor() + ->getMock(); + + $adapter = $this->createMock(AdapterInterface::class); + + $tableProperty = new ReflectionProperty(AbstractTableGateway::class, 'table'); + $tableProperty->setValue($tableGateway, 'test_table'); + + $adapterProperty = new ReflectionProperty(AbstractTableGateway::class, 'adapter'); + $adapterProperty->setValue($tableGateway, $adapter); + + $resultSetProperty = new ReflectionProperty(AbstractTableGateway::class, 'resultSetPrototype'); + $resultSetProperty->setValue($tableGateway, $resultSetPrototype); + + if (null !== $featureSet) { + $featureSetProperty = new ReflectionProperty(AbstractTableGateway::class, 'featureSet'); + $featureSetProperty->setValue($tableGateway, $featureSet); + } + + return $tableGateway; } } diff --git a/test/unit/TableGateway/Feature/SequenceFeatureTest.php b/test/unit/TableGateway/Feature/SequenceFeatureTest.php index f8eaaadac..b90262fd0 100644 --- a/test/unit/TableGateway/Feature/SequenceFeatureTest.php +++ b/test/unit/TableGateway/Feature/SequenceFeatureTest.php @@ -32,52 +32,44 @@ final class SequenceFeatureTest extends TestCase /** @var string sequence name */ protected static string $sequenceName = 'table_sequence'; - #[Override] - protected function setUp(): void + /** @psalm-return array */ + public static function lastSequenceIdProvider(): array { - $this->feature = new SequenceFeature($this->primaryKeyField, self::$sequenceName); + return [ + ['PostgreSQL'], + ['Oracle'], + ]; } - private function createTableGatewayWithPlatform( - string $platformName, - int $sequenceValue = 2 - ): AbstractTableGateway&MockObject { - $platform = $this->createMock(PlatformInterface::class); - $platform->expects($this->any()) - ->method('getName') - ->willReturn($platformName); - $platform->expects($this->any()) - ->method('quoteIdentifier') - ->willReturnCallback(fn($name) => $name); + /** @psalm-return array */ + public static function nextSequenceIdProvider(): array + { + return [ + ['PostgreSQL', 'SELECT NEXTVAL(\'"' . self::$sequenceName . '"\')'], + ['Oracle', 'SELECT ' . self::$sequenceName . '.NEXTVAL as "nextval" FROM dual'], + ]; + } - $result = $this->createMock(ResultInterface::class); - $result->expects($this->any()) - ->method('current') - ->willReturn(['nextval' => $sequenceValue, 'currval' => $sequenceValue]); + #[DataProvider('lastSequenceIdProvider')] + public function testLastSequenceId(string $platformName): void + { + $tableGateway = $this->createTableGatewayWithPlatform($platformName, 55); + $this->feature->setTableGateway($tableGateway); - $statement = $this->createMock(StatementInterface::class); - $statement->expects($this->any()) - ->method('execute') - ->willReturn($result); + $result = $this->feature->lastSequenceId(); - $adapter = $this->getMockBuilder(Adapter::class) - ->onlyMethods(['getPlatform', 'createStatement']) - ->disableOriginalConstructor() - ->getMock(); - $adapter->expects($this->any()) - ->method('getPlatform') - ->willReturn($platform); - $adapter->expects($this->any()) - ->method('createStatement') - ->willReturn($statement); + self::assertEquals(55, $result); + } - /** @var AbstractTableGateway&MockObject $tableGateway */ - $tableGateway = $this->getMockBuilder(TableGateway::class) - ->setConstructorArgs(['table', $adapter]) - ->onlyMethods([]) - ->getMock(); + public function testLastSequenceIdThrowsExceptionForUnsupportedPlatform(): void + { + $tableGateway = $this->createTableGatewayWithPlatform('MySQL'); + $this->feature->setTableGateway($tableGateway); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Unsupported platform for retrieving last sequence id'); - return $tableGateway; + $this->feature->lastSequenceId(); } /** @@ -114,8 +106,7 @@ public function testNextSequenceId(string $platformName, string $statementSql): $adapter->expects($this->once()) ->method('createStatement') ->willReturn($statement); - $this->tableGateway = $this - ->getMockBuilder(TableGateway::class) + $this->tableGateway = $this->getMockBuilder(TableGateway::class) ->setConstructorArgs(['table', $adapter]) ->onlyMethods([]) ->getMock(); @@ -123,50 +114,31 @@ public function testNextSequenceId(string $platformName, string $statementSql): $this->feature->nextSequenceId(); } - /** @psalm-return array */ - public static function nextSequenceIdProvider(): array - { - return [ - ['PostgreSQL', 'SELECT NEXTVAL(\'"' . self::$sequenceName . '"\')'], - ['Oracle', 'SELECT ' . self::$sequenceName . '.NEXTVAL as "nextval" FROM dual'], - ]; - } - - public function testPreInsertWhenPrimaryKeyAlreadyInValues(): void + public function testNextSequenceIdThrowsExceptionForUnsupportedPlatform(): void { - $tableGateway = $this->createTableGatewayWithPlatform('PostgreSQL'); + $tableGateway = $this->createTableGatewayWithPlatform('MySQL'); $this->feature->setTableGateway($tableGateway); - $insert = new Insert('table'); - $insert->columns(['id', 'name']); - $insert->values([42, 'test']); - - $result = $this->feature->preInsert($insert); - - self::assertSame($insert, $result); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Unsupported platform for retrieving next sequence id'); - $sequenceValueProp = new ReflectionProperty(SequenceFeature::class, 'sequenceValue'); - self::assertEquals(42, $sequenceValueProp->getValue($this->feature)); + $this->feature->nextSequenceId(); } - public function testPreInsertGeneratesSequenceWhenPrimaryKeyNotInValues(): void + public function testPostInsertDoesNotSetLastInsertValueWhenSequenceValueIsNull(): void { - $tableGateway = $this->createTableGatewayWithPlatform('PostgreSQL', 99); + $tableGateway = $this->createTableGatewayWithPlatform('PostgreSQL'); $this->feature->setTableGateway($tableGateway); - $insert = new Insert('table'); - $insert->columns(['name']); - $insert->values(['test']); - - $result = $this->feature->preInsert($insert); + $lastInsertValueProp = new ReflectionProperty(AbstractTableGateway::class, 'lastInsertValue'); + $lastInsertValueProp->setValue($tableGateway, 999); - self::assertSame($insert, $result); + $statement = $this->createMock(StatementInterface::class); + $result = $this->createMock(ResultInterface::class); - $sequenceValueProp = new ReflectionProperty(SequenceFeature::class, 'sequenceValue'); - self::assertEquals(99, $sequenceValueProp->getValue($this->feature)); + $this->feature->postInsert($statement, $result); - $rawState = $insert->getRawState(); - self::assertContains('id', $rawState['columns']); + self::assertEquals(999, $lastInsertValueProp->getValue($tableGateway)); } public function testPostInsertSetsLastInsertValue(): void @@ -187,62 +159,71 @@ public function testPostInsertSetsLastInsertValue(): void self::assertEquals(123, $tableGateway->lastInsertValue); } - #[DataProvider('lastSequenceIdProvider')] - public function testLastSequenceId(string $platformName): void + public function testPreInsertGeneratesSequenceWhenPrimaryKeyNotInValues(): void { - $tableGateway = $this->createTableGatewayWithPlatform($platformName, 55); + $tableGateway = $this->createTableGatewayWithPlatform('PostgreSQL', 99); $this->feature->setTableGateway($tableGateway); - $result = $this->feature->lastSequenceId(); + $insert = new Insert('table'); + $insert->columns(['name']); + $insert->values(['test']); - self::assertEquals(55, $result); - } + $result = $this->feature->preInsert($insert); - /** @psalm-return array */ - public static function lastSequenceIdProvider(): array - { - return [ - ['PostgreSQL'], - ['Oracle'], - ]; + self::assertSame($insert, $result); + + $sequenceValueProp = new ReflectionProperty(SequenceFeature::class, 'sequenceValue'); + self::assertEquals(99, $sequenceValueProp->getValue($this->feature)); + + $rawState = $insert->getRawState(); + self::assertContains('id', $rawState['columns']); } - public function testNextSequenceIdThrowsExceptionForUnsupportedPlatform(): void + public function testPreInsertReturnsEarlyWhenNextSequenceIdReturnsNull(): void { - $tableGateway = $this->createTableGatewayWithPlatform('MySQL'); - $this->feature->setTableGateway($tableGateway); + $tableGateway = $this->createTableGatewayWithPlatform('PostgreSQL'); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Unsupported platform for retrieving next sequence id'); + $feature = $this->getMockBuilder(SequenceFeature::class) + ->setConstructorArgs([$this->primaryKeyField, self::$sequenceName]) + ->onlyMethods(['nextSequenceId']) + ->getMock(); - $this->feature->nextSequenceId(); - } + $feature->expects($this->once()) + ->method('nextSequenceId') + ->willReturn(null); - public function testLastSequenceIdThrowsExceptionForUnsupportedPlatform(): void - { - $tableGateway = $this->createTableGatewayWithPlatform('MySQL'); - $this->feature->setTableGateway($tableGateway); + $feature->setTableGateway($tableGateway); - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Unsupported platform for retrieving last sequence id'); + $insert = new Insert('table'); + $insert->columns(['name']); + $insert->values(['test']); - $this->feature->lastSequenceId(); + $result = $feature->preInsert($insert); + + self::assertSame($insert, $result); + + $sequenceValueProp = new ReflectionProperty(SequenceFeature::class, 'sequenceValue'); + self::assertNull($sequenceValueProp->getValue($feature)); + + $rawState = $insert->getRawState(); + self::assertNotContains('id', $rawState['columns']); } - public function testPostInsertDoesNotSetLastInsertValueWhenSequenceValueIsNull(): void + public function testPreInsertWhenPrimaryKeyAlreadyInValues(): void { $tableGateway = $this->createTableGatewayWithPlatform('PostgreSQL'); $this->feature->setTableGateway($tableGateway); - $lastInsertValueProp = new ReflectionProperty(AbstractTableGateway::class, 'lastInsertValue'); - $lastInsertValueProp->setValue($tableGateway, 999); + $insert = new Insert('table'); + $insert->columns(['id', 'name']); + $insert->values([42, 'test']); - $statement = $this->createMock(StatementInterface::class); - $result = $this->createMock(ResultInterface::class); + $result = $this->feature->preInsert($insert); - $this->feature->postInsert($statement, $result); + self::assertSame($insert, $result); - self::assertEquals(999, $lastInsertValueProp->getValue($tableGateway)); + $sequenceValueProp = new ReflectionProperty(SequenceFeature::class, 'sequenceValue'); + self::assertEquals(42, $sequenceValueProp->getValue($this->feature)); } public function testPreInsertWithPrimaryKeyColumnButNullValue(): void @@ -262,33 +243,49 @@ public function testPreInsertWithPrimaryKeyColumnButNullValue(): void self::assertNull($sequenceValueProp->getValue($this->feature)); } - public function testPreInsertReturnsEarlyWhenNextSequenceIdReturnsNull(): void + #[Override] + protected function setUp(): void { - $tableGateway = $this->createTableGatewayWithPlatform('PostgreSQL'); - - $feature = $this->getMockBuilder(SequenceFeature::class) - ->setConstructorArgs([$this->primaryKeyField, self::$sequenceName]) - ->onlyMethods(['nextSequenceId']) - ->getMock(); - - $feature->expects($this->once()) - ->method('nextSequenceId') - ->willReturn(null); - - $feature->setTableGateway($tableGateway); + $this->feature = new SequenceFeature($this->primaryKeyField, self::$sequenceName); + } - $insert = new Insert('table'); - $insert->columns(['name']); - $insert->values(['test']); + private function createTableGatewayWithPlatform( + string $platformName, + int $sequenceValue = 2, + ): AbstractTableGateway&MockObject { + $platform = $this->createMock(PlatformInterface::class); + $platform->expects($this->any()) + ->method('getName') + ->willReturn($platformName); + $platform->expects($this->any()) + ->method('quoteIdentifier') + ->willReturnCallback(static fn($name) => $name); - $result = $feature->preInsert($insert); + $result = $this->createMock(ResultInterface::class); + $result->expects($this->any()) + ->method('current') + ->willReturn(['nextval' => $sequenceValue, 'currval' => $sequenceValue]); - self::assertSame($insert, $result); + $statement = $this->createMock(StatementInterface::class); + $statement->expects($this->any()) + ->method('execute') + ->willReturn($result); - $sequenceValueProp = new ReflectionProperty(SequenceFeature::class, 'sequenceValue'); - self::assertNull($sequenceValueProp->getValue($feature)); + $adapter = $this->getMockBuilder(Adapter::class) + ->onlyMethods(['getPlatform', 'createStatement']) + ->disableOriginalConstructor() + ->getMock(); + $adapter->expects($this->any()) + ->method('getPlatform') + ->willReturn($platform); + $adapter->expects($this->any()) + ->method('createStatement') + ->willReturn($statement); - $rawState = $insert->getRawState(); - self::assertNotContains('id', $rawState['columns']); + /** @var AbstractTableGateway&MockObject $tableGateway */ + return $this->getMockBuilder(TableGateway::class) + ->setConstructorArgs(['table', $adapter]) + ->onlyMethods([]) + ->getMock(); } } diff --git a/test/unit/TableGateway/Feature/TestAsset/TestGlobalAdapterFeatureSubclass.php b/test/unit/TableGateway/Feature/TestAsset/TestGlobalAdapterFeatureSubclass.php index 4a35144c7..71d3ad37c 100644 --- a/test/unit/TableGateway/Feature/TestAsset/TestGlobalAdapterFeatureSubclass.php +++ b/test/unit/TableGateway/Feature/TestAsset/TestGlobalAdapterFeatureSubclass.php @@ -9,6 +9,4 @@ /** * Test subclass to verify class-specific adapter behavior */ -class TestGlobalAdapterFeatureSubclass extends GlobalAdapterFeature -{ -} +class TestGlobalAdapterFeatureSubclass extends GlobalAdapterFeature {} diff --git a/test/unit/TableGateway/Feature/TestAsset/TestTableGateway.php b/test/unit/TableGateway/Feature/TestAsset/TestTableGateway.php index dd69b7255..ca5d004b7 100644 --- a/test/unit/TableGateway/Feature/TestAsset/TestTableGateway.php +++ b/test/unit/TableGateway/Feature/TestAsset/TestTableGateway.php @@ -8,7 +8,5 @@ class TestTableGateway extends TableGateway { - public function __construct() - { - } + public function __construct() {} } diff --git a/test/unit/TableGateway/Feature/TestAsset/TestTableGatewayFeature.php b/test/unit/TableGateway/Feature/TestAsset/TestTableGatewayFeature.php index 6aefacf5d..d297ef04c 100644 --- a/test/unit/TableGateway/Feature/TestAsset/TestTableGatewayFeature.php +++ b/test/unit/TableGateway/Feature/TestAsset/TestTableGatewayFeature.php @@ -18,13 +18,6 @@ class TestTableGatewayFeature extends AbstractFeature /** @var array> */ public array $magicMethodSpecs = []; - public function testMethod(mixed ...$args): mixed - { - $this->called = true; - $this->receivedArgs = $args; - return $this->returnValue; - } - public function customMethod(array $args): string { $this->called = true; @@ -37,4 +30,11 @@ public function getMagicMethodSpecifications(): array { return $this->magicMethodSpecs; } + + public function testMethod(mixed ...$args): mixed + { + $this->called = true; + $this->receivedArgs = $args; + return $this->returnValue; + } } diff --git a/test/unit/TableGateway/TableGatewayTest.php b/test/unit/TableGateway/TableGatewayTest.php index 2d9298eb1..050895e92 100644 --- a/test/unit/TableGateway/TableGatewayTest.php +++ b/test/unit/TableGateway/TableGatewayTest.php @@ -35,24 +35,19 @@ final class TableGatewayTest extends TestCase { protected Adapter&MockObject $mockAdapter; - #[Override] - protected function setUp(): void + /** + * @psalm-return array{ + * 'identifier-alias': list{array{U: TableIdentifier}, TableIdentifier}, + * 'simple-alias': list{array{U: string}, string} + * } + */ + public static function aliasedTables(): array { - // mock the adapter, driver, and parts - $mockResult = $this->getMockBuilder(ResultInterface::class)->getMock(); - $mockStatement = $this->getMockBuilder(StatementInterface::class)->getMock(); - $mockStatement->expects($this->any())->method('execute')->willReturn($mockResult); - $mockConnection = $this->getMockBuilder(ConnectionInterface::class)->getMock(); - $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); - $mockDriver->expects($this->any())->method('createStatement')->willReturn($mockStatement); - $mockDriver->expects($this->any())->method('getConnection')->willReturn($mockConnection); - $mockPlatform = $this->getMockBuilder(PlatformInterface::class)->getMock(); - - // setup mock adapter - $this->mockAdapter = $this->getMockBuilder(Adapter::class) - ->onlyMethods([]) - ->setConstructorArgs([$mockDriver, $mockPlatform]) - ->getMock(); + $identifier = new TableIdentifier('Users'); + return [ + 'simple-alias' => [['U' => 'Users'], 'Users'], + 'identifier-alias' => [['U' => $identifier], $identifier], + ]; } /** @@ -63,7 +58,7 @@ public function testConstructor(): void // constructor with only required args $table = new TableGateway( 'foo', - $this->mockAdapter + $this->mockAdapter, ); self::assertEquals('foo', $table->getTable()); @@ -73,12 +68,12 @@ public function testConstructor(): void self::assertInstanceOf(Sql::class, $table->getSql()); // injecting all args - $table = new TableGateway( + $table = new TableGateway( 'foo', $this->mockAdapter, $featureSet = new Feature\FeatureSet(), - $resultSet = new ResultSet(), - $sql = new Sql($this->mockAdapter, 'foo') + $resultSet = new ResultSet(), + $sql = new Sql($this->mockAdapter, 'foo'), ); self::assertEquals('foo', $table->getTable()); @@ -92,80 +87,82 @@ public function testConstructor(): void /** @psalm-suppress NullArgument - Testing incorrect constructor */ new TableGateway( null, - $this->mockAdapter + $this->mockAdapter, ); } - #[Group('6726')] - #[Group('6740')] - public function testTableAsString(): void + public function testConstructorThrowsExceptionWhenSqlTableDoesNotMatch(): void { - $ti = 'fooTable.barSchema'; - // constructor with only required args - $table = new TableGateway( - $ti, - $this->mockAdapter + $sql = new Sql($this->mockAdapter, 'bar'); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage( + 'The table inside the provided Sql object must match the table of this TableGateway', ); - self::assertEquals($ti, $table->getTable()); + new TableGateway('foo', $this->mockAdapter, null, null, $sql); } - #[Group('6726')] - #[Group('6740')] - public function testTableAsTableIdentifierObject(): void + public function testConstructorWithArrayOfFeatures(): void { - $ti = new TableIdentifier('fooTable', 'barSchema'); - // constructor with only required args - $table = new TableGateway( - $ti, - $this->mockAdapter - ); + $feature1 = new Feature\SequenceFeature('id', 'foo_seq'); + $feature2 = new Feature\GlobalAdapterFeature(); - self::assertEquals($ti, $table->getTable()); + // Set up global adapter for GlobalAdapterFeature + Feature\GlobalAdapterFeature::setStaticAdapter($this->mockAdapter); + + $table = new TableGateway('foo', $this->mockAdapter, [$feature1, $feature2]); + + $featureSet = $table->getFeatureSet(); + self::assertInstanceOf(FeatureSet::class, $featureSet); + self::assertSame($feature1, $featureSet->getFeatureByClassName(Feature\SequenceFeature::class)); + self::assertSame($feature2, $featureSet->getFeatureByClassName(Feature\GlobalAdapterFeature::class)); + + // Clean up static adapter + $reflection = new ReflectionProperty(Feature\GlobalAdapterFeature::class, 'staticAdapters'); + $reflection->setValue(null, []); } - #[Group('6726')] - #[Group('6740')] - public function testTableAsAliasedTableIdentifierObject(): void + public function testConstructorWithCustomResultSetPrototype(): void { - // phpcs:disable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps - $aliasedTI = ['foo' => new TableIdentifier('fooTable', 'barSchema')]; - // constructor with only required args - $table = new TableGateway( - $aliasedTI, - $this->mockAdapter - ); + $resultSet = new ResultSet(); - self::assertEquals($aliasedTI, $table->getTable()); - // phpcs:enable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps + $table = new TableGateway('foo', $this->mockAdapter, null, $resultSet); + + self::assertSame($resultSet, $table->getResultSetPrototype()); } - /** - * @psalm-return array{ - * 'identifier-alias': list{array{U: TableIdentifier}, TableIdentifier}, - * 'simple-alias': list{array{U: string}, string} - * } - */ - public static function aliasedTables(): array + public function testConstructorWithFeatureSet(): void { - $identifier = new TableIdentifier('Users'); - return [ - 'simple-alias' => [['U' => 'Users'], 'Users'], - 'identifier-alias' => [['U' => $identifier], $identifier], - ]; + $feature = new Feature\SequenceFeature('id', 'foo_seq'); + $featureSet = new FeatureSet([$feature]); + + $table = new TableGateway('foo', $this->mockAdapter, $featureSet); + + self::assertSame($featureSet, $table->getFeatureSet()); + } + + public function testConstructorWithSingleFeature(): void + { + $feature = new Feature\SequenceFeature('id', 'foo_seq'); + + $table = new TableGateway('foo', $this->mockAdapter, $feature); + + $featureSet = $table->getFeatureSet(); + self::assertInstanceOf(FeatureSet::class, $featureSet); + self::assertSame($feature, $featureSet->getFeatureByClassName(Feature\SequenceFeature::class)); } /** * @param AliasedTable $tableValue */ #[DataProvider('aliasedTables')] - #[Group('7311')] - public function testInsertShouldResetTableToUnaliasedTable( + public function testDeleteShouldResetTableToUnaliasedTable( array $tableValue, - string|TableIdentifier $expected + string|TableIdentifier $expected, ): void { - $insert = new Insert(); - $insert->into($tableValue); + $delete = new Delete(); + $delete->from($tableValue); $result = $this->getMockBuilder(ResultInterface::class) ->getMock(); @@ -179,10 +176,10 @@ public function testInsertShouldResetTableToUnaliasedTable( ->method('execute') ->willReturn($result); - $statementExpectation = function (Insert $insert) use ($expected, $statement): MockObject&StatementInterface { - $state = $insert->getRawState(); + $statementExpectation = function (Delete $delete) use ($expected, $statement): MockObject&StatementInterface { + $state = $delete->getRawState(); $this->assertIsArray($state); - self::assertSame($expected, $state['table']); + $this->assertSame($expected, $state['table']); return $statement; }; @@ -193,11 +190,11 @@ public function testInsertShouldResetTableToUnaliasedTable( ->method('getTable') ->willReturn($tableValue); $sql->expects($this->once()) - ->method('insert') - ->willReturn($insert); + ->method('delete') + ->willReturn($delete); $sql->expects($this->once()) ->method('prepareStatementForSqlObject') - ->with($this->equalTo($insert)) + ->with($this->equalTo($delete)) ->willReturnCallback($statementExpectation); $table = new TableGateway( @@ -205,19 +202,20 @@ public function testInsertShouldResetTableToUnaliasedTable( $this->mockAdapter, null, null, - $sql + $sql, ); - $table->insert([ + $table->delete([ 'foo' => 'FOO', ]); - $state = $insert->getRawState(); + $state = $delete->getRawState(); + $this->assertIsArray($state); $this->assertIsArray($state['table']); $this->assertEquals( $tableValue, - $state['table'] + $state['table'], ); } @@ -225,12 +223,13 @@ public function testInsertShouldResetTableToUnaliasedTable( * @param AliasedTable $tableValue */ #[DataProvider('aliasedTables')] - public function testUpdateShouldResetTableToUnaliasedTable( + #[Group('7311')] + public function testInsertShouldResetTableToUnaliasedTable( array $tableValue, - string|TableIdentifier $expected + string|TableIdentifier $expected, ): void { - $update = new Update(); - $update->table($tableValue); + $insert = new Insert(); + $insert->into($tableValue); $result = $this->getMockBuilder(ResultInterface::class) ->getMock(); @@ -244,10 +243,10 @@ public function testUpdateShouldResetTableToUnaliasedTable( ->method('execute') ->willReturn($result); - $statementExpectation = function (Update $update) use ($expected, $statement): MockObject&StatementInterface { - $state = $update->getRawState(); + $statementExpectation = function (Insert $insert) use ($expected, $statement): MockObject&StatementInterface { + $state = $insert->getRawState(); $this->assertIsArray($state); - $this->assertSame($expected, $state['table']); + self::assertSame($expected, $state['table']); return $statement; }; @@ -258,11 +257,11 @@ public function testUpdateShouldResetTableToUnaliasedTable( ->method('getTable') ->willReturn($tableValue); $sql->expects($this->once()) - ->method('update') - ->willReturn($update); + ->method('insert') + ->willReturn($insert); $sql->expects($this->once()) ->method('prepareStatementForSqlObject') - ->with($this->equalTo($update)) + ->with($this->equalTo($insert)) ->willReturnCallback($statementExpectation); $table = new TableGateway( @@ -270,34 +269,77 @@ public function testUpdateShouldResetTableToUnaliasedTable( $this->mockAdapter, null, null, - $sql + $sql, ); - $table->update([ + $table->insert([ 'foo' => 'FOO', - ], [ - 'bar' => 'BAR', ]); - $state = $update->getRawState(); + $state = $insert->getRawState(); $this->assertIsArray($state); $this->assertIsArray($state['table']); $this->assertEquals( $tableValue, - $state['table'] + $state['table'], ); } + #[Group('6726')] + #[Group('6740')] + public function testTableAsAliasedTableIdentifierObject(): void + { + // phpcs:disable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps + $aliasedTI = ['foo' => new TableIdentifier('fooTable', 'barSchema')]; + // constructor with only required args + $table = new TableGateway( + $aliasedTI, + $this->mockAdapter, + ); + + self::assertEquals($aliasedTI, $table->getTable()); + + // phpcs:enable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps + } + + #[Group('6726')] + #[Group('6740')] + public function testTableAsString(): void + { + $ti = 'fooTable.barSchema'; + // constructor with only required args + $table = new TableGateway( + $ti, + $this->mockAdapter, + ); + + self::assertEquals($ti, $table->getTable()); + } + + #[Group('6726')] + #[Group('6740')] + public function testTableAsTableIdentifierObject(): void + { + $ti = new TableIdentifier('fooTable', 'barSchema'); + // constructor with only required args + $table = new TableGateway( + $ti, + $this->mockAdapter, + ); + + self::assertEquals($ti, $table->getTable()); + } + /** * @param AliasedTable $tableValue */ #[DataProvider('aliasedTables')] - public function testDeleteShouldResetTableToUnaliasedTable( + public function testUpdateShouldResetTableToUnaliasedTable( array $tableValue, - string|TableIdentifier $expected + string|TableIdentifier $expected, ): void { - $delete = new Delete(); - $delete->from($tableValue); + $update = new Update(); + $update->table($tableValue); $result = $this->getMockBuilder(ResultInterface::class) ->getMock(); @@ -311,8 +353,8 @@ public function testDeleteShouldResetTableToUnaliasedTable( ->method('execute') ->willReturn($result); - $statementExpectation = function (Delete $delete) use ($expected, $statement): MockObject&StatementInterface { - $state = $delete->getRawState(); + $statementExpectation = function (Update $update) use ($expected, $statement): MockObject&StatementInterface { + $state = $update->getRawState(); $this->assertIsArray($state); $this->assertSame($expected, $state['table']); return $statement; @@ -325,11 +367,11 @@ public function testDeleteShouldResetTableToUnaliasedTable( ->method('getTable') ->willReturn($tableValue); $sql->expects($this->once()) - ->method('delete') - ->willReturn($delete); + ->method('update') + ->willReturn($update); $sql->expects($this->once()) ->method('prepareStatementForSqlObject') - ->with($this->equalTo($delete)) + ->with($this->equalTo($update)) ->willReturnCallback($statementExpectation); $table = new TableGateway( @@ -337,82 +379,41 @@ public function testDeleteShouldResetTableToUnaliasedTable( $this->mockAdapter, null, null, - $sql + $sql, ); - $table->delete([ + $table->update([ 'foo' => 'FOO', + ], [ + 'bar' => 'BAR', ]); - $state = $delete->getRawState(); - + $state = $update->getRawState(); $this->assertIsArray($state); $this->assertIsArray($state['table']); $this->assertEquals( $tableValue, - $state['table'] + $state['table'], ); } - public function testConstructorThrowsExceptionWhenSqlTableDoesNotMatch(): void - { - $sql = new Sql($this->mockAdapter, 'bar'); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage( - 'The table inside the provided Sql object must match the table of this TableGateway' - ); - - new TableGateway('foo', $this->mockAdapter, null, null, $sql); - } - - public function testConstructorWithSingleFeature(): void - { - $feature = new Feature\SequenceFeature('id', 'foo_seq'); - - $table = new TableGateway('foo', $this->mockAdapter, $feature); - - $featureSet = $table->getFeatureSet(); - self::assertInstanceOf(FeatureSet::class, $featureSet); - self::assertSame($feature, $featureSet->getFeatureByClassName(Feature\SequenceFeature::class)); - } - - public function testConstructorWithArrayOfFeatures(): void - { - $feature1 = new Feature\SequenceFeature('id', 'foo_seq'); - $feature2 = new Feature\GlobalAdapterFeature(); - - // Set up global adapter for GlobalAdapterFeature - Feature\GlobalAdapterFeature::setStaticAdapter($this->mockAdapter); - - $table = new TableGateway('foo', $this->mockAdapter, [$feature1, $feature2]); - - $featureSet = $table->getFeatureSet(); - self::assertInstanceOf(FeatureSet::class, $featureSet); - self::assertSame($feature1, $featureSet->getFeatureByClassName(Feature\SequenceFeature::class)); - self::assertSame($feature2, $featureSet->getFeatureByClassName(Feature\GlobalAdapterFeature::class)); - - // Clean up static adapter - $reflection = new ReflectionProperty(Feature\GlobalAdapterFeature::class, 'staticAdapters'); - $reflection->setValue(null, []); - } - - public function testConstructorWithFeatureSet(): void - { - $feature = new Feature\SequenceFeature('id', 'foo_seq'); - $featureSet = new FeatureSet([$feature]); - - $table = new TableGateway('foo', $this->mockAdapter, $featureSet); - - self::assertSame($featureSet, $table->getFeatureSet()); - } - - public function testConstructorWithCustomResultSetPrototype(): void + #[Override] + protected function setUp(): void { - $resultSet = new ResultSet(); - - $table = new TableGateway('foo', $this->mockAdapter, null, $resultSet); + // mock the adapter, driver, and parts + $mockResult = $this->getMockBuilder(ResultInterface::class)->getMock(); + $mockStatement = $this->getMockBuilder(StatementInterface::class)->getMock(); + $mockStatement->expects($this->any())->method('execute')->willReturn($mockResult); + $mockConnection = $this->getMockBuilder(ConnectionInterface::class)->getMock(); + $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + $mockDriver->expects($this->any())->method('createStatement')->willReturn($mockStatement); + $mockDriver->expects($this->any())->method('getConnection')->willReturn($mockConnection); + $mockPlatform = $this->getMockBuilder(PlatformInterface::class)->getMock(); - self::assertSame($resultSet, $table->getResultSetPrototype()); + // setup mock adapter + $this->mockAdapter = $this->getMockBuilder(Adapter::class) + ->onlyMethods([]) + ->setConstructorArgs([$mockDriver, $mockPlatform]) + ->getMock(); } } diff --git a/test/unit/TestAsset/ConnectionWrapper.php b/test/unit/TestAsset/ConnectionWrapper.php index 0ac80f792..8c6d91f8b 100644 --- a/test/unit/TestAsset/ConnectionWrapper.php +++ b/test/unit/TestAsset/ConnectionWrapper.php @@ -14,7 +14,7 @@ final class ConnectionWrapper extends AbstractPdoConnection { public function __construct( - PDO $connectionParameters = new PdoStubDriver() + PDO $connectionParameters = new PdoStubDriver(), ) { $this->setResource($connectionParameters); } diff --git a/test/unit/TestAsset/DeleteDecorator.php b/test/unit/TestAsset/DeleteDecorator.php index 5e30307c4..92632211f 100644 --- a/test/unit/TestAsset/DeleteDecorator.php +++ b/test/unit/TestAsset/DeleteDecorator.php @@ -14,8 +14,8 @@ final class DeleteDecorator extends Sql\Delete implements Sql\Platform\PlatformD * @return $this Provides a fluent interface */ public function setSubject( - Sql\SqlInterface|Sql\PreparableSqlInterface|null $subject - ): DeleteDecorator { + Sql\SqlInterface|Sql\PreparableSqlInterface|null $subject, + ): self { $this->subject = $subject; return $this; } diff --git a/test/unit/TestAsset/DeleteIgnore.php b/test/unit/TestAsset/DeleteIgnore.php index 903b2ad68..f18dbfc10 100644 --- a/test/unit/TestAsset/DeleteIgnore.php +++ b/test/unit/TestAsset/DeleteIgnore.php @@ -22,7 +22,7 @@ class DeleteIgnore extends Delete protected function processdeleteIgnore( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): string { return parent::processDelete($platform, $driver, $parameterContainer); } diff --git a/test/unit/TestAsset/InsertDecorator.php b/test/unit/TestAsset/InsertDecorator.php index 04d8af64b..1f10dad13 100644 --- a/test/unit/TestAsset/InsertDecorator.php +++ b/test/unit/TestAsset/InsertDecorator.php @@ -14,8 +14,8 @@ final class InsertDecorator extends Sql\Insert implements Sql\Platform\PlatformD * @return $this Provides a fluent interface */ public function setSubject( - Sql\SqlInterface|Sql\PreparableSqlInterface|null $subject - ): InsertDecorator { + Sql\SqlInterface|Sql\PreparableSqlInterface|null $subject, + ): self { $this->subject = $subject; return $this; } diff --git a/test/unit/TestAsset/ObjectToString.php b/test/unit/TestAsset/ObjectToString.php index b2c36c820..0789f258b 100644 --- a/test/unit/TestAsset/ObjectToString.php +++ b/test/unit/TestAsset/ObjectToString.php @@ -9,11 +9,12 @@ class ObjectToString implements Stringable { - public function __construct(protected string $value) - { - } + public function __construct( + protected string $value, + ) {} - #[Override] public function __toString(): string + #[Override] + public function __toString(): string { return $this->value; } diff --git a/test/unit/TestAsset/PdoStubDriver.php b/test/unit/TestAsset/PdoStubDriver.php index d8464ff15..51f7dfc1f 100644 --- a/test/unit/TestAsset/PdoStubDriver.php +++ b/test/unit/TestAsset/PdoStubDriver.php @@ -9,22 +9,25 @@ class PdoStubDriver extends PDO { - #[Override] public function beginTransaction(): bool + public function __construct() { - return true; + parent::__construct('sqlite::memory:'); } - #[Override] public function commit(): bool + #[Override] + public function beginTransaction(): bool { return true; } - public function __construct() + #[Override] + public function commit(): bool { - parent::__construct('sqlite::memory:'); + return true; } - #[Override] public function rollBack(): bool + #[Override] + public function rollBack(): bool { return true; } diff --git a/test/unit/TestAsset/Replace.php b/test/unit/TestAsset/Replace.php index 481c5d053..0097f1ef5 100644 --- a/test/unit/TestAsset/Replace.php +++ b/test/unit/TestAsset/Replace.php @@ -22,7 +22,7 @@ class Replace extends Insert protected function processreplace( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): ?string { return parent::processInsert($platform, $driver, $parameterContainer); } diff --git a/test/unit/TestAsset/SelectDecorator.php b/test/unit/TestAsset/SelectDecorator.php index 5f0bb004c..fb07a16de 100644 --- a/test/unit/TestAsset/SelectDecorator.php +++ b/test/unit/TestAsset/SelectDecorator.php @@ -11,7 +11,7 @@ final class SelectDecorator extends Sql\Select implements Sql\Platform\PlatformD /** * @return $this Provides a fluent interface */ - public function setSubject(?object $subject): SelectDecorator + public function setSubject(?object $subject): self { $this->subject = $subject; return $this; diff --git a/test/unit/TestAsset/TemporaryResultSet.php b/test/unit/TestAsset/TemporaryResultSet.php index 47bfc43dc..bdcf4fe6f 100644 --- a/test/unit/TestAsset/TemporaryResultSet.php +++ b/test/unit/TestAsset/TemporaryResultSet.php @@ -6,6 +6,4 @@ use PhpDb\ResultSet\ResultSet; -class TemporaryResultSet extends ResultSet -{ -} +class TemporaryResultSet extends ResultSet {} diff --git a/test/unit/TestAsset/TestSql92Platform.php b/test/unit/TestAsset/TestSql92Platform.php index 8b66369dc..b94f3f79f 100644 --- a/test/unit/TestAsset/TestSql92Platform.php +++ b/test/unit/TestAsset/TestSql92Platform.php @@ -11,7 +11,6 @@ class TestSql92Platform extends Sql92 { public function __construct( protected ?DriverInterface $driver = null, - protected bool $quoteIdentifiers = true - ) { - } + protected bool $quoteIdentifiers = true, + ) {} } diff --git a/test/unit/TestAsset/TrustingMysqlPlatform.php b/test/unit/TestAsset/TrustingMysqlPlatform.php index b745cae68..25d33f320 100644 --- a/test/unit/TestAsset/TrustingMysqlPlatform.php +++ b/test/unit/TestAsset/TrustingMysqlPlatform.php @@ -12,18 +12,18 @@ final class TrustingMysqlPlatform extends Sql92 /** @var array{string, string} */ protected array $quoteIdentifier = ['`', '`']; - /** - * @param string $value - */ #[Override] - public function quoteValue($value): string + public function getName(): string { - return "'" . $value . "'"; + return 'mysql'; } + /** + * @param string $value + */ #[Override] - public function getName(): string + public function quoteValue($value): string { - return 'mysql'; + return "'{$value}'"; } } diff --git a/test/unit/TestAsset/TrustingOraclePlatform.php b/test/unit/TestAsset/TrustingOraclePlatform.php index 47ca1447d..2fa40679e 100644 --- a/test/unit/TestAsset/TrustingOraclePlatform.php +++ b/test/unit/TestAsset/TrustingOraclePlatform.php @@ -9,18 +9,18 @@ final class TrustingOraclePlatform extends Sql92 { - /** - * @param string $value - */ #[Override] - public function quoteValue($value): string + public function getName(): string { - return "'" . $value . "'"; + return 'oracle'; } + /** + * @param string $value + */ #[Override] - public function getName(): string + public function quoteValue($value): string { - return 'oracle'; + return "'{$value}'"; } } diff --git a/test/unit/TestAsset/TrustingSqlServerPlatform.php b/test/unit/TestAsset/TrustingSqlServerPlatform.php index 9054d51da..c202659ac 100644 --- a/test/unit/TestAsset/TrustingSqlServerPlatform.php +++ b/test/unit/TestAsset/TrustingSqlServerPlatform.php @@ -12,18 +12,18 @@ final class TrustingSqlServerPlatform extends Sql92 /** @var array{string, string} */ protected array $quoteIdentifier = ['[', ']']; - /** - * @param string $value - */ #[Override] - public function quoteValue($value): string + public function getName(): string { - return "'" . $value . "'"; + return 'sqlserver'; } + /** + * @param string $value + */ #[Override] - public function getName(): string + public function quoteValue($value): string { - return 'sqlserver'; + return "'{$value}'"; } } diff --git a/test/unit/TestAsset/UpdateDecorator.php b/test/unit/TestAsset/UpdateDecorator.php index d5c01bba9..18cfaf276 100644 --- a/test/unit/TestAsset/UpdateDecorator.php +++ b/test/unit/TestAsset/UpdateDecorator.php @@ -14,8 +14,8 @@ final class UpdateDecorator extends Sql\Update implements Sql\Platform\PlatformD * @return $this Provides a fluent interface */ public function setSubject( - Sql\SqlInterface|Sql\PreparableSqlInterface|null $subject - ): UpdateDecorator { + Sql\SqlInterface|Sql\PreparableSqlInterface|null $subject, + ): self { $this->subject = $subject; return $this; } diff --git a/test/unit/TestAsset/UpdateIgnore.php b/test/unit/TestAsset/UpdateIgnore.php index 9e11d4d4f..0f2ca17d6 100644 --- a/test/unit/TestAsset/UpdateIgnore.php +++ b/test/unit/TestAsset/UpdateIgnore.php @@ -31,7 +31,7 @@ class UpdateIgnore extends Update protected function processupdateIgnore( PlatformInterface $platform, ?DriverInterface $driver = null, - ?ParameterContainer $parameterContainer = null + ?ParameterContainer $parameterContainer = null, ): string { return parent::processUpdate($platform, $driver, $parameterContainer); }