Even stricted PHPStan rules!

This commit is contained in:
flash 2024-08-04 00:14:17 +00:00
parent 310a665595
commit fbca708fbd
24 changed files with 178 additions and 103 deletions

View file

@ -1 +1 @@
0.2408.32219
0.2408.40014

View file

@ -1,4 +1,7 @@
parameters:
level: 9
checkUninitializedProperties: true
checkImplicitMixed: true
checkBenevolentUnionTypes: true
paths:
- src

View file

@ -1,7 +1,7 @@
<?php
// CSRFP.php
// Created: 2021-06-11
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index;
@ -74,10 +74,10 @@ class CSRFP {
return false;
$unpacked = unpack('Vts', $token);
if($unpacked === false)
if(!is_array($unpacked) || !isset($unpacked['ts']) || !is_int($unpacked['ts']))
return false;
$uTime = (int)($unpacked['ts'] ?? 0);
$uTime = $unpacked['ts'];
if($uTime < 0)
return false;

View file

@ -1,7 +1,7 @@
<?php
// ArrayCacheProvider.php
// Created: 2024-04-10
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index\Cache\ArrayCache;
@ -65,6 +65,9 @@ class ArrayCacheProvider implements ICacheProvider {
'ttl' => 0,
];
if(is_float($value))
$value = (int)$value;
return $value;
}

View file

@ -1,7 +1,7 @@
<?php
// CacheTools.php
// Created: 2024-04-10
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index\Cache;
@ -44,13 +44,14 @@ final class CacheTools {
/** @param array<string, int|string> $uri */
private static function resolveBackend(array $uri): ICacheBackend {
static $backends = [];
static $backends = null;
if(!is_array($backends))
$backends = [];
$scheme = $uri['scheme'];
$backend = $backends[$scheme] ?? null;
if(in_array($scheme, $backends))
$backend = $backends[$scheme];
else {
if(!($backend instanceof ICacheBackend)) {
$backend = null;
if(array_key_exists($scheme, self::CACHE_PROTOS))

View file

@ -1,7 +1,7 @@
<?php
// MemcachedBackend.php
// Created: 2024-04-10
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index\Cache\Memcached;
@ -106,6 +106,9 @@ class MemcachedBackend implements ICacheBackend {
if(is_array($query['server']))
foreach($query['server'] as $endPoint) {
if(!is_string($endPoint))
continue;
$parts = explode(';', $endPoint, 2);
$weight = count($parts) > 1 ? (int)$parts[1] : 0;
$endPoint = EndPoint::parse($parts[0]);

View file

@ -1,7 +1,7 @@
<?php
// ValkeyProvider.php
// Created: 2024-04-10
// Updated: 2024-04-10
// Updated: 2024-08-04
namespace Index\Cache\Valkey;
@ -63,11 +63,13 @@ class ValkeyProvider implements ICacheProvider {
}
public function increment(string $key, int $amount = 1): int {
return $this->redis->incrBy($key, $amount);
$result = $this->redis->incrBy($key, $amount);
return is_int($result) ? $result : 0;
}
public function decrement(string $key, int $amount = 1): int {
return $this->redis->decrBy($key, $amount);
$result = $this->redis->decrBy($key, $amount);
return is_int($result) ? $result : 0;
}
public function close(): void {

View file

@ -1,7 +1,7 @@
<?php
// DbResultIterator.php
// Created: 2024-02-06
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index\Data;
@ -14,7 +14,7 @@ use Iterator;
* @implements Iterator<int, object>
*/
class DbResultIterator implements Iterator {
private bool $wasValid;
private bool $wasValid = false;
private object $current;
/**
@ -29,6 +29,8 @@ class DbResultIterator implements Iterator {
) {
if(!is_callable($construct))
throw new InvalidArgumentException('$construct must be a callable.');
$this->current = (object)[];
}
public function current(): mixed {

View file

@ -1,7 +1,7 @@
<?php
// DbTools.php
// Created: 2021-05-02
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index\Data;
@ -43,13 +43,14 @@ final class DbTools {
/** @param array<string, string|int> $uri */
private static function resolveBackend(array $uri): IDbBackend {
static $backends = [];
static $backends = null;
if(!is_array($backends))
$backends = [];
$scheme = $uri['scheme'];
$backend = $backends[$scheme] ?? null;
if(in_array($scheme, $backends))
$backend = $backends[$scheme];
else {
if(!($backend instanceof IDbBackend)) {
$backend = null;
if(array_key_exists($scheme, self::DB_PROTOS))

View file

@ -1,7 +1,7 @@
<?php
// MariaDBCharacterSetInfo.php
// Created: 2021-05-02
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index\Data\MariaDB;
@ -27,7 +27,8 @@ class MariaDBCharacterSetInfo {
* @return string Character set name.
*/
public function getCharacterSet(): string {
return $this->charSet->charset ?? '';
return isset($this->charSet->charset) && is_scalar($this->charSet->charset)
? (string)$this->charSet->charset : '';
}
/**
@ -36,7 +37,8 @@ class MariaDBCharacterSetInfo {
* @return string Default collation name.
*/
public function getDefaultCollation(): string {
return $this->charSet->collation ?? '';
return isset($this->charSet->collation) && is_scalar($this->charSet->collation)
? (string)$this->charSet->collation : '';
}
/**
@ -46,7 +48,8 @@ class MariaDBCharacterSetInfo {
* @return string Source directory.
*/
public function getDirectory(): string {
return $this->charSet->dir ?? '';
return isset($this->charSet->dir) && is_scalar($this->charSet->dir)
? (string)$this->charSet->dir : '';
}
/**
@ -55,7 +58,8 @@ class MariaDBCharacterSetInfo {
* @return int Minimum character width in bytes.
*/
public function getMinimumWidth(): int {
return $this->charSet->min_length ?? 0;
return isset($this->charSet->min_length) && is_scalar($this->charSet->min_length)
? (int)$this->charSet->min_length : 0;
}
/**
@ -64,7 +68,8 @@ class MariaDBCharacterSetInfo {
* @return int Maximum character width in bytes.
*/
public function getMaximumWidth(): int {
return $this->charSet->max_length ?? 0;
return isset($this->charSet->max_length) && is_scalar($this->charSet->max_length)
? (int)$this->charSet->max_length : 0;
}
/**
@ -73,7 +78,8 @@ class MariaDBCharacterSetInfo {
* @return int Character set identifier.
*/
public function getId(): int {
return $this->charSet->number ?? 0;
return isset($this->charSet->number) && is_scalar($this->charSet->number)
? (int)$this->charSet->number : 0;
}
/**
@ -84,6 +90,7 @@ class MariaDBCharacterSetInfo {
* @return int Character set status.
*/
public function getState(): int {
return $this->charSet->state ?? 0;
return isset($this->charSet->state) && is_scalar($this->charSet->state)
? (int)$this->charSet->state : 0;
}
}

View file

@ -1,7 +1,7 @@
<?php
// MariaDBConnection.php
// Created: 2021-04-30
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index\Data\MariaDB;
@ -250,7 +250,10 @@ class MariaDBConnection implements IDbConnection, IDbTransactions {
* @return MariaDBWarning[] List of last errors.
*/
public function getLastErrors(): array {
return MariaDBWarning::fromLastErrors($this->connection->error_list);
// imagine if stdlib stuff had type annotations, couldn't be me
/** @var array<int, array{errno: int, sqlstate: string, error: string}> */
$errorList = $this->connection->error_list;
return MariaDBWarning::fromLastErrors($errorList);
}
/**

View file

@ -1,14 +1,15 @@
<?php
// DbMigrationManager.php
// Created: 2023-01-07
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index\Data\Migration;
use stdClass;
use InvalidArgumentException;
use DateTimeImmutable;
use DateTimeInterface;
use InvalidArgumentException;
use RuntimeException;
use Index\XDateTime;
use Index\Data\{IDbConnection,IDbStatement,DbType};
use Index\Data\SQLite\SQLiteConnection;
@ -44,8 +45,8 @@ final class %s implements IDbMigration {
EOF;
private IDbStatement $checkStmt;
private IDbStatement $insertStmt;
private ?IDbStatement $checkStmt = null;
private ?IDbStatement $insertStmt = null;
/**
* @param IDbConnection $conn Connection to apply to migrations to.
@ -94,9 +95,13 @@ EOF;
* Checks if a particular migration is present in the tracking table.
*
* @param string $name Name of the migration.
* @throws RuntimeException If the migration manager has not been initialised.
* @return bool true if the migration has been run, false otherwise.
*/
public function checkMigration(string $name): bool {
if($this->checkStmt === null)
throw new RuntimeException('Database migration manager has not been initialised.');
$this->checkStmt->reset();
$this->checkStmt->addParameter(1, $name, DbType::STRING);
$this->checkStmt->execute();
@ -109,8 +114,12 @@ EOF;
*
* @param string $name Name of the migration.
* @param ?DateTimeInterface $dateTime Timestamp of when the migration was run, null for now.
* @throws RuntimeException If the migration manager has not been initialised.
*/
public function completeMigration(string $name, ?DateTimeInterface $dateTime = null): void {
if($this->insertStmt === null)
throw new RuntimeException('Database migration manager has not been initialised.');
$dateTime = XDateTime::toISO8601String($dateTime);
$this->insertStmt->reset();

View file

@ -1,7 +1,7 @@
<?php
// SQLiteBackend.php
// Created: 2021-05-02
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index\Data\SQLite;
@ -23,7 +23,9 @@ class SQLiteBackend implements IDbBackend {
* @return string Version of the library.
*/
public function getVersion(): string {
return SQLite3::version()['versionString'];
$version = SQLite3::version();
return isset($version['versionString']) && is_scalar($version['versionString'])
? (string)$version['versionString'] : '';
}
/**

View file

@ -1,7 +1,7 @@
<?php
// FormContent.php
// Created: 2022-02-10
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index\Http\Content;
@ -110,7 +110,13 @@ class FormContent implements IHttpContent {
* @return FormContent Instance representing the request body.
*/
public static function fromRequest(): FormContent {
return self::fromRaw($_POST, $_FILES);
/** @var array<string, mixed> $postVars */
$postVars = $_POST;
/** @var array<string, mixed> $filesVars */
$filesVars = $_FILES;
return self::fromRaw($postVars, $filesVars);
}
public function __toString(): string {

View file

@ -1,7 +1,7 @@
<?php
// HttpRequest.php
// Created: 2022-02-08
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index\Http;
@ -136,11 +136,11 @@ class HttpRequest extends HttpMessage {
*/
public static function fromRequest(): HttpRequest {
$build = new HttpRequestBuilder;
$build->setHttpVersion($_SERVER['SERVER_PROTOCOL']);
$build->setMethod($_SERVER['REQUEST_METHOD']);
$build->setHttpVersion((string)filter_input(INPUT_SERVER, 'SERVER_PROTOCOL'));
$build->setMethod((string)filter_input(INPUT_SERVER, 'REQUEST_METHOD'));
// this currently doesn't "properly" support the scenario where a full url is specified in the http request
$path = $_SERVER['REQUEST_URI'];
$path = (string)filter_input(INPUT_SERVER, 'REQUEST_URI');
$pathQueryOffset = strpos($path, '?');
if($pathQueryOffset !== false)
$path = substr($path, 0, $pathQueryOffset);
@ -154,22 +154,27 @@ class HttpRequest extends HttpMessage {
$path = '/' . $path;
$build->setPath($path);
$build->setParams($_GET);
$build->setCookies($_COOKIE);
/** @var array<string, mixed> $getVars */
$getVars = $_GET;
$build->setParams($getVars);
/** @var array<string, string> $cookieVars */
$cookieVars = $_COOKIE;
$build->setCookies($cookieVars);
$contentType = null;
$contentLength = 0;
$headers = self::getRawRequestHeaders();
foreach($headers as $name => $value) {
$nameLower = strtolower($name);
if($nameLower === 'content-type')
if($name === 'content-type')
try {
$contentType = MediaType::parse($value);
} catch(InvalidArgumentException $ex) {
$contentType = null;
}
elseif($nameLower === 'content-length')
elseif($name === 'content-length')
$contentLength = (int)$value;
$build->setHeader($name, $value);
@ -189,29 +194,43 @@ class HttpRequest extends HttpMessage {
/** @return array<string, string> */
private static function getRawRequestHeaders(): array {
if(function_exists('getallheaders'))
return getallheaders();
if(function_exists('getallheaders')) {
$raw = getallheaders();
$headers = [];
foreach($raw as $name => $value)
if(is_string($name) && is_string($value))
$headers[strtolower($name)] = $value;
return $headers;
}
$headers = [];
foreach($_SERVER as $key => $value) {
if(!is_string($key) || !is_scalar($value))
continue;
if(substr($key, 0, 5) === 'HTTP_') {
$key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))));
$headers[$key] = $value;
$key = str_replace(' ', '-', strtolower(str_replace('_', ' ', substr($key, 5))));
$headers[$key] = (string)$value;
} elseif($key === 'CONTENT_TYPE') {
$headers['Content-Type'] = $value;
$headers['content-type'] = (string)$value;
} elseif($key === 'CONTENT_LENGTH') {
$headers['Content-Length'] = $value;
$headers['content-length'] = (string)$value;
}
}
if(!isset($headers['Authorization'])) {
if(isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
$headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
} elseif(isset($_SERVER['PHP_AUTH_USER'])) {
$headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . ($_SERVER['PHP_AUTH_PW'] ?? ''));
} elseif(isset($_SERVER['PHP_AUTH_DIGEST'])) {
$headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST'];
if(!isset($headers['authorization'])) {
if(filter_has_var(INPUT_SERVER, 'REDIRECT_HTTP_AUTHORIZATION')) {
$headers['authorization'] = (string)filter_input(INPUT_SERVER, 'REDIRECT_HTTP_AUTHORIZATION');
} elseif(filter_has_var(INPUT_SERVER, 'PHP_AUTH_USER')) {
$headers['authorization'] = sprintf('Basic %s', base64_encode(sprintf(
'%s:%s',
(string)filter_input(INPUT_SERVER, 'PHP_AUTH_USER'),
(string)filter_input(INPUT_SERVER, 'PHP_AUTH_PW')
)));
} elseif(filter_has_var(INPUT_SERVER, 'PHP_AUTH_DIGEST')) {
$headers['authorization'] = (string)filter_input(INPUT_SERVER, 'PHP_AUTH_DIGEST');
}
}

View file

@ -1,7 +1,7 @@
<?php
// HttpResponseBuilder.php
// Created: 2022-02-08
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index\Http;
@ -14,7 +14,7 @@ use Index\Performance\Timings;
*/
class HttpResponseBuilder extends HttpMessageBuilder {
private int $statusCode = -1;
private ?string $statusText;
private ?string $statusText = null;
/** @var string[] */
private array $vary = [];
@ -61,7 +61,7 @@ class HttpResponseBuilder extends HttpMessageBuilder {
* @param string $statusText Status text.
*/
public function setStatusText(string $statusText): void {
$this->statusText = (string)$statusText;
$this->statusText = $statusText;
}
/**

View file

@ -1,7 +1,7 @@
<?php
// HttpUploadedFile.php
// Created: 2022-02-10
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index\Http;
@ -200,6 +200,7 @@ class HttpUploadedFile implements ICloseable {
$key = "_{$key}";
if(is_array($val)) {
/** @var array<string, mixed> $val */
$arr[$key] = self::traverseFILES($val, $keyName);
} else {
$arr[$key][$keyName] = $val;
@ -230,8 +231,17 @@ class HttpUploadedFile implements ICloseable {
if(is_array($arr['error'])) {
$keys = array_keys($arr);
foreach($keys as $keyName)
$out[$key] = array_merge_recursive($out[$key] ?? [], self::traverseFILES($arr[$keyName], (string)$keyName));
foreach($keys as $keyName) {
$source = $arr[$keyName];
if(!is_array($source))
continue;
/** @var array<string, mixed> $mergeWith */
$mergeWith = $out[$key] ?? [];
/** @var array<string, mixed> $source */
$out[$key] = array_merge_recursive($mergeWith, self::traverseFILES($source, (string)$keyName));
}
continue;
}
}
@ -251,9 +261,13 @@ class HttpUploadedFile implements ICloseable {
continue;
$key = substr($key, 1);
$coll[$key] = isset($val['error'])
? self::createFromFILE($val)
: self::createObjectInstances($val);
if(isset($val['error']))
/** @var array<string, int|string> $val */
$coll[$key] = self::createFromFILE($val);
else
/** @var array<string, mixed> $val */
$coll[$key] = self::createObjectInstances($val);
}
return $coll;

View file

@ -1,7 +1,7 @@
<?php
// HttpRouter.php
// Created: 2024-03-28
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index\Http\Routing;
@ -15,7 +15,7 @@ use Index\Http\ErrorHandling\{HtmlErrorHandler,IErrorHandler,PlainErrorHandler};
class HttpRouter implements IRouter {
use RouterTrait;
/** @var object[] */
/** @var array{handler: callable, match?: string, prefix?: string}[] */
private array $middlewares = [];
/** @var array<string, array<string, callable>> */
@ -147,16 +147,16 @@ class HttpRouter implements IRouter {
* @param callable $handler Middleware handler.
*/
public function use(string $path, callable $handler): void {
$this->middlewares[] = $mwInfo = new stdClass;
$mwInfo->handler = $handler;
$mwInfo = [];
$mwInfo['handler'] = $handler;
$prepared = self::preparePath($path, true);
$mwInfo->dynamic = $prepared !== false;
if($mwInfo->dynamic)
$mwInfo->match = $prepared;
if($prepared === false)
$mwInfo['prefix'] = $path;
else
$mwInfo->prefix = $path;
$mwInfo['match'] = $prepared;
$this->middlewares[] = $mwInfo;
}
/**
@ -207,19 +207,19 @@ class HttpRouter implements IRouter {
$middlewares = [];
foreach($this->middlewares as $mwInfo) {
if($mwInfo->dynamic ?? false) {
if(preg_match($mwInfo->match ?? '', $path, $args) !== 1)
if(array_key_exists('match', $mwInfo)) {
if(preg_match($mwInfo['match'], $path, $args) !== 1)
continue;
array_shift($args);
} else {
if(!str_starts_with($path, $mwInfo->prefix ?? ''))
} elseif(array_key_exists('prefix', $mwInfo)) {
if(!str_starts_with($path, $mwInfo['prefix']))
continue;
$args = [];
}
} else continue;
$middlewares[] = [$mwInfo->handler ?? null, $args];
$middlewares[] = [$mwInfo['handler'], $args];
}
$methods = [];

View file

@ -1,7 +1,7 @@
<?php
// IntegerBaseConverter.php
// Created: 2024-07-31
// Updated: 2024-07-31
// Updated: 2024-08-04
namespace Index;
@ -95,6 +95,6 @@ class IntegerBaseConverter {
$output += $pos * ($fromBase ** ($length - $i));
}
return $output;
return (int)$output;
}
}

View file

@ -1,7 +1,7 @@
<?php
// PerformanceCounter.php
// Created: 2022-02-16
// Updated: 2024-08-01
// Updated: 2024-08-04
namespace Index\Performance;
@ -24,7 +24,7 @@ final class PerformanceCounter {
* @return int Ticks count.
*/
public static function getTicks(): int {
return hrtime(true);
return (int)hrtime(true);
}
/**

View file

@ -1,7 +1,7 @@
<?php
// Stopwatch.php
// Created: 2021-04-26
// Updated: 2024-08-01
// Updated: 2024-08-04
namespace Index\Performance;
@ -59,9 +59,9 @@ class Stopwatch {
/**
* Gets the number of elapsed milliseconds.
*
* @return int Number of elapsed milliseconds.
* @return int|float Number of elapsed milliseconds.
*/
public function getElapsedTime(): int {
public function getElapsedTime(): int|float {
return $this->getElapsedTicks() / $this->frequency;
}

View file

@ -1,7 +1,7 @@
<?php
// TimingPoint.php
// Created: 2022-02-16
// Updated: 2024-08-01
// Updated: 2024-08-04
namespace Index\Performance;
@ -45,7 +45,7 @@ class TimingPoint {
*
* @return int Duration time amount.
*/
public function getDurationTime(): int {
public function getDurationTime(): int|float {
return $this->duration / PerformanceCounter::getFrequency();
}

View file

@ -1,7 +1,7 @@
<?php
// Timings.php
// Created: 2022-02-16
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index\Performance;
@ -15,7 +15,7 @@ class Timings {
private int $lastLapTicks;
/** @var TimingPoint[] */
private array $laps;
private array $laps = [];
/**
* @param ?Stopwatch $sw Stopwatch to measure timing points from, null to start a new one.

View file

@ -1,7 +1,7 @@
<?php
// XArray.php
// Created: 2022-02-02
// Updated: 2024-08-03
// Updated: 2024-08-04
namespace Index;
@ -119,9 +119,9 @@ final class XArray {
* Retrieves the first key in a collection.
*
* @param mixed[] $iterable
* @return int|string|null
* @return mixed
*/
public static function firstKey(iterable $iterable): int|string|null {
public static function firstKey(iterable $iterable): mixed {
if(is_array($iterable))
return array_key_first($iterable);
@ -135,9 +135,9 @@ final class XArray {
* Retrieves the last key in a collection.
*
* @param mixed[] $iterable
* @return int|string|null
* @return mixed
*/
public static function lastKey(iterable $iterable): int|string|null {
public static function lastKey(iterable $iterable): mixed {
if(is_array($iterable))
return array_key_last($iterable);
@ -153,13 +153,13 @@ final class XArray {
* @param mixed[] $iterable
* @param mixed $value
* @param bool $strict
* @return int|string|false
* @return mixed
*/
public static function indexOf(
iterable $iterable,
mixed $value,
bool $strict = false
): int|string|false {
): mixed {
if(is_array($iterable))
return array_search($value, $iterable, $strict);