diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2be2a59..785bb63 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,9 +22,29 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} - tools: phpunit - name: Run tests - phpunit run: | composer install ./vendor/bin/phpunit --configuration phpunit.xml.dist tests/Unit + + + static-analysis: + name: Static Analysis + runs-on: ubuntu-22.04 + strategy: + matrix: + php-versions: [ '8.0', '8.1', '8.2', '8.3' ] + + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + + - name: Run tests - phpunit + run: | + composer install + ./vendor/bin/phpstan analyse --configuration phpstan.neon src diff --git a/composer.json b/composer.json index 88a744c..4c0040f 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ }, "require-dev": { "phpunit/phpunit": "^9.6|^10.5|^11.3", - "phpunit/php-code-coverage": "^9.2|^10.1|^11.0" + "phpunit/php-code-coverage": "^9.2|^10.1|^11.0", + "phpstan/phpstan": "^1.12" }, "autoload": { "psr-4": { diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..91916fe --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 9 + paths: + - src + phpVersion: 80200 diff --git a/src/ParsedQuery.php b/src/ParsedQuery.php index e8e5e78..9661485 100644 --- a/src/ParsedQuery.php +++ b/src/ParsedQuery.php @@ -1,4 +1,5 @@ hash = sha1($query); } - public function isEqualTo($query) + public function isEqualTo(ParsedQuery|string $query): bool { if (!($query instanceof self)) { $query = new self($query); diff --git a/src/Pdo.php b/src/Pdo.php index c016927..be4841e 100644 --- a/src/Pdo.php +++ b/src/Pdo.php @@ -13,26 +13,30 @@ class Pdo extends \PDO private QueryLog $queryLog; /** - * @param ResultCollection|null $collection + * @param ResultCollection|null $collection */ public function __construct( ResultCollection $collection = null ) { $this->mockedQueries = $collection ?? new ResultCollection(); - $this->queryLog = new QueryLog(); + $this->queryLog = new QueryLog(); } /** - * @throws PseudoException|Throwable + * @param string $query + * @param array $options + * @return PdoStatement + * @throws PseudoException + * @throws Throwable */ - public function prepare($query, $options = null) : PdoStatement + public function prepare(string $query, array $options = []): PdoStatement { $result = $this->mockedQueries->getResult($query); return new PdoStatement($result, $this->queryLog, $query); } - public function beginTransaction() : bool + public function beginTransaction(): bool { if (!$this->inTransaction) { $this->inTransaction = true; @@ -43,7 +47,7 @@ public function beginTransaction() : bool return false; } - public function commit() : bool + public function commit(): bool { if ($this->inTransaction()) { $this->inTransaction = false; @@ -54,7 +58,7 @@ public function commit() : bool return false; } - public function rollBack() : bool + public function rollBack(): bool { if ($this->inTransaction()) { $this->inTransaction = false; @@ -65,12 +69,12 @@ public function rollBack() : bool return false; } - public function inTransaction() : bool + public function inTransaction(): bool { return $this->inTransaction; } - public function exec($statement) : false|int + public function exec($statement): false|int { $result = $this->query($statement); @@ -78,14 +82,14 @@ public function exec($statement) : false|int } /** - * @param string $query - * @param int|null $fetchMode - * @param mixed ...$fetchModeArgs + * @param string $query + * @param int|null $fetchMode + * @param mixed ...$fetchModeArgs * * @return PdoStatement * @throws PseudoException|Throwable */ - public function query(string $query, ?int $fetchMode = null, mixed ...$fetchModeArgs) : PdoStatement + public function query(string $query, ?int $fetchMode = null, mixed ...$fetchModeArgs): PdoStatement { if ($this->mockedQueries->exists($query)) { $result = $this->mockedQueries->getResult($query); @@ -101,27 +105,28 @@ public function query(string $query, ?int $fetchMode = null, mixed ...$fetchMode } /** - * @param null $name + * @param string|null $name * - * @return false|string + * @return string|false * @throws PseudoException + * @throws Throwable */ - public function lastInsertId($name = null) : false|string + public function lastInsertId(?string $name = null): string|false { - $result = $this->getLastResult(); + $lastResult = $this->getLastResult(); - if (!$result) { + if (is_bool($lastResult)) { return false; } - return $result->getInsertId(); + return (string) $lastResult->getInsertId(); } /** - * @return Result|false + * @return Result|bool * @throws PseudoException|Throwable */ - private function getLastResult() : Result|false + private function getLastResult(): Result|bool { try { $lastQuery = $this->queryLog[count($this->queryLog) - 1]; @@ -133,27 +138,37 @@ private function getLastResult() : Result|false } /** - * @param string $filePath + * @param string $filePath */ - public function save(string $filePath) : void + public function save(string $filePath): void { file_put_contents($filePath, serialize($this->mockedQueries)); } /** - * @param $filePath + * @param string $filePath + * @throws PseudoException */ - public function load($filePath) : void + public function load(string $filePath): void { - $this->mockedQueries = unserialize(file_get_contents($filePath)); + $fileContents = file_get_contents($filePath); + + if ($fileContents === false) { + throw new PseudoException('Unable to read file: ' . $filePath); + } + + /** @var ResultCollection $resultCollection */ + $resultCollection = unserialize($fileContents); + + $this->mockedQueries = $resultCollection; } /** * @param string $sql - * @param array|null $params + * @param array|null $params * @param mixed $expectedResults */ - public function mock(string $sql, ?array $params = null, mixed $expectedResults = null) : void + public function mock(string $sql, ?array $params = null, mixed $expectedResults = null): void { $this->mockedQueries->addQuery($sql, $params, $expectedResults); } @@ -161,7 +176,7 @@ public function mock(string $sql, ?array $params = null, mixed $expectedResults /** * @return ResultCollection */ - public function getMockedQueries() : ResultCollection + public function getMockedQueries(): ResultCollection { return $this->mockedQueries; } diff --git a/src/PdoStatement.php b/src/PdoStatement.php index dd787d6..c52fd99 100644 --- a/src/PdoStatement.php +++ b/src/PdoStatement.php @@ -4,20 +4,22 @@ use ArrayIterator; use Iterator; +use Pseudo\Exceptions\LogicException; use Pseudo\Exceptions\PseudoException; use ReflectionClass; use ReflectionException; -use stdClass; class PdoStatement extends \PDOStatement { - + private Result $result; + private int $fetchMode = \PDO::FETCH_BOTH; //DEFAULT FETCHMODE /** - * @var Result; + * @var array */ - private Result $result; - private int $fetchMode = PDO::FETCH_BOTH; //DEFAULT FETCHMODE private array $boundParams = []; + /** + * @var array + */ private array $boundColumns = []; /** @@ -25,14 +27,14 @@ class PdoStatement extends \PDOStatement */ private QueryLog $queryLog; - private ?string $statement; + private string $statement; /** - * @param null $result + * @param mixed $result * @param QueryLog|null $queryLog - * @param null $statement + * @param string $statement */ - public function __construct($result = null, QueryLog $queryLog = null, $statement = null) + public function __construct(mixed $result = null, QueryLog $queryLog = null, string $statement = '') { if (!($result instanceof Result)) { $result = new Result(); @@ -45,17 +47,22 @@ public function __construct($result = null, QueryLog $queryLog = null, $statemen $this->statement = $statement; } - public function setResult(Result $result) : void + public function setResult(Result|bool $result): void { + if (is_bool($result)) { + return; + } + $this->result = $result; } /** - * @param array|null $params + * @param array|null $params * * @return bool + * @throws LogicException */ - public function execute(array $params = null) : bool + public function execute(array $params = null): bool { $params = array_merge((array)$params, $this->boundParams); $this->result->setParams($params, !empty($this->boundParams)); @@ -77,12 +84,12 @@ public function execute(array $params = null) : bool return $success; } - public function fetch($mode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) : mixed + public function fetch($mode = null, $cursorOrientation = \PDO::FETCH_ORI_NEXT, $cursorOffset = 0): mixed { // scrolling cursors not implemented $row = $this->result->nextRow(); if ($row) { - return $this->proccessFetchedRow($row, $mode); + return $this->processFetchedRow($row, $mode); } return false; @@ -91,62 +98,82 @@ public function fetch($mode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $c public function bindParam( $param, &$var, - $type = PDO::PARAM_STR, + $type = \PDO::PARAM_STR, $maxLength = null, $driverOptions = null - ) : bool { + ): bool { $this->boundParams[$param] =& $var; return true; } - public function bindColumn($column, &$var, $type = null, $maxLength = null, $driverOptions = null) : bool + public function bindColumn($column, &$var, $type = null, $maxLength = null, $driverOptions = null): bool { $this->boundColumns[$column] =& $var; return true; } - public function bindValue($param, $value, $type = PDO::PARAM_STR) : bool + public function bindValue($param, $value, $type = \PDO::PARAM_STR): bool { $this->boundParams[$param] = $value; return true; } - public function rowCount() : int + public function rowCount(): int { return $this->result->getAffectedRowCount(); } - public function fetchColumn($column = 0) : mixed + public function fetchColumn($column = 0): mixed { $row = $this->result->nextRow(); - if ($row) { - $row = $this->proccessFetchedRow($row, PDO::FETCH_NUM); + if (is_array($row)) { + $row = $this->processFetchedRow($row, \PDO::FETCH_NUM); - return $row[$column]; + if (is_array($row)) { + return $row[$column]; + } } return false; } - public function fetchAll(int $mode = PDO::FETCH_DEFAULT, mixed ...$args) : array + /** + * @param int $mode + * @param mixed ...$args + * + * @return array + * @throws PseudoException + */ + public function fetchAll(int $mode = \PDO::FETCH_DEFAULT, mixed ...$args): array { - $rows = $this->result->getRows() ?? []; - $returnArray = []; - foreach ($rows as $row) { - $returnArray[] = $this->proccessFetchedRow($row, $mode); + $rows = $this->result->getRows() ?? []; + + if (is_array($rows)) { + return array_map( + function ($row) use ($mode) { + return $this->processFetchedRow($row, $mode); + }, + $rows + ); } - return $returnArray; + return []; } - private function proccessFetchedRow($row, $fetchMode) : mixed + /** + * @param array $row + * @param int|null $fetchMode + * + * @return mixed + */ + private function processFetchedRow(array $row, ?int $fetchMode): mixed { $i = 0; switch ($fetchMode ?: $this->fetchMode) { - case PDO::FETCH_BOTH: + case \PDO::FETCH_BOTH: $returnRow = []; $keys = array_keys($row); $c = 0; @@ -156,13 +183,13 @@ private function proccessFetchedRow($row, $fetchMode) : mixed } return $returnRow; - case PDO::FETCH_ASSOC: + case \PDO::FETCH_ASSOC: return $row; - case PDO::FETCH_NUM: + case \PDO::FETCH_NUM: return array_values($row); - case PDO::FETCH_OBJ: + case \PDO::FETCH_OBJ: return (object)$row; - case PDO::FETCH_BOUND: + case \PDO::FETCH_BOUND: if ($this->result->isOrdinalArray($this->boundColumns)) { foreach ($this->boundColumns as &$column) { $column = array_values($row)[++$i]; @@ -174,7 +201,7 @@ private function proccessFetchedRow($row, $fetchMode) : mixed } return true; - case PDO::FETCH_COLUMN: + case \PDO::FETCH_COLUMN: $returnRow = array_values($row); return $returnRow[0]; @@ -184,18 +211,22 @@ private function proccessFetchedRow($row, $fetchMode) : mixed } /** - * @param string $class - * @param null $constructorArgs + * @param class-string|null $class + * @param array $constructorArgs * - * @return bool|mixed + * @return object|false * @throws ReflectionException */ - public function fetchObject($class = stdClass::class, $constructorArgs = null) : object|false + public function fetchObject(?string $class = "stdClass", array $constructorArgs = []): object|false { + if (is_null($class)) { + return false; + } + $row = $this->result->nextRow(); if ($row) { $reflect = new ReflectionClass($class); - $obj = $reflect->newInstanceArgs($constructorArgs ?: []); + $obj = $reflect->newInstanceArgs($constructorArgs); foreach ($row as $key => $val) { $obj->$key = $val; } @@ -209,15 +240,15 @@ public function fetchObject($class = stdClass::class, $constructorArgs = null) : /** * @return string|null */ - public function errorCode() : ?string + public function errorCode(): ?string { return $this->result->getErrorCode(); } /** - * @return array + * @return array */ - public function errorInfo() : array + public function errorInfo(): array { return [$this->result->getErrorInfo()]; } @@ -226,13 +257,15 @@ public function errorInfo() : array * @return int * @throws PseudoException */ - public function columnCount() : int + public function columnCount(): int { $rows = $this->result->getRows(); - if ($rows) { + if (is_array($rows)) { $row = array_shift($rows); - return count(array_keys($row)); + if (is_array($row)) { + return count(array_keys($row)); + } } return 0; @@ -245,7 +278,7 @@ public function columnCount() : int * * @return bool|int */ - public function setFetchMode($mode, $className = null, ...$params) : bool|int + public function setFetchMode($mode, $className = null, ...$params): bool|int { $r = new ReflectionClass(new Pdo()); $constants = $r->getConstants(); @@ -267,12 +300,19 @@ public function setFetchMode($mode, $className = null, ...$params) : bool|int return false; } - public function getBoundParams() : array + /** + * @return array + */ + public function getBoundParams(): array { return $this->boundParams; } - public function getIterator() : Iterator + /** + * @return Iterator + * @throws PseudoException + */ + public function getIterator(): Iterator { return new ArrayIterator($this->fetchAll()); } diff --git a/src/QueryLog.php b/src/QueryLog.php index a59bcf5..4146dc7 100644 --- a/src/QueryLog.php +++ b/src/QueryLog.php @@ -9,9 +9,16 @@ use IteratorAggregate; use Traversable; +/** + * @implements IteratorAggregate + * @implements ArrayAccess + */ class QueryLog implements IteratorAggregate, ArrayAccess, Countable { - private $queries = []; + /** + * @var array + */ + private array $queries = []; public function count(): int { @@ -28,7 +35,7 @@ public function offsetExists($offset): bool return isset($this->queries[$offset]); } - public function offsetGet($offset): mixed + public function offsetGet(mixed $offset): ParsedQuery { if (!$this->offsetExists($offset)) { throw new InvalidArgumentException("Offset $offset does not exist"); @@ -47,11 +54,14 @@ public function offsetUnset($offset): void unset($this->queries[$offset]); } - public function addQuery($sql): void + public function addQuery(string $sql): void { $this->queries[] = new ParsedQuery($sql); } + /** + * @return array + */ public function getQueries(): array { return $this->queries; diff --git a/src/Result.php b/src/Result.php index 9725828..c6490bf 100644 --- a/src/Result.php +++ b/src/Result.php @@ -7,22 +7,33 @@ class Result { + /** + * @var array> + */ private array $rows = []; - private ?bool $executionResult = null; + private ?bool $executionResult; private bool $isParameterized = false; private string $errorCode; private string $errorInfo; private int $affectedRowCount = 0; private int $insertId = 0; private int $rowOffset = 0; + /** + * @var array + */ private array $params = []; - public function __construct($rows = null, $params = null, $executionResult = null) + /** + * @param array>|null $rows + * @param array|null $params + * @param bool|null $executionResult + */ + public function __construct(?array $rows = null, ?array $params = null, ?bool $executionResult = null) { if (is_array($rows)) { if ($params) { $this->rows[$this->stringifyParameterSet($params)] = $rows; - $this->isParameterized = true; + $this->isParameterized = true; } else { $this->rows = $rows; } @@ -32,9 +43,13 @@ public function __construct($rows = null, $params = null, $executionResult = nul } /** + * @param array $row + * @param array $params + * + * @return void * @throws PseudoException */ - public function addRow(array $row, $params = null): void + public function addRow(array $row, ?array $params = null): void { if (empty($row)) { return; @@ -53,7 +68,13 @@ public function addRow(array $row, $params = null): void } } - public function setParams($params, bool $parameterize = false): void + /** + * @param array $params + * @param bool $parameterize + * + * @return void + */ + public function setParams(array $params, bool $parameterize = false): void { $this->params = $params; if ($parameterize) { @@ -61,7 +82,13 @@ public function setParams($params, bool $parameterize = false): void } } - public function getRows(array $params = []) + /** + * @param array $params + * + * @return mixed + * @throws PseudoException + */ + public function getRows(array $params = []): mixed { if (!empty($this->params) && empty($params)) { $params = $this->params; @@ -88,9 +115,9 @@ public function getRows(array $params = []) /** * Returns the next row if it exists, otherwise returns false * - * @param array $rows Rows to get row from + * @param array $rows Rows to get row from * - * @return false|array Next row (false if doesn't exist) + * @return false|array Next row (false if it doesn't exist) */ private function getRowIfExists(array $rows): false|array { @@ -98,13 +125,16 @@ private function getRowIfExists(array $rows): false|array return false; } - return $rows[$this->rowOffset]; + /** @var array $row */ + $row = $rows[$this->rowOffset]; + + return $row; } /** * Returns the next available row if it exists, otherwise returns false * - * @return false|array + * @return false|array */ public function nextRow(): false|array { @@ -126,7 +156,7 @@ public function nextRow(): false|array } - public function setInsertId($insertId): void + public function setInsertId(int $insertId): void { $this->insertId = $insertId; } @@ -137,11 +167,11 @@ public function getInsertId(): int } /** - * @param $errorCode + * @param string $errorCode * * @throws PseudoException */ - public function setErrorCode($errorCode): void + public function setErrorCode(string $errorCode): void { if (ctype_alnum($errorCode) && strlen($errorCode) == 5) { $this->errorCode = $errorCode; @@ -159,9 +189,9 @@ public function getErrorCode(): string } /** - * @param $errorInfo + * @param string $errorInfo */ - public function setErrorInfo($errorInfo): void + public function setErrorInfo(string $errorInfo): void { $this->errorInfo = $errorInfo; } @@ -174,7 +204,7 @@ public function getErrorInfo(): string return $this->errorInfo; } - public function setAffectedRowCount($affectedRowCount): void + public function setAffectedRowCount(int $affectedRowCount): void { $this->affectedRowCount = $affectedRowCount; } @@ -184,6 +214,11 @@ public function getAffectedRowCount(): int return $this->affectedRowCount; } + /** + * @param array $arr + * + * @return bool + */ public function isOrdinalArray(array $arr): bool { return !(is_string(key($arr))); @@ -215,6 +250,11 @@ public function getExecutionResult(): bool return $this->executionResult; } + /** + * @param array $params + * + * @return string + */ private function stringifyParameterSet(array $params): string { if ($this->isOrdinalArray($params)) { @@ -230,15 +270,24 @@ private function stringifyParameterSet(array $params): string } } + /** + * @param string $parameterKey + * @param array $row + * + * @return void + */ private function initializeParameterizedRows(string $parameterKey, array $row): void { if (empty($this->rows)) { $this->rows[$parameterKey][] = $row; - $this->isParameterized = true; + $this->isParameterized = true; } } /** + * @param array $row + * + * @return void * @throws PseudoException */ private function addNonParameterizedRow(array $row): void diff --git a/src/ResultCollection.php b/src/ResultCollection.php index 40675d6..f2bf534 100644 --- a/src/ResultCollection.php +++ b/src/ResultCollection.php @@ -8,6 +8,9 @@ class ResultCollection implements Countable { + /** + * @var array + */ private array $queries = []; public function count(): int @@ -15,6 +18,12 @@ public function count(): int return count($this->queries); } + /** + * @param string $sql + * @param array|null $params + * @param mixed|null $results + * @return void + */ public function addQuery(string $sql, ?array $params = null, mixed $results = null): void { $query = new ParsedQuery($sql); @@ -34,7 +43,7 @@ public function addQuery(string $sql, ?array $params = null, mixed $results = nu $this->queries[$query->getHash()] = $storedResults; } - public function exists($sql): bool + public function exists(string $sql): bool { $query = new ParsedQuery($sql); return isset($this->queries[$query->getHash()]);