refactor: reorganize project structure for better maintainability

- Move 45 debug/test files from root to organized scripts/ directories
- Secure public/ directory by removing debug files (security improvement)
- Create structured scripts organization:
  • scripts/debug/      (20 files) - Framework debugging tools
  • scripts/test/       (18 files) - Test and validation scripts
  • scripts/maintenance/ (5 files) - Maintenance utilities
  • scripts/dev/         (2 files) - Development tools

Security improvements:
- Removed all debug/test files from public/ directory
- Only production files remain: index.php, health.php

Root directory cleanup:
- Reduced from 47 to 2 PHP files in root
- Only essential production files: console.php, worker.php

This improves:
 Security (no debug code in public/)
 Organization (clear separation of concerns)
 Maintainability (easy to find and manage scripts)
 Professional structure (clean root directory)
This commit is contained in:
2025-10-05 10:59:15 +02:00
parent 03e5188644
commit 887847dde6
77 changed files with 3902 additions and 787 deletions

View File

@@ -0,0 +1,38 @@
<?php
/**
* Temporary workaround for the "Uninitialized string offset 0" warning in ClassLoader.php
*
* This file registers an autoloader hook that catches empty class names,
* logs them with stack traces, but doesn't throw exceptions, allowing the
* application to continue running.
*
* Usage: Include this file at the beginning of your application bootstrap process,
* before any other autoloading occurs.
*/
// Register the autoloader hook
spl_autoload_register(function($class) {
if (empty($class)) {
// Log the empty class name with stack trace
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
$logMessage = date('Y-m-d H:i:s') . " - Empty class name detected in autoloader.\nStack trace:\n" .
json_encode($trace, JSON_PRETTY_PRINT) . "\n\n";
// Log to file
file_put_contents(
__DIR__ . '/empty_class_debug.log',
$logMessage,
FILE_APPEND
);
// Also log to error_log for server logs
error_log('Empty class name detected in autoloader. See empty_class_debug.log for details.');
// Return false to continue with other autoloaders
return false;
}
// Not an empty class name, let other autoloaders handle it
return false;
}, true, true); // prepend=true to ensure this runs before other autoloaders

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
/**
* Bootstrap Discovery System
*
* This script runs the discovery scanners and stores results
* Run this ONCE to initialize the new discovery system
*/
require_once __DIR__ . '/vendor/autoload.php';
use App\Framework\BuildTime\Discovery\Scanners\AttributeScanner;
use App\Framework\BuildTime\Discovery\Scanners\InterfaceScanner;
use App\Framework\BuildTime\Discovery\Scanners\TemplateScanner;
use App\Framework\Core\PathProvider;
use App\Framework\Discovery\Storage\DiscoveryStorageService;
use App\Framework\Filesystem\FileScanner;
use App\Framework\Filesystem\FileSystemService;
use App\Framework\Logging\NullLogger;
use App\Framework\Reflection\CachedReflectionProvider;
echo "🚀 Bootstrapping Discovery System...\n\n";
$totalStart = microtime(true);
// Create dependencies
$basePath = __DIR__;
$pathProvider = new PathProvider($basePath);
$storage = new DiscoveryStorageService($pathProvider);
$fileSystemService = new FileSystemService();
$logger = null;
$fileScanner = new FileScanner($logger, null, $fileSystemService);
$reflectionProvider = new CachedReflectionProvider();
// 1. Discover Attributes
echo "📦 Discovering attributes...\n";
$attrStart = microtime(true);
$attributeScanner = new AttributeScanner($fileScanner, $reflectionProvider);
$paths = [$pathProvider->getSourcePath()];
$attributeRegistry = $attributeScanner->scan($paths);
$storage->storeAttributes($attributeRegistry);
$attrDuration = round((microtime(true) - $attrStart) * 1000, 2);
echo "{$attributeRegistry->count()} attributes in {$attrDuration}ms\n\n";
// 2. Discover Templates
echo "📄 Discovering templates...\n";
$tplStart = microtime(true);
$templateScanner = new TemplateScanner($fileScanner);
$templatePaths = [
$pathProvider->getSourcePath(),
$pathProvider->getBasePath() . '/resources'
];
$templateRegistry = $templateScanner->scan($templatePaths);
$storage->storeTemplates($templateRegistry);
$tplDuration = round((microtime(true) - $tplStart) * 1000, 2);
echo "" . count($templateRegistry->getAll()) . " templates in {$tplDuration}ms\n\n";
// 3. Discover Interfaces
echo "🔌 Discovering interface implementations...\n";
$intStart = microtime(true);
$interfaceScanner = new InterfaceScanner($fileScanner, $reflectionProvider, []);
$interfaceRegistry = $interfaceScanner->scan($paths);
$storage->storeInterfaces($interfaceRegistry);
$intDuration = round((microtime(true) - $intStart) * 1000, 2);
echo "{$interfaceRegistry->count()} implementations in {$intDuration}ms\n\n";
// Summary
$totalDuration = round((microtime(true) - $totalStart) * 1000, 2);
echo str_repeat("=", 60) . "\n";
echo "🎉 Discovery bootstrap complete in {$totalDuration}ms\n";
echo " 📁 Stored in: storage/discovery/\n";
echo str_repeat("=", 60) . "\n";

View File

@@ -0,0 +1,90 @@
<?php
// build-container.php
require __DIR__ . '/../vendor/autoload.php';
use App\Framework\DI\DefaultContainer;
use App\Framework\DI\ContainerCompiler;
// Container initialisieren
$container = new DefaultContainer();
// Hier wichtige Core-Klassen registrieren
$container->bind(\App\Framework\Http\RequestFactory::class, \App\Framework\Http\RequestFactory::class);
// Weitere Bindungen...
// Liste der zu kompilierenden Services
$services = [
\App\Framework\Core\Application::class,
\App\Framework\EventBus\DefaultEventBus::class,
\App\Framework\CommandBus\DefaultCommandBus::class,
\App\Framework\Router\HttpRouter::class,
\App\Framework\Http\RequestFactory::class,
// Weitere wichtige Services...
];
// Services aus Verzeichnissen automatisch erkennen
$servicesFromDiscovery = discoverServicesFromDirectories([
__DIR__ . '/../src/Application',
__DIR__ . '/../src/Framework',
]);
$services = array_merge($services, $servicesFromDiscovery);
// Container kompilieren
$compiler = new ContainerCompiler();
$compiler->compile(
$container,
$services,
__DIR__ . '/../cache/CompiledContainer.php'
);
echo "Container kompiliert!\n";
// Hilfsfunktion zum Entdecken von Services
function discoverServicesFromDirectories(array $directories): array
{
$services = [];
foreach ($directories as $directory) {
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory));
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'php') {
$className = getClassNameFromFile($file->getRealPath());
if ($className) {
$services[] = $className;
}
}
}
}
return $services;
}
function getClassNameFromFile(string $file): ?string
{
$content = file_get_contents($file);
$tokens = token_get_all($content);
$namespace = '';
$class = '';
$namespaceFound = false;
$classFound = false;
foreach ($tokens as $token) {
if (is_array($token)) {
if ($token[0] === T_NAMESPACE) {
$namespaceFound = true;
} elseif ($namespaceFound && $token[0] === T_STRING) {
$namespace .= $token[1];
} elseif ($namespaceFound && $token[0] === T_NS_SEPARATOR) {
$namespace .= '\\';
} elseif ($token[0] === T_CLASS) {
$classFound = true;
} elseif ($classFound && $token[0] === T_STRING) {
$class = $token[1];
break;
}
} elseif ($namespaceFound && $token === ';') {
$namespaceFound = false;
}
}
return $namespace && $class ? $namespace . '\\' . $class : null;
}

View File

@@ -0,0 +1,224 @@
<?php
declare(strict_types=1);
/**
* Script to populate the database with images found in the filesystem
*
* This script scans storage/uploads/ for image files and creates database records
* to match the existing files on disk.
*/
require_once 'vendor/autoload.php';
use App\Domain\Media\Image;
use App\Framework\Core\ValueObjects\FileSize;
use App\Framework\Core\ValueObjects\Hash;
use App\Framework\Database\DatabaseManager;
use App\Framework\Filesystem\FilePath;
use App\Framework\Http\MimeType;
use App\Framework\Ulid\Ulid;
use App\Framework\Ulid\UlidGenerator;
use App\Framework\DateTime\SystemClock;
class ImageMigrationScript
{
private PDO $db;
private SystemClock $clock;
private string $uploadsPath;
public function __construct()
{
$this->clock = new SystemClock();
$this->uploadsPath = __DIR__ . '/storage/uploads';
// Initialize database
$this->initializeDatabase();
}
private function initializeDatabase(): void
{
// Simple SQLite connection for this script
$pdo = new PDO('sqlite:' . __DIR__ . '/database.sqlite');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->db = $pdo;
}
public function run(): void
{
echo "🔍 Scanning for images in: {$this->uploadsPath}\n";
if (!is_dir($this->uploadsPath)) {
echo "❌ Uploads directory not found: {$this->uploadsPath}\n";
return;
}
$imageFiles = $this->findImageFiles();
echo "📁 Found " . count($imageFiles) . " image files\n";
if (empty($imageFiles)) {
echo " No images to migrate\n";
return;
}
$this->migrateImages($imageFiles);
echo "✅ Migration completed!\n";
}
private function findImageFiles(): array
{
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->uploadsPath)
);
$imageFiles = [];
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
foreach ($iterator as $file) {
if (!$file->isFile()) {
continue;
}
$extension = strtolower($file->getExtension());
if (!in_array($extension, $allowedExtensions)) {
continue;
}
$imageFiles[] = [
'path' => $file->getPathname(),
'filename' => $file->getFilename(),
'extension' => $extension,
'size' => $file->getSize(),
'mtime' => $file->getMTime()
];
}
return $imageFiles;
}
private function migrateImages(array $imageFiles): void
{
// Check current database schema
$this->checkDatabaseSchema();
$migrated = 0;
$errors = 0;
foreach ($imageFiles as $fileInfo) {
try {
$this->migrateImageFile($fileInfo);
$migrated++;
echo "✓ Migrated: {$fileInfo['filename']}\n";
} catch (Exception $e) {
$errors++;
echo "❌ Error migrating {$fileInfo['filename']}: " . $e->getMessage() . "\n";
}
}
echo "\n📊 Summary:\n";
echo " Migrated: $migrated\n";
echo " Errors: $errors\n";
}
private function checkDatabaseSchema(): void
{
// Check what columns exist in the images table
$stmt = $this->db->query("PRAGMA table_info(images)");
$columns = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo "📋 Database schema (images table):\n";
foreach ($columns as $column) {
echo " - {$column['name']} ({$column['type']})\n";
}
echo "\n";
}
private function migrateImageFile(array $fileInfo): void
{
$fullPath = $fileInfo['path'];
// Extract image dimensions if possible
$imageInfo = @getimagesize($fullPath);
$width = $imageInfo[0] ?? 0;
$height = $imageInfo[1] ?? 0;
// Generate ULID
$ulidGenerator = new UlidGenerator();
$ulidString = $ulidGenerator->generate($this->clock);
// Calculate hash
$hashValue = hash_file('sha256', $fullPath);
// Determine MIME type
$mimeTypeString = match (strtolower($fileInfo['extension'])) {
'jpg', 'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'webp' => 'image/webp',
default => 'image/jpeg'
};
// Extract original filename from the complex filename structure
$originalFilename = $this->extractOriginalFilename($fileInfo['filename']);
// Get relative path from storage root
$relativePath = str_replace($this->uploadsPath . '/', '', $fullPath);
$pathOnly = dirname($relativePath);
// Insert into database using the correct table structure
$sql = "INSERT INTO images (
ulid, filename, original_filename, mime_type, file_size,
width, height, hash, path, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = $this->db->prepare($sql);
$now = date('Y-m-d H:i:s');
$stmt->execute([
$ulidString,
$fileInfo['filename'],
$originalFilename,
$mimeTypeString,
$fileInfo['size'],
$width,
$height,
$hashValue,
$pathOnly,
$now,
$now
]);
}
private function extractOriginalFilename(string $filename): string
{
// Pattern for files like: BFWCAKKEHTKF5SYR_6626fc6b...cd1_original.png
if (preg_match('/^[A-Z0-9]{16}_[a-f0-9]{64}_original\.(.+)$/', $filename, $matches)) {
// This is an original file, try to find the pattern in other files
$basePattern = substr($filename, 0, strpos($filename, '_original.'));
// For now, just return a cleaned version
return "original." . $matches[1];
}
// Pattern for simple files like: 00MF9VW9R36NJN3VCFSTS2CK6R.jpg
if (preg_match('/^[A-Z0-9]{26}\.(.+)$/', $filename, $matches)) {
return "image." . $matches[1];
}
// Fallback: return as-is
return $filename;
}
}
// Run the migration
echo "🚀 Starting image migration from filesystem to database...\n\n";
try {
$migration = new ImageMigrationScript();
$migration->run();
} catch (Exception $e) {
echo "💥 Migration failed: " . $e->getMessage() . "\n";
echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
exit(1);
}
echo "\n🎉 Migration script completed!\n";

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
/**
* Quick fix to restore localhost functionality
* Clears discovery cache and forces a minimal discovery
*/
echo "🚨 Quick Fix: Restoring localhost functionality\n";
echo "===============================================\n\n";
try {
require __DIR__ . '/../vendor/autoload.php';
echo "1. ✅ Clearing discovery cache...\n";
// Clear storage cache
exec('rm -rf ' . __DIR__ . '/../storage/cache/*', $output, $returnCode);
if ($returnCode === 0) {
echo " ✅ Storage cache cleared\n";
}
// Clear any potential cache files in var/
if (is_dir(__DIR__ . '/../var/cache')) {
exec('rm -rf ' . __DIR__ . '/../var/cache/*', $output, $returnCode);
echo " ✅ Var cache cleared\n";
}
echo "2. ⚡ Optimizing discovery paths...\n";
// Create a temporary configuration to reduce discovery scope
$optimizedConfig = [
'discovery_paths' => [
'Application', // Only scan application code
'Domain' // And domain models
],
'exclude_paths' => [
'Framework/AsyncExamples', // Skip examples
'Framework/Testing', // Skip testing utilities
'Framework/Debug', // Skip debug utilities
'tests' // Skip test files
]
];
echo " ✅ Limited discovery to: " . implode(', ', $optimizedConfig['discovery_paths']) . "\n";
echo " ✅ Excluded: " . implode(', ', $optimizedConfig['exclude_paths']) . "\n";
echo "\n3. 🧪 Testing basic application...\n";
// Test if basic classes load without discovery
$testClasses = [
'App\\Framework\\Core\\Application',
'App\\Framework\\Http\\HttpRequest'
];
foreach ($testClasses as $class) {
if (class_exists($class)) {
echo "$class loaded\n";
} else {
echo "$class failed\n";
}
}
echo "\n4. 💡 Recommendations:\n";
echo " • Discovery system needs optimization for large codebase\n";
echo " • Consider implementing lazy loading for non-critical components\n";
echo " • Use incremental discovery instead of full scans\n";
echo " • Add performance monitoring to discovery process\n";
echo "\n🎉 Quick fix complete!\n";
echo "💬 Try accessing https://localhost/ now\n";
} catch (Exception $e) {
echo "❌ Error: " . $e->getMessage() . "\n";
exit(1);
}