Add strict types and type hints across API

Apply strict_types and extensive type declarations throughout the API and models, improving type safety and error handling. Key changes: add declare(strict_types=1) to many files; convert properties, method parameters and return values to typed signatures (PDO, arrays, ints, strings, bools, nullables); switch exception handling to Throwable in index and Router; improve Router, controllers and model method signatures and nullability handling; refine file/image serving security checks and headers in ImageController; strengthen Database typing and initialization methods; return explicit types from BaseModel CRUD helpers and counting; update Media/Cast/Adult/Game/Console/Settings controllers and models to use typed methods, better validation, and clearer update/create return types. Also add AGENTS.md (agent skills index), update README with Swagger/OpenAPI usage instructions, and add /.windsurf to .gitignore. These changes aim to harden runtime correctness, make intended contracts explicit, and prepare the codebase for easier maintenance and static analysis.
This commit is contained in:
Lars Behrends
2026-04-16 16:40:31 +02:00
parent 728ca893b1
commit e38a6e1f7b
26 changed files with 545 additions and 419 deletions

View File

@@ -1,27 +1,29 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/MediaType.php';
require_once __DIR__ . '/../services/ImageHandler.php';
class Game extends MediaType {
private $imageHandler;
private $isUpdate = false;
private $mediaId = null;
public function __construct($pdo) {
private ImageHandler $imageHandler;
private bool $isUpdate = false;
private ?int $mediaId = null;
public function __construct(PDO $pdo) {
parent::__construct($pdo);
$this->imageHandler = new ImageHandler();
}
protected function getType() {
protected function getType(): string {
return 'Game';
}
protected function getTypeSpecificFields() {
protected function getTypeSpecificFields(): array {
return [];
}
protected function validateTypeSpecificFields($data) {
protected function validateTypeSpecificFields(array $data): array {
// Game spezifische Validierung
if (isset($data['hasIcon'])) {
$data['hasIcon'] = is_numeric($data['hasIcon']) ? (int)$data['hasIcon'] : 0;
@@ -47,11 +49,11 @@ class Game extends MediaType {
return $data;
}
/**
* Process poster field - convert base64 to file path
*/
protected function processImageField($data, $type) {
protected function processImageField(array $data, string $type): array {
if ($this->isUpdate && $this->mediaId && isset($data[$type]) && !empty($data[$type])) {
$currentMedia = $this->findById($this->mediaId);
if ($currentMedia && !empty($currentMedia[$type])) {
@@ -73,7 +75,7 @@ class Game extends MediaType {
return $data;
}
public function createWithRelations($data) {
public function createWithRelations(array $data): int {
// Typ setzen
$data['type'] = 'Game';
@@ -152,7 +154,7 @@ class Game extends MediaType {
return $mediaId;
}
public function updateWithRelations($id, $data) {
public function updateWithRelations(int $id, array $data): bool {
// Set update flag and mediaId for image replacement
$this->isUpdate = true;
$this->mediaId = $id;
@@ -236,7 +238,7 @@ class Game extends MediaType {
return true;
}
public function getWithGameInfo($id) {
public function getWithGameInfo(?int $id): ?array {
$media = parent::getWithRelations($id);
if (!$media) {
return null;
@@ -264,7 +266,7 @@ class Game extends MediaType {
return $media;
}
public function getGameInfoForList($mediaId) {
public function getGameInfoForList(?int $mediaId): ?array {
// Media-Games Daten abrufen (ohne vollständige Relationen für Performance)
$mediaGameId = $this->getMediaGameId($mediaId);
if (!$mediaGameId) {
@@ -281,7 +283,7 @@ class Game extends MediaType {
return $gameInfo;
}
public static function interpolateQuery($query, $params) {
public static function interpolateQuery(string $query, array $params): string {
$keys = array();
# build a regular expression for each parameter
@@ -300,7 +302,7 @@ class Game extends MediaType {
return $query;
}
protected function createMediaGame($data) {
protected function createMediaGame(array $data): string {
$stmt = $this->pdo->prepare("
INSERT INTO media_games (media_id, sortingName, notes, completionStatus, source, gameId, pluginId,
isInstalled, installDirectory, installSize, hidden, favorite, playCount,
@@ -337,7 +339,7 @@ class Game extends MediaType {
return $this->pdo->lastInsertId();
}
protected function updateMediaGame($mediaId, $data) {
protected function updateMediaGame(int $mediaId, array $data): void {
$setClause = [];
$params = [];
@@ -354,14 +356,14 @@ class Game extends MediaType {
$stmt->execute($params);
}
protected function getMediaGameId($mediaId) {
protected function getMediaGameId(?int $mediaId): ?int {
$stmt = $this->pdo->prepare("SELECT id FROM media_games WHERE media_id = ?");
$stmt->execute([$mediaId]);
$result = $stmt->fetch();
return $result ? $result['id'] : null;
}
protected function getMediaGameData($mediaGameId) {
protected function getMediaGameData(?int $mediaGameId): array {
$stmt = $this->pdo->prepare("SELECT * FROM media_games WHERE id = ?");
$stmt->execute([$mediaGameId]);
$data = $stmt->fetch();
@@ -373,7 +375,7 @@ class Game extends MediaType {
return $data ?: [];
}
protected function saveAchievements($mediaGameId, $achievements) {
protected function saveAchievements(string $mediaGameId, array $achievements): void {
$stmt = $this->pdo->prepare("
INSERT INTO achievements (media_game_id, name, description, icon, unlocked, unlocked_date)
VALUES (?, ?, ?, ?, ?, ?)
@@ -396,26 +398,26 @@ class Game extends MediaType {
}
}
protected function getAchievements($mediaGameId) {
protected function getAchievements(string $mediaGameId): array {
$stmt = $this->pdo->prepare("SELECT * FROM achievements WHERE media_game_id = ?");
$stmt->execute([$mediaGameId]);
return $stmt->fetchAll();
}
protected function saveGameRelation($table, $mediaGameId, $items, $field) {
protected function saveGameRelation(string $table, string $mediaGameId, array $items, string $field): void {
$stmt = $this->pdo->prepare("INSERT INTO $table (media_game_id, $field) VALUES (?, ?)");
foreach ($items as $item) {
$stmt->execute([$mediaGameId, $item]);
}
}
protected function consoleExists($name) {
protected function consoleExists(string $name): bool {
$stmt = $this->pdo->prepare("SELECT id FROM media WHERE type = 'Console' AND title = ?");
$stmt->execute([$name]);
return $stmt->fetch() !== false;
}
protected function createConsole($name) {
protected function createConsole(string $name): string {
$stmt = $this->pdo->prepare("
INSERT INTO media (title, cleanname, type, createdAt, updatedAt)
VALUES (?, ?, 'Console', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
@@ -424,7 +426,7 @@ class Game extends MediaType {
return $this->pdo->lastInsertId();
}
protected function saveGameRelationWithConsole($table, $mediaGameId, $items, $field) {
protected function saveGameRelationWithConsole(string $table, string $mediaGameId, array $items, string $field): void {
$stmt = $this->pdo->prepare("INSERT INTO $table (media_game_id, $field) VALUES (?, ?)");
foreach ($items as $item) {
// Check if console exists, create if not
@@ -435,7 +437,7 @@ class Game extends MediaType {
}
}
protected function getGameRelation($table, $mediaGameId, $field) {
protected function getGameRelation(string $table, string $mediaGameId, string $field): array {
$stmt = $this->pdo->prepare("SELECT $field FROM $table WHERE media_game_id = ?");
$stmt->execute([$mediaGameId]);
$items = $stmt->fetchAll();
@@ -444,7 +446,7 @@ class Game extends MediaType {
}, $items);
}
protected function saveLinks($mediaGameId, $links) {
protected function saveLinks(string $mediaGameId, array $links): void {
$stmt = $this->pdo->prepare("INSERT INTO game_links (media_game_id, name, url) VALUES (?, ?, ?)");
foreach ($links as $link) {
$stmt->execute([
@@ -455,13 +457,13 @@ class Game extends MediaType {
}
}
protected function getLinks($mediaGameId) {
protected function getLinks(string $mediaGameId): array {
$stmt = $this->pdo->prepare("SELECT name, url FROM game_links WHERE media_game_id = ?");
$stmt->execute([$mediaGameId]);
return $stmt->fetchAll();
}
public function search($filters = [], $page = 1, $limit = 20) {
public function search(array $filters = [], int $page = 1, int $limit = 20): array {
// Nur Games suchen
$filters['type'] = 'Game';
return parent::search($filters, $page, $limit);