diff --git a/phpstan.neon b/phpstan.neon index eea61ca0..e697482b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,9 +1,8 @@ includes: - - phar://phpstan.phar/conf/bleedingEdge.neon - phpstan-baseline.neon parameters: - level: 6 + level: 7 paths: - src - tests diff --git a/src/Connection.php b/src/Connection.php index bdd94b65..0035bc12 100755 --- a/src/Connection.php +++ b/src/Connection.php @@ -28,6 +28,7 @@ use InvalidArgumentException; use Override; use Psr\Log\LoggerInterface; +use RuntimeException; /** * This class handles all traffic from and to the database and takes care of a correct tx-handling @@ -155,6 +156,10 @@ public function multiInsert(string $tableName, array $columns, array $valueSets, $output = true; $setsPerInsert = (int) floor(970 / count($columns)); + if ($setsPerInsert < 1) { + throw new RuntimeException('At least one set per insert operation must be performed.'); + } + foreach (array_chunk($valueSets, $setsPerInsert) as $valueSet) { $output = $output && $this->dbDriver->triggerMultiInsert( $tableName, @@ -1238,13 +1243,11 @@ public function getCacheSize(): int * An internal wrapper to dbsafeString, used to process a complete array of parameters * as used by prepared statements. * - * @template TKey of array-key - * - * @param array $params + * @param array $params * @param list|false $escapes An array of boolean for each param, used to block the escaping of html-special chars. * If not passed, all params will be cleaned. * - * @return array + * @return list * * @see Db::dbsafeString($string, $htmlSpecialChars = true) */ @@ -1275,7 +1278,7 @@ private function dbsafeParams(array $params, array | false $escapes = []): array $replace[$key] = $param; } - return $replace; + return array_values($replace); } /** diff --git a/src/Driver/MysqliDriver.php b/src/Driver/MysqliDriver.php index 0fed968b..7e1c56e1 100755 --- a/src/Driver/MysqliDriver.php +++ b/src/Driver/MysqliDriver.php @@ -160,7 +160,7 @@ public function _pQuery(string $query, array $params): bool throw new QueryException('Could not execute statement: ' . $this->getError(), $query, $params); } - $this->affectedRowsCount = $statement->affected_rows; + $this->affectedRowsCount = (int) $statement->affected_rows; $statement->free_result(); return $output; @@ -193,6 +193,10 @@ public function getPArray(string $query, array $params): Generator $result = $statement->get_result(); + if ($result === false) { + return; + } + while ($row = $result->fetch_assoc()) { yield $row; } diff --git a/src/Driver/PostgresDriver.php b/src/Driver/PostgresDriver.php index 709796f5..1c985f75 100755 --- a/src/Driver/PostgresDriver.php +++ b/src/Driver/PostgresDriver.php @@ -97,6 +97,8 @@ public function _pQuery(string $query, array $params): bool throw new QueryException('Could not prepare statement: ' . $this->getError(), $query, $params); } + $this->assertConnected(); + $result = pg_execute($this->linkDB, $name, $params); if ($result === false) { throw new QueryException('Could not execute statement: ' . $this->getError(), $query, $params); @@ -119,6 +121,8 @@ public function getPArray(string $query, array $params): Generator throw new QueryException('Could not prepare statement: ' . $this->getError(), $query, $params); } + $this->assertConnected(); + $resultSet = pg_execute($this->linkDB, $name, $params); if ($resultSet === false) { @@ -193,6 +197,8 @@ public function insertOrUpdate(string $table, array $columns, array $values, arr #[Override] public function getError(): string { + $this->assertConnected(); + return pg_last_error($this->linkDB); } @@ -486,6 +492,8 @@ public function transactionRollback(): void #[Override] public function getDbInfo(): array { + $this->assertConnected(); + return pg_version($this->linkDB); } @@ -626,6 +634,8 @@ private function getPreparedStatementName(string $query): false | string return $sum; } + $this->assertConnected(); + if (pg_prepare($this->linkDB, $sum, $query)) { $this->statementsCache[] = $sum; } else { @@ -690,4 +700,14 @@ public function getNthLastElementFromSlug(string $column, int $position): string { return "SPLIT_PART(REVERSE(SPLIT_PART(REVERSE($column), '/', $position)), '/', 1)"; } + + /** + * @phpstan-assert Connection $this->linkDB + */ + private function assertConnected(): void + { + if (!$this->linkDB instanceof Connection) { + throw new ConnectionException('Database not connected.'); + } + } } diff --git a/src/DriverFactory.php b/src/DriverFactory.php index e5407dc3..43670f5b 100644 --- a/src/DriverFactory.php +++ b/src/DriverFactory.php @@ -26,7 +26,7 @@ class DriverFactory public function factory(string $driver): DriverInterface { $class = 'Artemeon\\Database\\Driver\\' . ucfirst($driver) . 'Driver'; - if (!class_exists($class)) { + if (!class_exists($class) || !is_a($class, DriverInterface::class, true)) { throw new DriverNotFoundException('Configured driver ' . $class . ' does not exist'); } diff --git a/src/MockConnection.php b/src/MockConnection.php index 574ca753..eae6ac27 100644 --- a/src/MockConnection.php +++ b/src/MockConnection.php @@ -44,7 +44,7 @@ class MockConnection implements ConnectionInterface { /** - * @var list> + * @var list> */ private array $rows = []; @@ -76,13 +76,13 @@ public function getPArray(string $query, array $params = [], ?int $start = null, #[Override] public function getPRow(string $query, array $params = [], int $number = 0, bool $cache = true, array $escapes = []): array { - return current($this->rows); + return current($this->rows) ?: []; } #[Override] public function selectRow(string $tableName, array $columns, array $identifiers, bool $cached = true, ?array $escapes = []): ?array { - return current($this->rows); + return current($this->rows) ?: []; } #[Override] diff --git a/src/Schema/TableColumn.php b/src/Schema/TableColumn.php index a9b612cd..ccf8edb4 100644 --- a/src/Schema/TableColumn.php +++ b/src/Schema/TableColumn.php @@ -25,11 +25,17 @@ class TableColumn implements JsonSerializable private string $databaseType = ''; private bool $nullable = true; + /** + * @param non-empty-string $name + */ public static function make(string $name): self { return new self($name); } + /** + * @param non-empty-string $name + */ public function __construct(private string $name) { } @@ -38,7 +44,7 @@ public function __construct(private string $name) * @inheritDoc * * @return array{ - * name: string, + * name: non-empty-string, * internalType: string, * databaseType: string, * nullable: bool, @@ -67,11 +73,17 @@ public function setNullable(bool $nullable): self return $this; } + /** + * @return non-empty-string + */ public function getName(): string { return $this->name; } + /** + * @param non-empty-string $name + */ public function setName(string $name): self { $this->name = $name; diff --git a/tests/ConnectionTest.php b/tests/ConnectionTest.php index 826bb27d..7a733098 100644 --- a/tests/ConnectionTest.php +++ b/tests/ConnectionTest.php @@ -646,6 +646,10 @@ public function testIntComparison(string $id, int $date, int $expected): void { // note calculation does not work if we cross a year border. $objLeftDate = DateTime::createFromFormat('YmdHis', '' . $date); + if ($objLeftDate === false) { + self::fail('Invalid date given.'); + } + $objLeftDate->add(new DateInterval('P1M')); $left = $objLeftDate->format('YmdHis');