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

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace App\Framework\View\ValueObjects;
use ReflectionClass;
final readonly class ComponentMetadata
{
/**
* @param class-string<HtmlElement> $class
* @param array<string, \ReflectionMethod> $factories
* @param array<string, \ReflectionMethod> $modifiers
*/
public function __construct(
public string $class,
public array $factories,
public array $modifiers,
public ReflectionClass $reflection
) {
}
public function hasFactory(string $name): bool
{
return isset($this->factories[$name]);
}
public function hasModifier(string $name): bool
{
return isset($this->modifiers[$name]);
}
public function getFactory(string $name): ?\ReflectionMethod
{
return $this->factories[$name] ?? null;
}
public function getModifier(string $name): ?\ReflectionMethod
{
return $this->modifiers[$name] ?? null;
}
}

View File

@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace App\Framework\View\ValueObjects;
final readonly class FormElement implements HtmlElement
{
public function __construct(
public HtmlTag $tag,
public HtmlAttributes $attributes,
public string $content = ''
) {
if (!$this->tag->isFormElement()) {
throw new \InvalidArgumentException("Tag {$this->tag} is not a form element");
}
}
public static function form(string $action = '', string $method = 'post', ?string $htmlId = null): self
{
$attributes = HtmlAttributes::empty()
->withAction($action)
->withMethod($method);
if ($htmlId) {
$attributes = $attributes->withId($htmlId);
}
return new self(HtmlTag::form(), $attributes);
}
public static function input(string $type, string $name, string $value = ''): self
{
$attributes = HtmlAttributes::empty()
->withType($type)
->withName($name);
if ($value !== '') {
$attributes = $attributes->withValue($value);
}
return new self(HtmlTag::input(), $attributes);
}
public static function hiddenInput(string $name, string $value): self
{
return self::input('hidden', $name, $value);
}
public static function textInput(string $name, string $value = ''): self
{
return self::input('text', $name, $value);
}
public static function emailInput(string $name, string $value = ''): self
{
return self::input('email', $name, $value);
}
public static function passwordInput(string $name): self
{
return self::input('password', $name);
}
public static function fileInput(string $name): self
{
return self::input('file', $name);
}
public static function submitButton(string $text = 'Submit'): self
{
$attributes = HtmlAttributes::empty()->withType('submit');
return new self(HtmlTag::button(), $attributes, $text);
}
public static function button(string $text, string $type = 'button'): self
{
$attributes = HtmlAttributes::empty()->withType($type);
return new self(HtmlTag::button(), $attributes, $text);
}
public static function label(string $text, string $for = ''): self
{
$attributes = $for ? HtmlAttributes::empty()->with('for', $for) : HtmlAttributes::empty();
return new self(HtmlTag::label(), $attributes, $text);
}
public static function textarea(string $name, string $content = ''): self
{
$attributes = HtmlAttributes::empty()->withName($name);
return new self(HtmlTag::textarea(), $attributes, $content);
}
public function withAttribute(string $name, ?string $value = null): self
{
return new self($this->tag, $this->attributes->with($name, $value), $this->content);
}
public function withRequired(): self
{
return new self($this->tag, $this->attributes->withRequired(), $this->content);
}
public function withDisabled(): self
{
return new self($this->tag, $this->attributes->withDisabled(), $this->content);
}
public function withClass(string $class): self
{
return new self($this->tag, $this->attributes->withClass($class), $this->content);
}
public function withId(string $id): self
{
return new self($this->tag, $this->attributes->withId($id), $this->content);
}
public function __toString(): string
{
$tagName = (string) $this->tag;
$attributesString = (string) $this->attributes;
$attributesPart = $attributesString ? " {$attributesString}" : '';
if ($this->tag->isSelfClosing()) {
return "<{$tagName}{$attributesPart}>";
}
return "<{$tagName}{$attributesPart}>{$this->content}</{$tagName}>";
}
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace App\Framework\View\ValueObjects;
use App\Framework\Http\Session\FormIdGenerator;
final readonly class FormId
{
public function __construct(
public string $value
) {
if (empty(trim($value))) {
throw new \InvalidArgumentException('FormId cannot be empty');
}
if (!preg_match('/^[a-zA-Z][a-zA-Z0-9_-]*$/', $value)) {
throw new \InvalidArgumentException('FormId must start with a letter and contain only letters, numbers, underscores, and hyphens');
}
}
public static function generate(FormIdGenerator $generator, string $route = '/', string $method = 'post'): self
{
// Use the existing FormIdGenerator logic
$formId = $generator->generateFormId($route, $method);
return new self($formId);
}
public static function fromString(string $value): self
{
return new self($value);
}
public function toString(): string
{
return $this->value;
}
public function __toString(): string
{
return $this->value;
}
public function equals(self $other): bool
{
return $this->value === $other->value;
}
public function isValid(FormIdGenerator $generator): bool
{
return $generator->isValidFormId($this);
}
}

View File

@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace App\Framework\View\ValueObjects;
final readonly class HtmlAttributes
{
/**
* @param array<string, string|null> $attributes
*/
public function __construct(
public array $attributes = []
) {}
public static function empty(): self
{
return new self([]);
}
public static function fromArray(array $attributes): self
{
return new self($attributes);
}
public function with(string $name, ?string $value = null): self
{
return new self([...$this->attributes, $name => $value]);
}
public function withFlag(string $name): self
{
return $this->with($name, null);
}
public function withId(string $id): self
{
return $this->with('id', $id);
}
public function withClass(string $class): self
{
return $this->with('class', $class);
}
public function withType(string $type): self
{
return $this->with('type', $type);
}
public function withName(string $name): self
{
return $this->with('name', $name);
}
public function withValue(string $value): self
{
return $this->with('value', $value);
}
public function withAction(string $action): self
{
return $this->with('action', $action);
}
public function withMethod(string $method): self
{
return $this->with('method', $method);
}
public function withDisabled(): self
{
return $this->withFlag('disabled');
}
public function withRequired(): self
{
return $this->withFlag('required');
}
public function withChecked(): self
{
return $this->withFlag('checked');
}
public function withReadonly(): self
{
return $this->withFlag('readonly');
}
public function withSelected(): self
{
return $this->withFlag('selected');
}
public function get(string $name): ?string
{
return $this->attributes[$name] ?? null;
}
public function has(string $name): bool
{
return array_key_exists($name, $this->attributes);
}
public function isFlag(string $name): bool
{
return $this->has($name) && $this->get($name) === null;
}
public function __toString(): string
{
if (empty($this->attributes)) {
return '';
}
$parts = [];
foreach ($this->attributes as $name => $value) {
if ($value === null) {
// Boolean/flag attribute without value
$parts[] = $name;
} else {
// Attribute with value
$escapedValue = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
$parts[] = "{$name}=\"{$escapedValue}\"";
}
}
return implode(' ', $parts);
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Framework\View\ValueObjects;
interface HtmlElement
{
public HtmlTag $tag { get; }
public HtmlAttributes $attributes { get; }
public string $content { get; }
public function __toString(): string;
}

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace App\Framework\View\ValueObjects;
final readonly class HtmlTag
{
public function __construct(
public TagName $name
) {}
public static function div(): self
{
return new self(TagName::DIV);
}
public static function form(): self
{
return new self(TagName::FORM);
}
public static function input(): self
{
return new self(TagName::INPUT);
}
public static function button(): self
{
return new self(TagName::BUTTON);
}
public static function label(): self
{
return new self(TagName::LABEL);
}
public static function textarea(): self
{
return new self(TagName::TEXTAREA);
}
public static function select(): self
{
return new self(TagName::SELECT);
}
public static function option(): self
{
return new self(TagName::OPTION);
}
public function isSelfClosing(): bool
{
return $this->name->isSelfClosing();
}
public function isFormElement(): bool
{
return $this->name->isFormElement();
}
public function __toString(): string
{
return $this->name->value;
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace App\Framework\View\ValueObjects;
final readonly class StandardHtmlElement implements HtmlElement
{
public function __construct(
public HtmlTag $tag,
public HtmlAttributes $attributes = new HtmlAttributes(),
public string $content = ''
) {}
public static function create(TagName $tagName, ?HtmlAttributes $attributes = null, string $content = ''): self
{
return new self(
new HtmlTag($tagName),
$attributes ?? HtmlAttributes::empty(),
$content
);
}
public function withAttributes(HtmlAttributes $attributes): self
{
return new self($this->tag, $attributes, $this->content);
}
public function withContent(string $content): self
{
return new self($this->tag, $this->attributes, $content);
}
public function withAttribute(string $name, ?string $value = null): self
{
return $this->withAttributes($this->attributes->with($name, $value));
}
public function __toString(): string
{
$tagName = (string) $this->tag;
$attributesString = (string) $this->attributes;
$attributesPart = $attributesString ? " {$attributesString}" : '';
if ($this->tag->isSelfClosing()) {
return "<{$tagName}{$attributesPart}>";
}
return "<{$tagName}{$attributesPart}>{$this->content}</{$tagName}>";
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace App\Framework\View\ValueObjects;
enum TagName: string
{
case DIV = 'div';
case FORM = 'form';
case INPUT = 'input';
case BUTTON = 'button';
case LABEL = 'label';
case TEXTAREA = 'textarea';
case SELECT = 'select';
case OPTION = 'option';
case SPAN = 'span';
case P = 'p';
case H1 = 'h1';
case H2 = 'h2';
case H3 = 'h3';
case H4 = 'h4';
case H5 = 'h5';
case H6 = 'h6';
case A = 'a';
case IMG = 'img';
case UL = 'ul';
case OL = 'ol';
case LI = 'li';
case TABLE = 'table';
case TR = 'tr';
case TD = 'td';
case TH = 'th';
case THEAD = 'thead';
case TBODY = 'tbody';
case TFOOT = 'tfoot';
public function isSelfClosing(): bool
{
return match ($this) {
self::INPUT, self::IMG => true,
default => false
};
}
public function isFormElement(): bool
{
return match ($this) {
self::FORM, self::INPUT, self::BUTTON, self::LABEL, self::TEXTAREA, self::SELECT, self::OPTION => true,
default => false
};
}
}