docs: consolidate documentation into organized structure

- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
This commit is contained in:
2025-10-05 11:05:04 +02:00
parent 887847dde6
commit 5050c7d73a
36686 changed files with 196456 additions and 12398919 deletions

View File

@@ -8,67 +8,106 @@ use App\Framework\DI\Container;
use App\Framework\Meta\MetaData;
use App\Framework\Template\Processing\DomProcessor;
use App\Framework\View\DomWrapper;
use App\Framework\View\RawHtml;
use App\Framework\View\RenderContext;
use App\Framework\View\TemplateProcessor;
final class ForProcessor implements DomProcessor
{
public function __construct(
private Container $container,
private ?TemplateProcessor $templateProcessor = null,
) {
// Falls kein TemplateProcessor übergeben wird, erstellen wir einen mit den Standard-Prozessoren
if ($this->templateProcessor === null) {
#$this->templateProcessor = new TemplateProcessor([],[PlaceholderReplacer::class], $this->container);
#$this->templateProcessor->register(PlaceholderReplacer::class);
$this->templateProcessor = $this->container->get(TemplateProcessor::class);
}
}
) {}
/**
* @inheritDoc
*/
public function process(DomWrapper $dom, RenderContext $context): DomWrapper
{
foreach ($dom->document->querySelectorAll('for[var][in]') as $node) {
$forNodes = $dom->document->querySelectorAll('for[var][in]');
foreach ($forNodes as $node) {
$var = $node->getAttribute('var');
$in = $node->getAttribute('in');
$output = '';
// Handle nested property paths (e.g., "redis.key_sample")
// Resolve items from context data or model
$items = $this->resolveValue($context->data, $in);
// Fallback to model if not found in data
if ($items === null && isset($context->model)) {
$items = $this->resolveValue(['model' => $context->model], 'model.' . $in);
if (str_starts_with($in, 'model.')) {
$items = $this->resolveValue(['model' => $context->model], $in);
} else {
$items = $this->resolveValue(['model' => $context->model], 'model.' . $in);
}
}
if (is_iterable($items)) {
foreach ($items as $item) {
$clone = $node->cloneNode(true);
// Neuen Kontext für die Schleifenvariable erstellen
// Create loop context with loop variable
$loopContext = new RenderContext(
template: $context->template,
metaData: new MetaData('', ''),
data: array_merge($context->data, [$var => $item]),
#model: $context->model,
controllerClass: $context->controllerClass
);
// Den Inhalt der Schleife mit den bestehenden Prozessoren verarbeiten
// Get innerHTML from cloned node
$innerHTML = $clone->innerHTML;
$processedContent = $this->templateProcessor->render($loopContext, $innerHTML, true);
// Handle case where DOM parser treats <for> as self-closing
if (trim($innerHTML) === '') {
$innerHTML = $this->collectSiblingContent($node, $dom);
}
// Replace loop variable placeholders
$innerHTML = $this->replaceLoopVariables($innerHTML, $var, $item);
// Process placeholders in loop content
$placeholderReplacer = $this->container->get(PlaceholderReplacer::class);
$processedContent = $placeholderReplacer->process($innerHTML, $loopContext);
// Handle nested <for> tags recursively
if (str_contains($processedContent, '<for ')) {
try {
$tempWrapper = DomWrapper::fromString($processedContent);
$this->process($tempWrapper, $loopContext);
$processedContent = $tempWrapper->toHtml(true);
} catch (\Exception $e) {
// Continue with unprocessed content on error
}
}
$output .= $processedContent;
}
}
$replacement = $dom->document->createDocumentFragment();
@$replacement->appendXML($output);
$node->parentNode?->replaceChild($replacement, $node);
// Replace for node with processed output
if (!empty($output)) {
try {
$replacement = $dom->document->createDocumentFragment();
@$replacement->appendXML($output);
$node->parentNode?->replaceChild($replacement, $node);
} catch (\Exception $e) {
// Fallback: Use innerHTML approach
$tempDiv = $dom->document->createElement('div');
$tempDiv->innerHTML = $output;
$parent = $node->parentNode;
$nextSibling = $node->nextSibling;
$parent->removeChild($node);
while ($tempDiv->firstChild) {
if ($nextSibling) {
$parent->insertBefore($tempDiv->firstChild, $nextSibling);
} else {
$parent->appendChild($tempDiv->firstChild);
}
}
}
} else {
// Remove empty for node
$node->parentNode?->removeChild($node);
}
}
return $dom;
@@ -85,8 +124,20 @@ final class ForProcessor implements DomProcessor
foreach ($keys as $key) {
if (is_array($value) && array_key_exists($key, $value)) {
$value = $value[$key];
} elseif (is_object($value) && isset($value->$key)) {
$value = $value->$key;
} elseif (is_object($value)) {
// Try property access first
if (isset($value->$key)) {
$value = $value->$key;
} elseif (method_exists($value, $key)) {
// Try method call
$value = $value->$key();
} elseif (method_exists($value, 'get' . ucfirst($key))) {
// Try getter method
$getterMethod = 'get' . ucfirst($key);
$value = $value->$getterMethod();
} else {
return null;
}
} else {
return null;
}
@@ -94,4 +145,91 @@ final class ForProcessor implements DomProcessor
return $value;
}
/**
* Replaces loop variable placeholders in the HTML content
*/
private function replaceLoopVariables(string $html, string $varName, mixed $item): string
{
$pattern = '/{{\\s*' . preg_quote($varName, '/') . '\\.([\\w]+)\\s*}}/';
return preg_replace_callback(
$pattern,
function ($matches) use ($item) {
$property = $matches[1];
if (is_array($item) && array_key_exists($property, $item)) {
$value = $item[$property];
if (is_bool($value)) {
return $value ? 'true' : 'false';
}
if ($value instanceof RawHtml) {
return $value->content;
}
return htmlspecialchars((string) $value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
} elseif (is_object($item) && isset($item->$property)) {
$value = $item->$property;
if (is_bool($value)) {
return $value ? 'true' : 'false';
}
if ($value instanceof RawHtml) {
return $value->content;
}
return htmlspecialchars((string) $value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
// Return placeholder unchanged if property not found
return $matches[0];
},
$html
);
}
/**
* Collects content from sibling nodes when <for> is treated as self-closing
*/
private function collectSiblingContent($forNode, DomWrapper $dom): string
{
$content = '';
$currentNode = $forNode->nextSibling;
while ($currentNode !== null) {
if ($currentNode->nodeType === XML_ELEMENT_NODE) {
// Check for loop content elements (TR for tables, DIV for other structures)
if ($currentNode->tagName === 'TR') {
$content .= $dom->document->saveHTML($currentNode);
$nextNode = $currentNode->nextSibling;
$currentNode->parentNode->removeChild($currentNode);
$currentNode = $nextNode;
break; // One TR per iteration
} elseif ($currentNode->tagName === 'TABLE') {
// Look for template TR inside table
$tableRows = $currentNode->querySelectorAll('tr');
foreach ($tableRows as $row) {
$rowHtml = $dom->document->saveHTML($row);
// Find row with placeholders (template row)
if (str_contains($rowHtml, '{{')) {
$content = $rowHtml;
break 2;
}
}
$currentNode = $currentNode->nextSibling;
} else {
$currentNode = $currentNode->nextSibling;
}
} else {
$currentNode = $currentNode->nextSibling;
}
}
return $content;
}
}