diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 4e660b208bd..4d42ad000e5 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -1,6 +1,14 @@ name: "Coding Standards" -on: ["pull_request", "push"] +on: + pull_request: + branches: + - "*.x" + - "master" + push: + branches: + - "*.x" + - "master" jobs: coding-standards: diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 696fb85d18c..13fc8597b08 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -1,77 +1,20 @@ name: "Continuous Integration" -on: ["pull_request", "push"] +on: + pull_request: + branches: + - "*.x" + - "master" + push: + branches: + - "*.x" + - "master" jobs: - static-analysis-phpstan: - name: "Static Analysis with PHPStan" - runs-on: "ubuntu-latest" - - strategy: - matrix: - php-version: - - "7.4" - - steps: - - name: "Checkout code" - uses: "actions/checkout@v2" - - - name: "Install PHP" - uses: "shivammathur/setup-php@v2" - with: - coverage: "none" - php-version: "${{ matrix.php-version }}" - tools: "cs2pr" - - - name: "Cache dependencies installed with composer" - uses: "actions/cache@v1" - with: - path: "~/.composer/cache" - key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" - restore-keys: "php-${{ matrix.php-version }}-composer-locked-" - - - name: "Install dependencies with composer" - run: "composer install --no-interaction --no-progress --no-suggest" - - - name: "Run a static analysis with phpstan/phpstan" - run: "vendor/bin/phpstan analyse --error-format=checkstyle | cs2pr" - - static-analysis-psalm: - name: "Static Analysis with Psalm" - runs-on: "ubuntu-latest" - - strategy: - matrix: - php-version: - - "7.4" - - steps: - - name: "Checkout code" - uses: "actions/checkout@v2" - - - name: "Install PHP" - uses: "shivammathur/setup-php@v2" - with: - coverage: "none" - php-version: "${{ matrix.php-version }}" - - - name: "Cache dependencies installed with composer" - uses: "actions/cache@v2" - with: - path: "~/.composer/cache" - key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" - restore-keys: "php-${{ matrix.php-version }}-composer-locked-" - - - name: "Install dependencies with composer" - run: "composer install --no-interaction --no-progress --no-suggest" - - - name: "Run a static analysis with vimeo/psalm" - run: "vendor/bin/psalm --show-info=false --stats --output-format=github --threads=4" - phpunit-oci8: name: "PHPUnit on OCI8" - runs-on: "ubuntu-latest" + runs-on: "ubuntu-20.04" strategy: matrix: @@ -115,7 +58,7 @@ jobs: phpunit-pdo-oci: name: "PHPUnit on PDO_OCI" - runs-on: "ubuntu-latest" + runs-on: "ubuntu-20.04" strategy: matrix: @@ -142,7 +85,7 @@ jobs: coverage: "pcov" - name: "Cache dependencies installed with composer" - uses: "actions/cache@v1" + uses: "actions/cache@v2" with: path: "~/.composer/cache" key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 00000000000..777c82f0ea8 --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,62 @@ + +name: "Static Analysis" + +on: + pull_request: + branches: + - "*.x" + - "master" + push: + branches: + - "*.x" + - "master" + +jobs: + static-analysis-phpstan: + name: "Static Analysis with PHPStan" + runs-on: "ubuntu-20.04" + + strategy: + matrix: + php-version: + - "7.4" + + steps: + - name: "Checkout code" + uses: "actions/checkout@v2" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "${{ matrix.php-version }}" + tools: "cs2pr" + + - name: "Cache dependencies installed with composer" + uses: "actions/cache@v2" + with: + path: "~/.composer/cache" + key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" + restore-keys: "php-${{ matrix.php-version }}-composer-locked-" + + - name: "Install dependencies with composer" + run: "composer install --no-interaction --no-progress --no-suggest" + + - name: "Run a static analysis with phpstan/phpstan" + run: "vendor/bin/phpstan analyse --error-format=checkstyle | cs2pr" + + static-analysis-psalm: + name: "Static Analysis with Psalm" + runs-on: "ubuntu-20.04" + + strategy: + matrix: + php-version: + - "7.4" + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Psalm + uses: docker://vimeo/psalm-github-actions diff --git a/docs/en/reference/data-retrieval-and-manipulation.rst b/docs/en/reference/data-retrieval-and-manipulation.rst index 0efba4c466c..7e85e3c3a82 100644 --- a/docs/en/reference/data-retrieval-and-manipulation.rst +++ b/docs/en/reference/data-retrieval-and-manipulation.rst @@ -398,6 +398,26 @@ Execute the query and fetch the first two columns into an associative array as k .. note:: All additional columns will be ignored and are only allowed to be selected by DBAL for its internal purposes. +fetchAllAssociativeIndexed() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Execute the query and fetch the data as an associative array where the key represents the first column and the value is +an associative array of the rest of the columns and their values: + +.. code-block:: php + + fetchAllAssociativeIndexed('SELECT id, username, password FROM user'); + + /* + array( + 1 => array( + 'username' => 'jwage', + 'password' => 'changeme', + ) + ) + */ + fetchNumeric() ~~~~~~~~~~~~~~ @@ -459,6 +479,19 @@ Execute the query and iterate over the first two columns as keys and values resp .. note:: All additional columns will be ignored and are only allowed to be selected by DBAL for its internal purposes. +iterateAssociativeIndexed() +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Execute the query and iterate over the result with the key representing the first column and the value being +an associative array of the rest of the columns and their values: + +.. code-block:: php + + iterateAssociativeIndexed('SELECT id, username, password FROM user') as $id => $data) { + // ... + } + delete() ~~~~~~~~~ diff --git a/src/Connection.php b/src/Connection.php index 6ba714db1ae..6195aa6bd59 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -833,6 +833,24 @@ public function fetchAllKeyValue(string $query, array $params = [], array $types return $this->executeQuery($query, $params, $types)->fetchAllKeyValue(); } + /** + * Prepares and executes an SQL query and returns the result as an associative array with the keys mapped + * to the first column and the values being an associative array representing the rest of the columns + * and their values. + * + * @param string $query SQL query + * @param array|array $params Query parameters + * @param array|array $types Parameter types + * + * @return array> + * + * @throws Exception + */ + public function fetchAllAssociativeIndexed(string $query, array $params = [], array $types = []): array + { + return $this->executeQuery($query, $params, $types)->fetchAllAssociativeIndexed(); + } + /** * Prepares and executes an SQL query and returns the result as an array of the first column values. * @@ -919,6 +937,24 @@ public function iterateKeyValue(string $query, array $params = [], array $types return $this->executeQuery($query, $params, $types)->iterateKeyValue(); } + /** + * Prepares and executes an SQL query and returns the result as an iterator with the keys mapped + * to the first column and the values being an associative array representing the rest of the columns + * and their values. + * + * @param string $query SQL query + * @param array|array $params Query parameters + * @param array|array $types Parameter types + * + * @return Traversable> + * + * @throws Exception + */ + public function iterateAssociativeIndexed(string $query, array $params = [], array $types = []): Traversable + { + return $this->executeQuery($query, $params, $types)->iterateAssociativeIndexed(); + } + /** * Prepares and executes an SQL query and returns the result as an iterator over the first column values. * diff --git a/src/Result.php b/src/Result.php index 7b7c6644bad..d983086a265 100644 --- a/src/Result.php +++ b/src/Result.php @@ -10,6 +10,7 @@ use LogicException; use Traversable; +use function array_shift; use function func_num_args; class Result @@ -129,6 +130,25 @@ public function fetchAllKeyValue(): array return $data; } + /** + * Returns an associative array with the keys mapped to the first column and the values being + * an associative array representing the rest of the columns and their values. + * + * @return array> + * + * @throws Exception + */ + public function fetchAllAssociativeIndexed(): array + { + $data = []; + + foreach ($this->fetchAllAssociative() as $row) { + $data[array_shift($row)] = $row; + } + + return $data; + } + /** * {@inheritDoc} * @@ -189,6 +209,21 @@ public function iterateKeyValue(): Traversable } } + /** + * Returns an iterator over the result set with the keys mapped to the first column and the values being + * an associative array representing the rest of the columns and their values. + * + * @return Traversable> + * + * @throws Exception + */ + public function iterateAssociativeIndexed(): Traversable + { + foreach ($this->iterateAssociative() as $row) { + yield array_shift($row) => $row; + } + } + /** * @return Traversable * diff --git a/tests/Functional/Connection/FetchTest.php b/tests/Functional/Connection/FetchTest.php index a6a5d341cf8..3fae6eb024a 100644 --- a/tests/Functional/Connection/FetchTest.php +++ b/tests/Functional/Connection/FetchTest.php @@ -114,6 +114,15 @@ public function testFetchAllKeyValueOneColumn(): void $this->connection->fetchAllKeyValue($sql); } + public function testFetchAllAssociativeIndexed(): void + { + self::assertEquals([ + 'foo' => ['b' => 1], + 'bar' => ['b' => 2], + 'baz' => ['b' => 3], + ], $this->connection->fetchAllAssociativeIndexed($this->query)); + } + public function testFetchFirstColumn(): void { self::assertEquals([ @@ -168,6 +177,15 @@ public function testIterateKeyValueOneColumn(): void iterator_to_array($this->connection->iterateKeyValue($sql)); } + public function testIterateAssociativeIndexed(): void + { + self::assertEquals([ + 'foo' => ['b' => 1], + 'bar' => ['b' => 2], + 'baz' => ['b' => 3], + ], iterator_to_array($this->connection->iterateAssociativeIndexed($this->query))); + } + public function testIterateColumn(): void { self::assertEquals([ diff --git a/tests/Platforms/AbstractPlatformTestCase.php b/tests/Platforms/AbstractPlatformTestCase.php index a75f2e8bcf4..95589f4c039 100644 --- a/tests/Platforms/AbstractPlatformTestCase.php +++ b/tests/Platforms/AbstractPlatformTestCase.php @@ -16,6 +16,7 @@ use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\Schema\UniqueConstraint; use Doctrine\DBAL\Types\Type; +use InvalidArgumentException; use PHPUnit\Framework\TestCase; use function get_class; @@ -84,7 +85,7 @@ public static function getReturnsForeignKeyReferentialActionSQL(): iterable public function testGetInvalidForeignKeyReferentialActionSQL(): void { - $this->expectException('InvalidArgumentException'); + $this->expectException(InvalidArgumentException::class); $this->platform->getForeignKeyReferentialActionSQL('unknown'); }