Skip to content

Commit

Permalink
Support PHP 8.4
Browse files Browse the repository at this point in the history
Also switched from Psalm to PHPStan.
  • Loading branch information
theodorejb committed Jan 26, 2025
1 parent 1614e43 commit 49607e8
Show file tree
Hide file tree
Showing 13 changed files with 70 additions and 72 deletions.
13 changes: 7 additions & 6 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:

strategy:
matrix:
php: [ '8.1', '8.2', '8.3' ]
php: [ '8.1', '8.2', '8.3', '8.4' ]

services:
mysql:
Expand Down Expand Up @@ -38,18 +38,19 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: psalm
extensions: sqlsrv-5.10.1

- name: Setup problem matchers for PHPUnit
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"

- name: Validate composer.json and composer.lock
run: composer validate --strict

- name: Install Composer dependencies
run: composer install --no-progress

- name: Run Psalm
run: psalm --output-format=github
if: ${{ matrix.php == '8.3' }}
- name: Perform static analysis
run: composer analyze -- --error-format=github
if: ${{ matrix.php == '8.4' }}

- name: Run PHPUnit
run: composer test-without-mssql
Expand Down
7 changes: 3 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.64",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^10.5",
"psalm/plugin-phpunit": "^0.19",
"ramsey/uuid": "^4.2.3",
"vimeo/psalm": "^5.26"
"ramsey/uuid": "^4.2.3"
},
"autoload": {
"psr-4": {
Expand All @@ -40,7 +39,7 @@
"sort-packages": true
},
"scripts": {
"analyze": "psalm",
"analyze": "phpstan analyze --level 10 src test",
"cs-fix": "php-cs-fixer fix -v",
"test": "phpunit",
"test-mssql": "phpunit --exclude-group mysql,pgsql",
Expand Down
22 changes: 0 additions & 22 deletions psalm.xml

This file was deleted.

5 changes: 4 additions & 1 deletion src/Options.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ public function __construct(
} elseif ($this->driver === 'pgsql') {
$this->binarySelectedAsStream = true;
$this->nativeBoolColumns = true;
$this->floatSelectedAsString = true;

if (PHP_VERSION_ID < 80_400) {
$this->floatSelectedAsString = true;
}
}
}
}
18 changes: 13 additions & 5 deletions src/PeachySql.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public function __construct(PDO $connection, ?Options $options = null)
public function begin(): void
{
if (!$this->conn->beginTransaction()) {
/** @phpstan-ignore argument.type */
throw $this->getError('Failed to begin transaction', $this->conn->errorInfo());
}
}
Expand All @@ -47,6 +48,7 @@ public function begin(): void
public function commit(): void
{
if (!$this->conn->commit()) {
/** @phpstan-ignore argument.type */
throw $this->getError('Failed to commit transaction', $this->conn->errorInfo());
}
}
Expand All @@ -58,6 +60,7 @@ public function commit(): void
public function rollback(): void
{
if (!$this->conn->rollback()) {
/** @phpstan-ignore argument.type */
throw $this->getError('Failed to roll back transaction', $this->conn->errorInfo());
}
}
Expand All @@ -72,10 +75,12 @@ final public function makeBinaryParam(?string $binaryStr): array
return [$binaryStr, PDO::PARAM_LOB, 0, $driverOptions];
}

/** @internal */
/**
* @param array{0: string, 1: int|null, 2: string|null} $error
* @internal
*/
public static function getError(string $message, array $error): SqlException
{
/** @var array{0: string, 1: int|null, 2: string|null} $error */
$code = $error[1] ?? 0;
$details = $error[2] ?? '';
$sqlState = $error[0];
Expand All @@ -84,18 +89,19 @@ public static function getError(string $message, array $error): SqlException
}

/**
* Returns a prepared statement which can be executed multiple times
* Returns a prepared statement which can be executed multiple times.
* @param list<mixed> $params
* @throws SqlException if an error occurs
*/
public function prepare(string $sql, array $params = []): Statement
{
try {
if (!$stmt = $this->conn->prepare($sql)) {
/** @phpstan-ignore argument.type */
throw $this->getError('Failed to prepare statement', $this->conn->errorInfo());
}

$i = 0;
/** @psalm-suppress MixedAssignment */
foreach ($params as &$param) {
$i++;

Expand All @@ -111,14 +117,16 @@ public function prepare(string $sql, array $params = []): Statement
}
}
} catch (\PDOException $e) {
/** @phpstan-ignore argument.type */
throw $this->getError('Failed to prepare statement', $this->conn->errorInfo());
}

return new Statement($stmt, $this->usedPrepare, $this->options);
}

/**
* Prepares and executes a single query with bound parameters
* Prepares and executes a single query with bound parameters.
* @param list<mixed> $params
*/
public function query(string $sql, array $params = []): Statement
{
Expand Down
18 changes: 6 additions & 12 deletions src/QueryBuilder/Insert.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public static function batchRows(array $colVals, int $maxBoundParams, int $maxRo
$maxRowsPerQuery = $maxRows;
}

/** @phpstan-ignore argument.type */
return array_chunk($colVals, $maxRowsPerQuery);
}

Expand All @@ -38,25 +39,18 @@ public static function batchRows(array $colVals, int $maxBoundParams, int $maxRo
*/
public function buildQuery(string $table, array $colVals): SqlParams
{
self::validateColValsStructure($colVals);
if (!$colVals || empty($colVals[0])) {
throw new \Exception('A valid array of columns/values to insert must be specified');
}

$columns = $this->escapeColumns(array_keys($colVals[0]));
$insert = "INSERT INTO {$table} (" . implode(', ', $columns) . ')';

$valSetStr = ' (' . str_repeat('?,', count($columns) - 1) . '?),';
$valStr = ' VALUES' . substr_replace(str_repeat($valSetStr, count($colVals)), '', -1); // remove trailing comma
$params = array_merge(...array_map('array_values', $colVals));
/** @phpstan-ignore argument.type */
$params = array_merge(...array_map(array_values(...), $colVals));

return new SqlParams($insert . $valStr, $params);
}

/**
* @throws \Exception if the column/values array does not have a valid structure
*/
private static function validateColValsStructure(array $colVals): void
{
if (empty($colVals[0]) || !is_array($colVals[0])) {
throw new \Exception('A valid array of columns/values to insert must be specified');
}
}
}
5 changes: 2 additions & 3 deletions src/QueryBuilder/Select.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
class Select extends Query
{
/**
* @param string[] $orderBy
* @throws \Exception if there is an invalid sort direction
*/
public function buildOrderByClause(array $orderBy): string
Expand All @@ -19,12 +20,10 @@ public function buildOrderByClause(array $orderBy): string
$sql = ' ORDER BY ';

// [column1, column2, ...]
if (isset($orderBy[0])) {
/** @var array<int, string> $orderBy */
if (array_is_list($orderBy)) {
return $sql . implode(', ', $this->escapeColumns($orderBy));
}

/** @var array<string, string> $orderBy */
// [column1 => direction, column2 => direction, ...]
foreach ($orderBy as $column => $direction) {
$column = $this->escapeIdentifier($column);
Expand Down
4 changes: 4 additions & 0 deletions src/QueryBuilder/Selector.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class Selector
{
/** @var WhereClause */
private array $where = [];
/**
* @var string[]
*/
private array $orderBy = [];
private ?int $limit = null;
private ?int $offset = null;
Expand All @@ -35,6 +38,7 @@ public function where(array $filter): static
}

/**
* @param string[] $sort
* @throws \Exception if called more than once
*/
public function orderBy(array $sort): static
Expand Down
1 change: 0 additions & 1 deletion src/QueryBuilder/Update.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ public function buildQuery(string $table, array $set, array $where): SqlParams
$params = [];
$sql = "UPDATE {$table} SET ";

/** @psalm-suppress MixedAssignment */
foreach ($set as $column => $value) {
$sql .= $this->escapeIdentifier($column) . ' = ?, ';
$params[] = $value;
Expand Down
17 changes: 10 additions & 7 deletions src/Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ public function execute(): void

try {
if (!$this->stmt->execute()) {
/** @phpstan-ignore argument.type */
throw PeachySql::getError('Failed to execute prepared statement', $this->stmt->errorInfo());
}
} catch (PDOException $e) {
/** @phpstan-ignore argument.type */
throw PeachySql::getError('Failed to execute prepared statement', $this->stmt->errorInfo());
}

Expand All @@ -61,15 +63,13 @@ public function execute(): void

/**
* Returns an iterator which can be used to loop through each row in the result
* @return \Generator<int, array>
* @return \Generator<int, mixed[]>
*/
public function getIterator(): \Generator
{
if ($this->stmt !== null) {
while (
/** @var array|false $row */
$row = $this->stmt->fetch(PDO::FETCH_ASSOC)
) {
while ($row = $this->stmt->fetch(PDO::FETCH_ASSOC)) {
/** @phpstan-ignore generator.valueType */
yield $row;
}

Expand All @@ -94,20 +94,23 @@ public function close(): void
}

/**
* Returns all rows selected by the query
* Returns all rows selected by the query.
* @return mixed[]
*/
public function getAll(): array
{
return iterator_to_array($this->getIterator());
}

/**
* Returns the first selected row, or null if zero rows were returned
* Returns the first selected row, or null if zero rows were returned.
* @return mixed[]|null
*/
public function getFirst(): ?array
{
$row = $this->getIterator()->current();

/** @phpstan-ignore notIdentical.alwaysTrue */
if ($row !== null) {
$this->close(); // don't leave the SQL statement open
}
Expand Down
29 changes: 19 additions & 10 deletions test/DbTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ public function testBlob(): void
$db = static::dbProvider();
$img = file_get_contents('test/DevTheorem.png');

if ($img === false) {
throw new \Exception('Failed to read image');
}

$id = $db->insertRow($this->table, [
'name' => 'DevTheorem',
'dob' => '2024-10-24',
Expand All @@ -122,7 +126,7 @@ public function testBlob(): void
->where(['user_id' => $id])->query()->getFirst();

if ($db->options->binarySelectedAsStream) {
/** @psalm-suppress PossiblyInvalidArgument */
/** @phpstan-ignore argument.type */
$row['photo'] = stream_get_contents($row['photo']);
}

Expand Down Expand Up @@ -153,6 +157,11 @@ public function testIteratorQuery(): void
$this->assertInstanceOf(\Generator::class, $iterator);
$colValsCompare = [];

/** @var array{
* user_id: int, name: string, dob: string, weight: string|float,
* is_disabled: int|bool, uuid: string|null|resource
* } $row
*/
foreach ($iterator as $row) {
unset($row['user_id']);

Expand All @@ -163,7 +172,7 @@ public function testIteratorQuery(): void
$row['is_disabled'] = (bool) $row['is_disabled'];
}
if ($options->binarySelectedAsStream && $row['uuid'] !== null) {
/** @psalm-suppress MixedArgument */
/** @phpstan-ignore argument.type */
$row['uuid'] = stream_get_contents($row['uuid']);
}

Expand All @@ -186,7 +195,6 @@ public function testIteratorQuery(): void
foreach ($realNames as $_row) {
$_id = $_row['user_id'];
$_name = $_row['name'];
/** @psalm-suppress MixedArrayAssignment */
$_uuid[0] = $_row['uuid'];
$stmt->execute();
}
Expand Down Expand Up @@ -250,14 +258,14 @@ public function testInsertBulk(): void
if ($options->binarySelectedAsStream || $options->nativeBoolColumns || $options->floatSelectedAsString) {
/** @var array{weight: float|string, is_disabled: int|bool, uuid: string|resource} $row */
foreach ($rows as &$row) {
if (!is_float($row['weight'])) {
if ($options->floatSelectedAsString) {
$row['weight'] = (float) $row['weight'];
}
if (!is_int($row['is_disabled'])) {
if ($options->nativeBoolColumns) {
$row['is_disabled'] = (int) $row['is_disabled'];
}
if (!is_string($row['uuid'])) {
/** @psalm-suppress InvalidArgument */
if ($options->binarySelectedAsStream) {
/** @phpstan-ignore argument.type */
$row['uuid'] = stream_get_contents($row['uuid']);
}
}
Expand All @@ -274,12 +282,13 @@ public function testInsertBulk(): void
$userId = $ids[0];
$set = ['uuid' => $peachySql->makeBinaryParam($newUuid)];
$peachySql->updateRows($this->table, $set, ['user_id' => $userId]);
/** @var array{uuid: string|resource} $updatedRow */
$updatedRow = $peachySql->selectFrom("SELECT uuid FROM {$this->table}")
->where(['user_id' => $userId])->query()->getFirst();

if (!is_string($updatedRow['uuid'])) {
$updatedRow['uuid'] = stream_get_contents($updatedRow['uuid']); // needed for PostgreSQL
if ($updatedRow === null) {
throw new \Exception('Failed to select updated UUID');
} elseif ($options->binarySelectedAsStream && is_resource($updatedRow['uuid'])) {
$updatedRow['uuid'] = stream_get_contents($updatedRow['uuid']);
}

$this->assertSame($newUuid, $updatedRow['uuid']);
Expand Down
Loading

0 comments on commit 49607e8

Please sign in to comment.