Files
mystuff_backend/api/services/DocumentationService.php
Lars Behrends 66f69bc90d Add PHP Media API scaffold and Docker configs
Initial project scaffold for a PHP Media API including routing, controllers, models and services under api/ (Router, Media/Cast/Image/Settings controllers, models, database/bootstrap files and automatic docs service). Adds Docker support (Dockerfile, docker-compose.yml, DOCKER_README.md, php-custom.ini), .htaccess for pretty URLs, API documentation and example payloads (API_EXAMPLES.md, api/README.md, api_examples/*.json), image handling service and logging, plus a comprehensive .gitignore. This commit provides a runnable development environment and example requests to get the API up and tested quickly.
2026-04-12 00:46:30 +02:00

205 lines
6.3 KiB
PHP

<?php
class DocumentationService {
private $controllersPath;
private $modelsPath;
public function __construct($controllersPath = __DIR__ . '/../controllers/', $modelsPath = __DIR__ . '/../models/') {
$this->controllersPath = $controllersPath;
$this->modelsPath = $modelsPath;
}
public function generateDocumentation() {
$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() {
$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($docComment, $methodName, $className) {
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($methodName) {
$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($className, $methodName) {
$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() {
$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;
}
}