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.
207 lines
6.4 KiB
PHP
207 lines
6.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
class DocumentationService {
|
|
private string $controllersPath;
|
|
private string $modelsPath;
|
|
|
|
public function __construct(string $controllersPath = __DIR__ . '/../controllers/', string $modelsPath = __DIR__ . '/../models/') {
|
|
$this->controllersPath = $controllersPath;
|
|
$this->modelsPath = $modelsPath;
|
|
}
|
|
|
|
public function generateDocumentation(): array {
|
|
$docs = [
|
|
'title' => 'Media API Documentation',
|
|
'version' => '1.0.0',
|
|
'baseUrl' => '/api',
|
|
'endpoints' => [],
|
|
'models' => []
|
|
];
|
|
|
|
// Controller scannen
|
|
$docs['endpoints'] = $this->scanControllers();
|
|
|
|
// Modelle scannen
|
|
$docs['models'] = $this->scanModels();
|
|
|
|
return $docs;
|
|
}
|
|
|
|
private function scanControllers(): array {
|
|
$endpoints = [];
|
|
|
|
$controllerFiles = glob($this->controllersPath . '*Controller.php');
|
|
|
|
foreach ($controllerFiles as $file) {
|
|
$className = basename($file, '.php');
|
|
require_once $file;
|
|
|
|
$reflection = new ReflectionClass($className);
|
|
$methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
|
|
|
|
foreach ($methods as $method) {
|
|
if ($method->getName() === '__construct') {
|
|
continue;
|
|
}
|
|
|
|
$docComment = $method->getDocComment();
|
|
$endpointInfo = $this->parseMethodDoc($docComment, $method->getName(), $className);
|
|
|
|
if ($endpointInfo) {
|
|
$endpoints[] = $endpointInfo;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $endpoints;
|
|
}
|
|
|
|
private function parseMethodDoc(?string $docComment, string $methodName, string $className): ?array {
|
|
if (!$docComment) {
|
|
return null;
|
|
}
|
|
|
|
$info = [
|
|
'controller' => $className,
|
|
'method' => $methodName,
|
|
'description' => '',
|
|
'parameters' => [],
|
|
'response' => [],
|
|
'example' => null
|
|
];
|
|
|
|
// Beschreibung extrahieren
|
|
if (preg_match('/\*\s*(.+?)\n/', $docComment, $matches)) {
|
|
$info['description'] = trim($matches[1]);
|
|
}
|
|
|
|
// @param extrahieren
|
|
if (preg_match_all('/@param\s+(\w+)\s+\$(\w+)\s+(.+)/', $docComment, $matches)) {
|
|
foreach ($matches[1] as $i => $type) {
|
|
$info['parameters'][] = [
|
|
'name' => $matches[2][$i],
|
|
'type' => $type,
|
|
'description' => trim($matches[3][$i])
|
|
];
|
|
}
|
|
}
|
|
|
|
// @return extrahieren
|
|
if (preg_match('/@return\s+(\w+)\s+(.+)/', $docComment, $matches)) {
|
|
$info['response'] = [
|
|
'type' => $matches[1],
|
|
'description' => trim($matches[2])
|
|
];
|
|
}
|
|
|
|
// @example extrahieren
|
|
if (preg_match('/@example\s+(.+)/', $docComment, $matches)) {
|
|
$info['example'] = trim($matches[1]);
|
|
}
|
|
|
|
// HTTP-Methoden und Pfade aus Methodennamen ableiten
|
|
$info['httpMethods'] = $this->inferHttpMethods($methodName);
|
|
$info['path'] = $this->inferPath($className, $methodName);
|
|
|
|
return $info;
|
|
}
|
|
|
|
private function inferHttpMethods(string $methodName): array {
|
|
$methods = [];
|
|
|
|
if (strpos($methodName, 'get') === 0) {
|
|
$methods[] = 'GET';
|
|
}
|
|
if (strpos($methodName, 'create') !== false || strpos($methodName, 'add') !== false || strpos($methodName, 'post') !== false) {
|
|
$methods[] = 'POST';
|
|
}
|
|
if (strpos($methodName, 'update') !== false) {
|
|
$methods[] = 'PUT';
|
|
}
|
|
if (strpos($methodName, 'delete') !== false) {
|
|
$methods[] = 'DELETE';
|
|
}
|
|
|
|
if (empty($methods)) {
|
|
$methods[] = 'GET'; // Default
|
|
}
|
|
|
|
return $methods;
|
|
}
|
|
|
|
private function inferPath(string $className, string $methodName): string {
|
|
$resource = strtolower(str_replace('Controller', '', $className));
|
|
$path = "/{$resource}";
|
|
|
|
if (strpos($methodName, 'getOne') !== false || strpos($methodName, 'update') !== false || strpos($methodName, 'delete') !== false) {
|
|
$path .= '/:id';
|
|
}
|
|
|
|
if (strpos($methodName, 'getMedia') !== false) {
|
|
$path .= '/:id/media';
|
|
}
|
|
|
|
if (strpos($methodName, 'handleEpisodes') !== false) {
|
|
$path .= '/:id/episodes';
|
|
}
|
|
|
|
if (strpos($methodName, 'handleTracks') !== false) {
|
|
$path .= '/:id/tracks';
|
|
}
|
|
|
|
if (strpos($methodName, 'handleAdult') !== false) {
|
|
$path .= '/adult';
|
|
}
|
|
|
|
return $path;
|
|
}
|
|
|
|
private function scanModels(): array {
|
|
$models = [];
|
|
|
|
$modelFiles = glob($this->modelsPath . '*.php');
|
|
|
|
foreach ($modelFiles as $file) {
|
|
$className = basename($file, '.php');
|
|
if ($className === 'BaseModel' || $className === 'MediaType') {
|
|
continue;
|
|
}
|
|
|
|
require_once $file;
|
|
|
|
$reflection = new ReflectionClass($className);
|
|
$docComment = $reflection->getDocComment();
|
|
|
|
$modelInfo = [
|
|
'name' => $className,
|
|
'description' => '',
|
|
'fields' => []
|
|
];
|
|
|
|
if ($docComment) {
|
|
if (preg_match('/\*\s*(.+?)\n/', $docComment, $matches)) {
|
|
$modelInfo['description'] = trim($matches[1]);
|
|
}
|
|
}
|
|
|
|
// Properties aus Klasse ableiten
|
|
$properties = $reflection->getProperties();
|
|
foreach ($properties as $property) {
|
|
if ($property->isPublic() || $property->isProtected()) {
|
|
$modelInfo['fields'][] = [
|
|
'name' => $property->getName(),
|
|
'type' => 'mixed',
|
|
'description' => ''
|
|
];
|
|
}
|
|
}
|
|
|
|
$models[] = $modelInfo;
|
|
}
|
|
|
|
return $models;
|
|
}
|
|
}
|