Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
15
src/Framework/Design/ValueObjects/BemType.php
Normal file
15
src/Framework/Design/ValueObjects/BemType.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
/**
|
||||
* BEM-Typen für CSS-Klassen
|
||||
*/
|
||||
enum BemType: string
|
||||
{
|
||||
case BLOCK = 'block';
|
||||
case ELEMENT = 'element';
|
||||
case MODIFIER = 'modifier';
|
||||
}
|
||||
20
src/Framework/Design/ValueObjects/ColorFormat.php
Normal file
20
src/Framework/Design/ValueObjects/ColorFormat.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
/**
|
||||
* Enum für Color Formate
|
||||
*/
|
||||
enum ColorFormat: string
|
||||
{
|
||||
case HEX = 'hex';
|
||||
case RGB = 'rgb';
|
||||
case RGBA = 'rgba';
|
||||
case HSL = 'hsl';
|
||||
case HSLA = 'hsla';
|
||||
case OKLCH = 'oklch';
|
||||
case NAMED = 'named';
|
||||
case CUSTOM_PROPERTY = 'custom_property';
|
||||
}
|
||||
185
src/Framework/Design/ValueObjects/ComponentPattern.php
Normal file
185
src/Framework/Design/ValueObjects/ComponentPattern.php
Normal file
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
/**
|
||||
* Value Object für erkannte Component-Patterns
|
||||
*/
|
||||
final readonly class ComponentPattern
|
||||
{
|
||||
public function __construct(
|
||||
public ComponentPatternType $type,
|
||||
public string $name,
|
||||
public array $classes,
|
||||
public array $metadata = []
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory für BEM Component
|
||||
*/
|
||||
public static function createBem(
|
||||
string $blockName,
|
||||
array $elements = [],
|
||||
array $modifiers = [],
|
||||
array $classes = []
|
||||
): self {
|
||||
return new self(
|
||||
type: ComponentPatternType::BEM,
|
||||
name: $blockName,
|
||||
classes: $classes,
|
||||
metadata: [
|
||||
'block' => $blockName,
|
||||
'elements' => $elements,
|
||||
'modifiers' => $modifiers,
|
||||
'element_count' => count($elements),
|
||||
'modifier_count' => count($modifiers),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory für Utility Component
|
||||
*/
|
||||
public static function createUtility(string $category, array $classes = []): self
|
||||
{
|
||||
return new self(
|
||||
type: ComponentPatternType::UTILITY,
|
||||
name: $category,
|
||||
classes: $classes,
|
||||
metadata: [
|
||||
'category' => $category,
|
||||
'class_count' => count($classes),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory für Traditional Component
|
||||
*/
|
||||
public static function createComponent(string $name, array $classes = []): self
|
||||
{
|
||||
return new self(
|
||||
type: ComponentPatternType::COMPONENT,
|
||||
name: $name,
|
||||
classes: $classes,
|
||||
metadata: [
|
||||
'component_type' => $name,
|
||||
'class_count' => count($classes),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle Klassennamen als Strings zurück
|
||||
*/
|
||||
public function getClassNames(): array
|
||||
{
|
||||
return array_map(
|
||||
fn (CssClassName $class) => $class->name,
|
||||
$this->classes
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob Pattern bestimmte Klasse enthält
|
||||
*/
|
||||
public function hasClass(string $className): bool
|
||||
{
|
||||
foreach ($this->classes as $class) {
|
||||
if ($class instanceof CssClassName && $class->name === $className) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysiert Pattern-Komplexität
|
||||
*/
|
||||
public function getComplexity(): string
|
||||
{
|
||||
$classCount = count($this->classes);
|
||||
|
||||
return match(true) {
|
||||
$classCount <= 3 => 'simple',
|
||||
$classCount <= 10 => 'moderate',
|
||||
default => 'complex'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt Pattern-spezifische Informationen zurück
|
||||
*/
|
||||
public function getPatternInfo(): array
|
||||
{
|
||||
return match($this->type) {
|
||||
ComponentPatternType::BEM => [
|
||||
'type' => 'BEM',
|
||||
'block' => $this->metadata['block'] ?? '',
|
||||
'elements' => $this->metadata['elements'] ?? [],
|
||||
'modifiers' => $this->metadata['modifiers'] ?? [],
|
||||
'structure' => $this->getBemStructure(),
|
||||
],
|
||||
ComponentPatternType::UTILITY => [
|
||||
'type' => 'Utility Classes',
|
||||
'category' => $this->metadata['category'] ?? '',
|
||||
'utilities' => $this->getClassNames(),
|
||||
],
|
||||
ComponentPatternType::COMPONENT => [
|
||||
'type' => 'Traditional Component',
|
||||
'component_type' => $this->metadata['component_type'] ?? '',
|
||||
'classes' => $this->getClassNames(),
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysiert BEM-Struktur
|
||||
*/
|
||||
private function getBemStructure(): array
|
||||
{
|
||||
$elements = $this->metadata['elements'] ?? [];
|
||||
$modifiers = $this->metadata['modifiers'] ?? [];
|
||||
|
||||
return [
|
||||
'has_elements' => ! empty($elements),
|
||||
'has_modifiers' => ! empty($modifiers),
|
||||
'element_count' => count($elements),
|
||||
'modifier_count' => count($modifiers),
|
||||
'completeness' => $this->getBemCompleteness($elements, $modifiers),
|
||||
];
|
||||
}
|
||||
|
||||
private function getBemCompleteness(array $elements, array $modifiers): string
|
||||
{
|
||||
if (empty($elements) && empty($modifiers)) {
|
||||
return 'block_only';
|
||||
}
|
||||
|
||||
if (! empty($elements) && empty($modifiers)) {
|
||||
return 'block_with_elements';
|
||||
}
|
||||
|
||||
if (empty($elements) && ! empty($modifiers)) {
|
||||
return 'block_with_modifiers';
|
||||
}
|
||||
|
||||
return 'full_bem';
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->type->value,
|
||||
'name' => $this->name,
|
||||
'classes' => $this->getClassNames(),
|
||||
'complexity' => $this->getComplexity(),
|
||||
'metadata' => $this->metadata,
|
||||
'pattern_info' => $this->getPatternInfo(),
|
||||
];
|
||||
}
|
||||
}
|
||||
15
src/Framework/Design/ValueObjects/ComponentPatternType.php
Normal file
15
src/Framework/Design/ValueObjects/ComponentPatternType.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
/**
|
||||
* Enum für Component Pattern Typen
|
||||
*/
|
||||
enum ComponentPatternType: string
|
||||
{
|
||||
case BEM = 'bem';
|
||||
case UTILITY = 'utility';
|
||||
case COMPONENT = 'component';
|
||||
}
|
||||
192
src/Framework/Design/ValueObjects/CssClass.php
Normal file
192
src/Framework/Design/ValueObjects/CssClass.php
Normal file
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
/**
|
||||
* Repräsentiert eine CSS-Klasse
|
||||
*/
|
||||
final readonly class CssClass
|
||||
{
|
||||
public function __construct(
|
||||
public string $name
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromSelector(string $selector): self
|
||||
{
|
||||
// Remove the leading dot from class selector
|
||||
$className = ltrim($selector, '.');
|
||||
|
||||
return new self($className);
|
||||
}
|
||||
|
||||
public function isBemBlock(): bool
|
||||
{
|
||||
// BEM Block: doesn't contain __ or --
|
||||
return ! str_contains($this->name, '__') && ! str_contains($this->name, '--');
|
||||
}
|
||||
|
||||
public function isBemElement(): bool
|
||||
{
|
||||
// BEM Element: contains __ but not --
|
||||
return str_contains($this->name, '__') && ! str_contains($this->name, '--');
|
||||
}
|
||||
|
||||
public function isBemModifier(): bool
|
||||
{
|
||||
// BEM Modifier: contains -- (and possibly __)
|
||||
return str_contains($this->name, '--');
|
||||
}
|
||||
|
||||
public function isUtilityClass(): bool
|
||||
{
|
||||
// Common utility class patterns
|
||||
$utilityPatterns = [
|
||||
'/^(text|bg|p|m|pt|pb|pl|pr|mt|mb|ml|mr|w|h|flex|grid|hidden|block|inline)-/',
|
||||
'/^(sm|md|lg|xl):|:/',
|
||||
'/^(hover|focus|active|disabled):/',
|
||||
];
|
||||
|
||||
foreach ($utilityPatterns as $pattern) {
|
||||
if (preg_match($pattern, $this->name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getBemBlock(): ?string
|
||||
{
|
||||
if ($this->isBemBlock()) {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
// Extract block from element or modifier
|
||||
if (str_contains($this->name, '__')) {
|
||||
return explode('__', $this->name)[0];
|
||||
}
|
||||
|
||||
if (str_contains($this->name, '--')) {
|
||||
return explode('--', $this->name)[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getBemElement(): ?string
|
||||
{
|
||||
if (! $this->isBemElement()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parts = explode('__', $this->name);
|
||||
if (count($parts) >= 2) {
|
||||
$element = $parts[1];
|
||||
// Remove modifier if present
|
||||
if (str_contains($element, '--')) {
|
||||
$element = explode('--', $element)[0];
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getBemModifier(): ?string
|
||||
{
|
||||
if (! $this->isBemModifier()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parts = explode('--', $this->name);
|
||||
if (count($parts) >= 2) {
|
||||
return $parts[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getUtilityCategory(): ?string
|
||||
{
|
||||
$categoryPatterns = [
|
||||
'spacing' => '/^[mp][trblxy]?-\d+$/',
|
||||
'sizing' => '/^[wh]-\d+$/',
|
||||
'typography' => '/^text-(xs|sm|base|lg|xl|\d+xl|center|left|right)$/',
|
||||
'color' => '/^(text|bg|border)-(red|blue|green|gray|yellow|purple|pink|indigo)-\d+$/',
|
||||
'display' => '/^(block|inline|flex|grid|hidden)$/',
|
||||
'flexbox' => '/^(justify|items|self)-(start|end|center|between|around)$/',
|
||||
];
|
||||
|
||||
foreach ($categoryPatterns as $category => $pattern) {
|
||||
if (preg_match($pattern, $this->name)) {
|
||||
return $category;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getComponentType(): ?string
|
||||
{
|
||||
$componentPatterns = [
|
||||
'button' => ['btn', 'button'],
|
||||
'card' => ['card'],
|
||||
'modal' => ['modal', 'dialog'],
|
||||
'form' => ['form', 'input', 'select', 'textarea'],
|
||||
'navigation' => ['nav', 'navbar', 'menu'],
|
||||
'table' => ['table', 'thead', 'tbody', 'tr', 'td', 'th'],
|
||||
'alert' => ['alert', 'message', 'notification'],
|
||||
'badge' => ['badge', 'tag', 'chip'],
|
||||
'dropdown' => ['dropdown', 'select'],
|
||||
'tabs' => ['tab', 'tabs'],
|
||||
'accordion' => ['accordion', 'collapse'],
|
||||
'breadcrumb' => ['breadcrumb'],
|
||||
'pagination' => ['pagination', 'pager'],
|
||||
];
|
||||
|
||||
foreach ($componentPatterns as $componentType => $patterns) {
|
||||
foreach ($patterns as $pattern) {
|
||||
if (str_starts_with($this->name, $pattern) || str_contains($this->name, $pattern)) {
|
||||
return $componentType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getNamingConvention(): string
|
||||
{
|
||||
if ($this->isBemBlock() || $this->isBemElement() || $this->isBemModifier()) {
|
||||
return 'bem';
|
||||
}
|
||||
|
||||
if (preg_match('/^[a-z][a-zA-Z0-9]*$/', $this->name)) {
|
||||
return 'camelCase';
|
||||
}
|
||||
|
||||
if (preg_match('/^[a-z][a-z0-9-]*$/', $this->name)) {
|
||||
return 'kebab-case';
|
||||
}
|
||||
|
||||
if (preg_match('/^[a-z][a-z0-9_]*$/', $this->name)) {
|
||||
return 'snake_case';
|
||||
}
|
||||
|
||||
return 'other';
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public static function fromString(string $className): self
|
||||
{
|
||||
return new self(trim($className, '. '));
|
||||
}
|
||||
}
|
||||
190
src/Framework/Design/ValueObjects/CssClassName.php
Normal file
190
src/Framework/Design/ValueObjects/CssClassName.php
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
/**
|
||||
* Value Object für CSS-Klassennamen
|
||||
*/
|
||||
final readonly class CssClassName
|
||||
{
|
||||
public function __construct(
|
||||
public string $name
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromString(string $className): self
|
||||
{
|
||||
return new self(trim($className, '. '));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob die Klasse BEM-Konvention folgt
|
||||
*/
|
||||
public function isBemBlock(): bool
|
||||
{
|
||||
return preg_match('/^[a-z][a-z0-9-]*$/', $this->name) &&
|
||||
! str_contains($this->name, '__') &&
|
||||
! str_contains($this->name, '--');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob die Klasse ein BEM-Element ist
|
||||
*/
|
||||
public function isBemElement(): bool
|
||||
{
|
||||
return preg_match('/^[a-z][a-z0-9-]*__[a-z][a-z0-9-]*$/', $this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob die Klasse ein BEM-Modifier ist
|
||||
*/
|
||||
public function isBemModifier(): bool
|
||||
{
|
||||
return str_contains($this->name, '--');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert BEM-Block-Namen
|
||||
*/
|
||||
public function getBemBlock(): ?string
|
||||
{
|
||||
if (preg_match('/^([a-z][a-z0-9-]*)/', $this->name, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert BEM-Element-Namen
|
||||
*/
|
||||
public function getBemElement(): ?string
|
||||
{
|
||||
if (preg_match('/__([a-z][a-z0-9-]*)/', $this->name, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert BEM-Modifier-Namen
|
||||
*/
|
||||
public function getBemModifier(): ?string
|
||||
{
|
||||
if (preg_match('/--([a-z][a-z0-9-]*)/', $this->name, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob es eine Utility-Klasse ist (Tailwind-Style)
|
||||
*/
|
||||
public function isUtilityClass(): bool
|
||||
{
|
||||
$utilityPatterns = [
|
||||
'/^[mp][trblxy]?-\d+$/', // Margin/Padding
|
||||
'/^[wh]-\d+$/', // Width/Height
|
||||
'/^text-(xs|sm|base|lg|xl|\d+xl)$/', // Text sizes
|
||||
'/^(text|bg|border)-(red|blue|green|gray|yellow|purple|pink|indigo)-\d+$/', // Colors
|
||||
'/^(block|inline|flex|grid|hidden)$/', // Display
|
||||
'/^(justify|items|self)-(start|end|center|between|around)$/', // Flexbox
|
||||
];
|
||||
|
||||
foreach ($utilityPatterns as $pattern) {
|
||||
if (preg_match($pattern, $this->name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erkennt Utility-Kategorie
|
||||
*/
|
||||
public function getUtilityCategory(): ?string
|
||||
{
|
||||
$categoryPatterns = [
|
||||
'spacing' => '/^[mp][trblxy]?-\d+$/',
|
||||
'sizing' => '/^[wh]-\d+$/',
|
||||
'typography' => '/^text-(xs|sm|base|lg|xl|\d+xl|center|left|right)$/',
|
||||
'color' => '/^(text|bg|border)-(red|blue|green|gray|yellow|purple|pink|indigo)-\d+$/',
|
||||
'display' => '/^(block|inline|flex|grid|hidden)$/',
|
||||
'flexbox' => '/^(justify|items|self)-(start|end|center|between|around)$/',
|
||||
];
|
||||
|
||||
foreach ($categoryPatterns as $category => $pattern) {
|
||||
if (preg_match($pattern, $this->name)) {
|
||||
return $category;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erkennt Component-Typ basierend auf Namen
|
||||
*/
|
||||
public function getComponentType(): ?string
|
||||
{
|
||||
$componentPatterns = [
|
||||
'button' => ['btn', 'button'],
|
||||
'card' => ['card'],
|
||||
'modal' => ['modal', 'dialog'],
|
||||
'form' => ['form', 'input', 'select', 'textarea'],
|
||||
'navigation' => ['nav', 'navbar', 'menu'],
|
||||
'table' => ['table', 'thead', 'tbody', 'tr', 'td', 'th'],
|
||||
'alert' => ['alert', 'message', 'notification'],
|
||||
'badge' => ['badge', 'tag', 'chip'],
|
||||
'dropdown' => ['dropdown', 'select'],
|
||||
'tabs' => ['tab', 'tabs'],
|
||||
'accordion' => ['accordion', 'collapse'],
|
||||
'breadcrumb' => ['breadcrumb'],
|
||||
'pagination' => ['pagination', 'pager'],
|
||||
];
|
||||
|
||||
foreach ($componentPatterns as $componentType => $patterns) {
|
||||
foreach ($patterns as $pattern) {
|
||||
if (str_starts_with($this->name, $pattern) || str_contains($this->name, $pattern)) {
|
||||
return $componentType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysiert Naming-Convention
|
||||
*/
|
||||
public function getNamingConvention(): string
|
||||
{
|
||||
if ($this->isBemBlock() || $this->isBemElement() || $this->isBemModifier()) {
|
||||
return 'bem';
|
||||
}
|
||||
|
||||
if (preg_match('/^[a-z][a-zA-Z0-9]*$/', $this->name)) {
|
||||
return 'camelCase';
|
||||
}
|
||||
|
||||
if (preg_match('/^[a-z][a-z0-9-]*$/', $this->name)) {
|
||||
return 'kebab-case';
|
||||
}
|
||||
|
||||
if (preg_match('/^[a-z][a-z0-9_]*$/', $this->name)) {
|
||||
return 'snake_case';
|
||||
}
|
||||
|
||||
return 'other';
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
285
src/Framework/Design/ValueObjects/CssColor.php
Normal file
285
src/Framework/Design/ValueObjects/CssColor.php
Normal file
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
use App\Framework\Core\ValueObjects\RGBColor;
|
||||
|
||||
/**
|
||||
* Value Object für CSS-Farben mit verschiedenen Formaten
|
||||
*/
|
||||
final readonly class CssColor
|
||||
{
|
||||
public function __construct(
|
||||
public string $originalValue,
|
||||
public ColorFormat $format,
|
||||
public ?RGBColor $rgbColor = null,
|
||||
public ?array $hslValues = null,
|
||||
public ?array $oklchValues = null,
|
||||
public ?string $namedColor = null,
|
||||
public ?string $customPropertyName = null
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromString(string $value): self
|
||||
{
|
||||
$value = trim($value);
|
||||
|
||||
// Hex Color
|
||||
if (preg_match('/^#[0-9A-Fa-f]{3,6}$/', $value)) {
|
||||
try {
|
||||
$rgbColor = RGBColor::fromHex($value);
|
||||
|
||||
return new self($value, ColorFormat::HEX, $rgbColor);
|
||||
} catch (\Exception) {
|
||||
return new self($value, ColorFormat::HEX);
|
||||
}
|
||||
}
|
||||
|
||||
// RGB Color
|
||||
if (preg_match('/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/', $value, $matches)) {
|
||||
$rgbColor = new RGBColor(
|
||||
(int) $matches[1],
|
||||
(int) $matches[2],
|
||||
(int) $matches[3]
|
||||
);
|
||||
|
||||
return new self($value, ColorFormat::RGB, $rgbColor);
|
||||
}
|
||||
|
||||
// RGBA Color
|
||||
if (preg_match('/^rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)$/', $value, $matches)) {
|
||||
$rgbColor = new RGBColor(
|
||||
(int) $matches[1],
|
||||
(int) $matches[2],
|
||||
(int) $matches[3]
|
||||
);
|
||||
|
||||
return new self($value, ColorFormat::RGBA, $rgbColor);
|
||||
}
|
||||
|
||||
// HSL Color
|
||||
if (preg_match('/^hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)$/', $value, $matches)) {
|
||||
$hslValues = [
|
||||
'h' => (int) $matches[1],
|
||||
's' => (int) $matches[2],
|
||||
'l' => (int) $matches[3],
|
||||
];
|
||||
|
||||
return new self($value, ColorFormat::HSL, null, $hslValues);
|
||||
}
|
||||
|
||||
// HSLA Color
|
||||
if (preg_match('/^hsla\((\d+),\s*(\d+)%,\s*(\d+)%,\s*([\d.]+)\)$/', $value, $matches)) {
|
||||
$hslValues = [
|
||||
'h' => (int) $matches[1],
|
||||
's' => (int) $matches[2],
|
||||
'l' => (int) $matches[3],
|
||||
'a' => (float) $matches[4],
|
||||
];
|
||||
|
||||
return new self($value, ColorFormat::HSLA, null, $hslValues);
|
||||
}
|
||||
|
||||
// OKLCH Color
|
||||
if (preg_match('/^oklch\(([\d.]+)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*([\d.]+))?\)$/', $value, $matches)) {
|
||||
$oklchValues = [
|
||||
'l' => (float) $matches[1], // Lightness (0-1)
|
||||
'c' => (float) $matches[2], // Chroma (0-0.4+)
|
||||
'h' => (float) $matches[3], // Hue (0-360)
|
||||
];
|
||||
|
||||
if (isset($matches[4])) {
|
||||
$oklchValues['a'] = (float) $matches[4]; // Alpha
|
||||
}
|
||||
|
||||
return new self($value, ColorFormat::OKLCH, null, null, $oklchValues);
|
||||
}
|
||||
|
||||
// CSS Custom Property
|
||||
if (preg_match('/^var\(--([^)]+)\)$/', $value, $matches)) {
|
||||
return new self($value, ColorFormat::CUSTOM_PROPERTY, null, null, null, null, $matches[1]);
|
||||
}
|
||||
|
||||
// Named Color
|
||||
$namedColors = [
|
||||
'red', 'blue', 'green', 'white', 'black', 'transparent', 'currentColor',
|
||||
'gray', 'grey', 'yellow', 'orange', 'purple', 'pink', 'brown',
|
||||
'cyan', 'magenta', 'lime', 'navy', 'silver', 'gold',
|
||||
];
|
||||
|
||||
if (in_array(strtolower($value), $namedColors)) {
|
||||
return new self($value, ColorFormat::NAMED, null, null, null, strtolower($value));
|
||||
}
|
||||
|
||||
// Fallback - behandle als named color
|
||||
return new self($value, ColorFormat::NAMED, null, null, null, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert die Farbe zu RGB wenn möglich
|
||||
*/
|
||||
public function toRGB(): ?RGBColor
|
||||
{
|
||||
if ($this->rgbColor) {
|
||||
return $this->rgbColor;
|
||||
}
|
||||
|
||||
// HSL zu RGB konvertieren wenn verfügbar
|
||||
if ($this->hslValues && isset($this->hslValues['h'], $this->hslValues['s'], $this->hslValues['l'])) {
|
||||
return $this->hslToRgb(
|
||||
$this->hslValues['h'],
|
||||
$this->hslValues['s'] / 100,
|
||||
$this->hslValues['l'] / 100
|
||||
);
|
||||
}
|
||||
|
||||
// OKLCH zu RGB konvertieren wenn verfügbar
|
||||
if ($this->oklchValues && isset($this->oklchValues['l'], $this->oklchValues['c'], $this->oklchValues['h'])) {
|
||||
return $this->oklchToRgb(
|
||||
$this->oklchValues['l'],
|
||||
$this->oklchValues['c'],
|
||||
$this->oklchValues['h']
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Hex-Repräsentation zurück wenn möglich
|
||||
*/
|
||||
public function toHex(): ?string
|
||||
{
|
||||
$rgb = $this->toRGB();
|
||||
if ($rgb) {
|
||||
return $rgb->toHex();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob die Farbe transparent ist
|
||||
*/
|
||||
public function isTransparent(): bool
|
||||
{
|
||||
return $this->namedColor === 'transparent' ||
|
||||
$this->originalValue === 'transparent' ||
|
||||
($this->format === ColorFormat::RGBA && str_contains($this->originalValue, ', 0)')) ||
|
||||
($this->format === ColorFormat::HSLA && str_contains($this->originalValue, ', 0)'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob die Farbe eine Custom Property ist
|
||||
*/
|
||||
public function isCustomProperty(): bool
|
||||
{
|
||||
return $this->format === ColorFormat::CUSTOM_PROPERTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Custom Property Namen zurück
|
||||
*/
|
||||
public function getCustomPropertyName(): ?string
|
||||
{
|
||||
return $this->customPropertyName;
|
||||
}
|
||||
|
||||
private function hslToRgb(int $h, float $s, float $l): RGBColor
|
||||
{
|
||||
$h = $h / 360;
|
||||
|
||||
if ($s === 0.0) {
|
||||
$r = $g = $b = $l; // Grayscale
|
||||
} else {
|
||||
$hue2rgb = function ($p, $q, $t) {
|
||||
if ($t < 0) {
|
||||
$t += 1;
|
||||
}
|
||||
if ($t > 1) {
|
||||
$t -= 1;
|
||||
}
|
||||
if ($t < 1 / 6) {
|
||||
return $p + ($q - $p) * 6 * $t;
|
||||
}
|
||||
if ($t < 1 / 2) {
|
||||
return $q;
|
||||
}
|
||||
if ($t < 2 / 3) {
|
||||
return $p + ($q - $p) * (2 / 3 - $t) * 6;
|
||||
}
|
||||
|
||||
return $p;
|
||||
};
|
||||
|
||||
$q = $l < 0.5 ? $l * (1 + $s) : $l + $s - $l * $s;
|
||||
$p = 2 * $l - $q;
|
||||
|
||||
$r = $hue2rgb($p, $q, $h + 1 / 3);
|
||||
$g = $hue2rgb($p, $q, $h);
|
||||
$b = $hue2rgb($p, $q, $h - 1 / 3);
|
||||
}
|
||||
|
||||
return new RGBColor(
|
||||
(int) round($r * 255),
|
||||
(int) round($g * 255),
|
||||
(int) round($b * 255)
|
||||
);
|
||||
}
|
||||
|
||||
private function oklchToRgb(float $l, float $c, float $h): RGBColor
|
||||
{
|
||||
// OKLCH zu OKLab
|
||||
$hRad = deg2rad($h);
|
||||
$a = $c * cos($hRad);
|
||||
$b = $c * sin($hRad);
|
||||
|
||||
// OKLab zu Linear RGB (vereinfachte Approximation)
|
||||
// Für eine exakte Konvertierung wären komplexere Matrizen-Operationen nötig
|
||||
$lRgb = $l + 0.3963377774 * $a + 0.2158037573 * $b;
|
||||
$mRgb = $l - 0.1055613458 * $a - 0.0638541728 * $b;
|
||||
$sRgb = $l - 0.0894841775 * $a - 1.2914855480 * $b;
|
||||
|
||||
// Cube root für Linear RGB
|
||||
$lRgb = $this->cubeRoot($lRgb);
|
||||
$mRgb = $this->cubeRoot($mRgb);
|
||||
$sRgb = $this->cubeRoot($sRgb);
|
||||
|
||||
// Linear RGB zu sRGB (vereinfacht)
|
||||
$r = +4.0767416621 * $lRgb - 3.3077115913 * $mRgb + 0.2309699292 * $sRgb;
|
||||
$g = -1.2684380046 * $lRgb + 2.6097574011 * $mRgb - 0.3413193965 * $sRgb;
|
||||
$b = -0.0041960863 * $lRgb - 0.7034186147 * $mRgb + 1.7076147010 * $sRgb;
|
||||
|
||||
// Gamma correction und Clamping
|
||||
$r = $this->gammaCorrect($r);
|
||||
$g = $this->gammaCorrect($g);
|
||||
$b = $this->gammaCorrect($b);
|
||||
|
||||
return new RGBColor(
|
||||
max(0, min(255, (int) round($r * 255))),
|
||||
max(0, min(255, (int) round($g * 255))),
|
||||
max(0, min(255, (int) round($b * 255)))
|
||||
);
|
||||
}
|
||||
|
||||
private function cubeRoot(float $value): float
|
||||
{
|
||||
return $value >= 0 ? pow($value, 1 / 3) : -pow(-$value, 1 / 3);
|
||||
}
|
||||
|
||||
private function gammaCorrect(float $value): float
|
||||
{
|
||||
if ($value >= 0.0031308) {
|
||||
return 1.055 * pow($value, 1 / 2.4) - 0.055;
|
||||
} else {
|
||||
return 12.92 * $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->originalValue;
|
||||
}
|
||||
}
|
||||
263
src/Framework/Design/ValueObjects/CssParseResult.php
Normal file
263
src/Framework/Design/ValueObjects/CssParseResult.php
Normal file
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
use App\Framework\Filesystem\FilePath;
|
||||
|
||||
/**
|
||||
* Ergebnis des CSS-Parsing-Prozesses
|
||||
*/
|
||||
final readonly class CssParseResult
|
||||
{
|
||||
/**
|
||||
* @param CssRule[] $rules
|
||||
* @param DesignToken[] $customProperties
|
||||
* @param CssClassName[] $classNames
|
||||
*/
|
||||
public function __construct(
|
||||
public ?FilePath $sourceFile,
|
||||
public array $rules,
|
||||
public array $customProperties,
|
||||
public array $classNames,
|
||||
public string $rawContent
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle gefundenen Selektoren zurück
|
||||
* @return CssSelector[]
|
||||
*/
|
||||
public function getAllSelectors(): array
|
||||
{
|
||||
$selectors = [];
|
||||
|
||||
foreach ($this->rules as $rule) {
|
||||
foreach ($rule->selectors as $selector) {
|
||||
$selectors[] = $selector;
|
||||
}
|
||||
}
|
||||
|
||||
return $selectors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle gefundenen Properties zurück
|
||||
* @return CssProperty[]
|
||||
*/
|
||||
public function getAllProperties(): array
|
||||
{
|
||||
$properties = [];
|
||||
|
||||
foreach ($this->rules as $rule) {
|
||||
foreach ($rule->properties as $property) {
|
||||
$properties[] = $property;
|
||||
}
|
||||
}
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtert Regeln nach Selektor-Typ
|
||||
*/
|
||||
public function getRulesBySelectorType(CssSelectorType $type): array
|
||||
{
|
||||
$matchingRules = [];
|
||||
|
||||
foreach ($this->rules as $rule) {
|
||||
foreach ($rule->selectors as $selector) {
|
||||
if ($selector->getType() === $type) {
|
||||
$matchingRules[] = $rule;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $matchingRules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtert Properties nach Kategorie
|
||||
*/
|
||||
public function getPropertiesByCategory(CssPropertyCategory $category): array
|
||||
{
|
||||
return array_filter(
|
||||
$this->getAllProperties(),
|
||||
fn (CssProperty $property) => $property->getCategory() === $category
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtert Custom Properties nach Typ
|
||||
*/
|
||||
public function getDesignTokensByType(DesignTokenType $type): array
|
||||
{
|
||||
return array_filter(
|
||||
$this->customProperties,
|
||||
fn (DesignToken $token) => $token->type === $type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtert CSS-Klassen nach Pattern
|
||||
*/
|
||||
public function getClassNamesByPattern(string $pattern): array
|
||||
{
|
||||
return array_filter(
|
||||
$this->classNames,
|
||||
fn (CssClassName $className) => str_contains($className->name, $pattern)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle BEM-Klassen zurück
|
||||
*/
|
||||
public function getBemClasses(): array
|
||||
{
|
||||
return array_filter(
|
||||
$this->classNames,
|
||||
fn (CssClassName $className) =>
|
||||
$className->isBemBlock() ||
|
||||
$className->isBemElement() ||
|
||||
$className->isBemModifier()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle Utility-Klassen zurück
|
||||
*/
|
||||
public function getUtilityClasses(): array
|
||||
{
|
||||
return array_filter(
|
||||
$this->classNames,
|
||||
fn (CssClassName $className) => $className->isUtilityClass()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysiert verwendete Farben
|
||||
*/
|
||||
public function getColorAnalysis(): array
|
||||
{
|
||||
$colors = [];
|
||||
$colorProperties = $this->getPropertiesByCategory(CssPropertyCategory::COLOR);
|
||||
|
||||
foreach ($colorProperties as $property) {
|
||||
$color = $property->toColor();
|
||||
if ($color) {
|
||||
$colors[] = [
|
||||
'property' => $property->name,
|
||||
'color' => $color,
|
||||
'format' => $color->format->value,
|
||||
'hex' => $color->toHex(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysiert Naming-Konventionen
|
||||
*/
|
||||
public function getNamingConventionAnalysis(): array
|
||||
{
|
||||
$conventions = [
|
||||
'bem' => 0,
|
||||
'camelCase' => 0,
|
||||
'kebab-case' => 0,
|
||||
'snake_case' => 0,
|
||||
'other' => 0,
|
||||
'violations' => [],
|
||||
];
|
||||
|
||||
foreach ($this->classNames as $className) {
|
||||
$convention = $className->getNamingConvention();
|
||||
|
||||
if (isset($conventions[$convention])) {
|
||||
$conventions[$convention]++;
|
||||
} else {
|
||||
$conventions['other']++;
|
||||
$conventions['violations'][] = $className->name;
|
||||
}
|
||||
}
|
||||
|
||||
$conventions['total_classes'] = count($this->classNames);
|
||||
$conventions['dominant_convention'] = $this->getDominantConvention($conventions);
|
||||
|
||||
return $conventions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistiken über die geparsten Daten
|
||||
*/
|
||||
public function getStatistics(): array
|
||||
{
|
||||
return [
|
||||
'source_file' => $this->sourceFile?->toString(),
|
||||
'total_rules' => count($this->rules),
|
||||
'total_selectors' => count($this->getAllSelectors()),
|
||||
'total_properties' => count($this->getAllProperties()),
|
||||
'design_tokens' => count($this->customProperties),
|
||||
'class_names' => count($this->classNames),
|
||||
'content_size_bytes' => strlen($this->rawContent),
|
||||
'selector_types' => $this->getSelectorTypeStats(),
|
||||
'property_categories' => $this->getPropertyCategoryStats(),
|
||||
'token_types' => $this->getTokenTypeStats(),
|
||||
];
|
||||
}
|
||||
|
||||
private function getDominantConvention(array $conventions): string
|
||||
{
|
||||
$max = 0;
|
||||
$dominant = 'mixed';
|
||||
|
||||
foreach (['bem', 'camelCase', 'kebab-case', 'snake_case'] as $convention) {
|
||||
if ($conventions[$convention] > $max) {
|
||||
$max = $conventions[$convention];
|
||||
$dominant = $convention;
|
||||
}
|
||||
}
|
||||
|
||||
return $dominant;
|
||||
}
|
||||
|
||||
private function getSelectorTypeStats(): array
|
||||
{
|
||||
$stats = [];
|
||||
|
||||
foreach ($this->getAllSelectors() as $selector) {
|
||||
$type = $selector->getType()->value;
|
||||
$stats[$type] = ($stats[$type] ?? 0) + 1;
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
private function getPropertyCategoryStats(): array
|
||||
{
|
||||
$stats = [];
|
||||
|
||||
foreach ($this->getAllProperties() as $property) {
|
||||
$category = $property->getCategory()->value;
|
||||
$stats[$category] = ($stats[$category] ?? 0) + 1;
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
private function getTokenTypeStats(): array
|
||||
{
|
||||
$stats = [];
|
||||
|
||||
foreach ($this->customProperties as $token) {
|
||||
$type = $token->type->value;
|
||||
$stats[$type] = ($stats[$type] ?? 0) + 1;
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
}
|
||||
149
src/Framework/Design/ValueObjects/CssProperty.php
Normal file
149
src/Framework/Design/ValueObjects/CssProperty.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
/**
|
||||
* Repräsentiert eine CSS-Property mit Namen und Wert
|
||||
*/
|
||||
final readonly class CssProperty
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $value
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Kategorisiert die Property
|
||||
*/
|
||||
public function getCategory(): CssPropertyCategory
|
||||
{
|
||||
$colorProperties = [
|
||||
'color', 'background-color', 'border-color', 'outline-color',
|
||||
'text-decoration-color', 'caret-color', 'column-rule-color',
|
||||
];
|
||||
|
||||
$spacingProperties = [
|
||||
'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
|
||||
'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
|
||||
'gap', 'row-gap', 'column-gap',
|
||||
];
|
||||
|
||||
$typographyProperties = [
|
||||
'font-family', 'font-size', 'font-weight', 'font-style',
|
||||
'line-height', 'letter-spacing', 'text-align', 'text-decoration',
|
||||
'text-transform', 'font-variant',
|
||||
];
|
||||
|
||||
$layoutProperties = [
|
||||
'display', 'position', 'top', 'right', 'bottom', 'left',
|
||||
'width', 'height', 'max-width', 'max-height', 'min-width', 'min-height',
|
||||
'flex', 'flex-direction', 'flex-wrap', 'justify-content', 'align-items',
|
||||
'grid', 'grid-template-columns', 'grid-template-rows',
|
||||
];
|
||||
|
||||
$borderProperties = [
|
||||
'border', 'border-top', 'border-right', 'border-bottom', 'border-left',
|
||||
'border-width', 'border-style', 'border-radius', 'outline',
|
||||
];
|
||||
|
||||
$animationProperties = [
|
||||
'transition', 'animation', 'transform', 'transition-duration',
|
||||
'transition-property', 'transition-timing-function', 'transition-delay',
|
||||
];
|
||||
|
||||
if (in_array($this->name, $colorProperties)) {
|
||||
return CssPropertyCategory::COLOR;
|
||||
}
|
||||
|
||||
if (in_array($this->name, $spacingProperties)) {
|
||||
return CssPropertyCategory::SPACING;
|
||||
}
|
||||
|
||||
if (in_array($this->name, $typographyProperties)) {
|
||||
return CssPropertyCategory::TYPOGRAPHY;
|
||||
}
|
||||
|
||||
if (in_array($this->name, $layoutProperties)) {
|
||||
return CssPropertyCategory::LAYOUT;
|
||||
}
|
||||
|
||||
if (in_array($this->name, $borderProperties)) {
|
||||
return CssPropertyCategory::BORDER;
|
||||
}
|
||||
|
||||
if (in_array($this->name, $animationProperties)) {
|
||||
return CssPropertyCategory::ANIMATION;
|
||||
}
|
||||
|
||||
return CssPropertyCategory::OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob die Property einen CSS Custom Property Wert verwendet
|
||||
*/
|
||||
public function usesCustomProperty(): bool
|
||||
{
|
||||
return str_contains($this->value, 'var(--');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert Custom Property Namen aus dem Wert
|
||||
*/
|
||||
public function getCustomPropertyReferences(): array
|
||||
{
|
||||
preg_match_all('/var\(--([^)]+)\)/', $this->value, $matches);
|
||||
|
||||
return $matches[1] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert zu CssColor wenn es eine Farbproperty ist
|
||||
*/
|
||||
public function toColor(): ?CssColor
|
||||
{
|
||||
if ($this->getCategory() !== CssPropertyCategory::COLOR) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return CssColor::fromString($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert numerischen Wert und Einheit
|
||||
*/
|
||||
public function parseNumericValue(): ?array
|
||||
{
|
||||
if (preg_match('/^(-?\d*\.?\d+)([a-zA-Z%]*)$/', $this->value, $matches)) {
|
||||
return [
|
||||
'value' => (float) $matches[1],
|
||||
'unit' => $matches[2] ?: null,
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob die Property wichtig ist (!important)
|
||||
*/
|
||||
public function isImportant(): bool
|
||||
{
|
||||
return str_contains($this->value, '!important');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Wert ohne !important zurück
|
||||
*/
|
||||
public function getValueWithoutImportant(): string
|
||||
{
|
||||
return trim(str_replace('!important', '', $this->value));
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return "{$this->name}: {$this->value}";
|
||||
}
|
||||
}
|
||||
20
src/Framework/Design/ValueObjects/CssPropertyCategory.php
Normal file
20
src/Framework/Design/ValueObjects/CssPropertyCategory.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
/**
|
||||
* Enum für CSS Property Kategorien
|
||||
*/
|
||||
enum CssPropertyCategory: string
|
||||
{
|
||||
case COLOR = 'color';
|
||||
case SPACING = 'spacing';
|
||||
case TYPOGRAPHY = 'typography';
|
||||
case LAYOUT = 'layout';
|
||||
case BORDER = 'border';
|
||||
case ANIMATION = 'animation';
|
||||
case TRANSFORM = 'transform';
|
||||
case OTHER = 'other';
|
||||
}
|
||||
190
src/Framework/Design/ValueObjects/CssRule.php
Normal file
190
src/Framework/Design/ValueObjects/CssRule.php
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
/**
|
||||
* Repräsentiert eine CSS-Regel mit Selektoren und Properties
|
||||
*/
|
||||
final readonly class CssRule
|
||||
{
|
||||
/**
|
||||
* @param CssSelector[] $selectors
|
||||
* @param CssProperty[] $properties
|
||||
*/
|
||||
public function __construct(
|
||||
public array $selectors,
|
||||
public array $properties
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle Selektor-Strings zurück
|
||||
*/
|
||||
public function getSelectorStrings(): array
|
||||
{
|
||||
return array_map(fn (CssSelector $selector) => $selector->value, $this->selectors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle Property-Namen zurück
|
||||
*/
|
||||
public function getPropertyNames(): array
|
||||
{
|
||||
return array_map(fn (CssProperty $property) => $property->name, $this->properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sucht eine Property nach Namen
|
||||
*/
|
||||
public function getProperty(string $name): ?CssProperty
|
||||
{
|
||||
foreach ($this->properties as $property) {
|
||||
if ($property->name === $name) {
|
||||
return $property;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt alle Properties einer bestimmten Kategorie zurück
|
||||
*/
|
||||
public function getPropertiesByCategory(CssPropertyCategory $category): array
|
||||
{
|
||||
return array_filter(
|
||||
$this->properties,
|
||||
fn (CssProperty $property) => $property->getCategory() === $category
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob die Regel einen bestimmten Selektor enthält
|
||||
*/
|
||||
public function hasSelector(string $selectorValue): bool
|
||||
{
|
||||
foreach ($this->selectors as $selector) {
|
||||
if ($selector->value === $selectorValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob die Regel eine bestimmte Property enthält
|
||||
*/
|
||||
public function hasProperty(string $propertyName): bool
|
||||
{
|
||||
return $this->getProperty($propertyName) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob die Regel Custom Properties verwendet
|
||||
*/
|
||||
public function usesCustomProperties(): bool
|
||||
{
|
||||
foreach ($this->properties as $property) {
|
||||
if ($property->usesCustomProperty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert alle Custom Property Referenzen
|
||||
*/
|
||||
public function getCustomPropertyReferences(): array
|
||||
{
|
||||
$references = [];
|
||||
|
||||
foreach ($this->properties as $property) {
|
||||
$propertyRefs = $property->getCustomPropertyReferences();
|
||||
$references = array_merge($references, $propertyRefs);
|
||||
}
|
||||
|
||||
return array_unique($references);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Regel als CSS-String zurück
|
||||
*/
|
||||
public function toCssString(): string
|
||||
{
|
||||
$selectors = implode(', ', $this->getSelectorStrings());
|
||||
$properties = [];
|
||||
|
||||
foreach ($this->properties as $property) {
|
||||
$properties[] = " {$property->name}: {$property->value};";
|
||||
}
|
||||
|
||||
$propertiesString = implode("\n", $properties);
|
||||
|
||||
return "$selectors {\n$propertiesString\n}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysiert die Spezifität der Selektoren
|
||||
*/
|
||||
public function getSpecificityAnalysis(): array
|
||||
{
|
||||
$analysis = [];
|
||||
|
||||
foreach ($this->selectors as $selector) {
|
||||
$analysis[] = [
|
||||
'selector' => $selector->value,
|
||||
'specificity' => $selector->calculateSpecificity(),
|
||||
'type' => $selector->getType()->value,
|
||||
];
|
||||
}
|
||||
|
||||
return $analysis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kategorisiert die Regel basierend auf Selektoren und Properties
|
||||
*/
|
||||
public function categorize(): array
|
||||
{
|
||||
$selectorTypes = [];
|
||||
$propertyCategories = [];
|
||||
|
||||
foreach ($this->selectors as $selector) {
|
||||
$selectorTypes[] = $selector->getType()->value;
|
||||
}
|
||||
|
||||
foreach ($this->properties as $property) {
|
||||
$propertyCategories[] = $property->getCategory()->value;
|
||||
}
|
||||
|
||||
return [
|
||||
'selector_types' => array_unique($selectorTypes),
|
||||
'property_categories' => array_unique($propertyCategories),
|
||||
'uses_custom_properties' => $this->usesCustomProperties(),
|
||||
'complexity' => $this->calculateComplexity(),
|
||||
];
|
||||
}
|
||||
|
||||
private function calculateComplexity(): string
|
||||
{
|
||||
$selectorCount = count($this->selectors);
|
||||
$propertyCount = count($this->properties);
|
||||
$totalSpecificity = array_sum(array_map(
|
||||
fn (CssSelector $selector) => $selector->calculateSpecificity(),
|
||||
$this->selectors
|
||||
));
|
||||
|
||||
$complexityScore = $selectorCount + $propertyCount + ($totalSpecificity / 10);
|
||||
|
||||
return match(true) {
|
||||
$complexityScore <= 5 => 'simple',
|
||||
$complexityScore <= 15 => 'moderate',
|
||||
default => 'complex'
|
||||
};
|
||||
}
|
||||
}
|
||||
114
src/Framework/Design/ValueObjects/CssSelector.php
Normal file
114
src/Framework/Design/ValueObjects/CssSelector.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
/**
|
||||
* Repräsentiert einen CSS-Selektor
|
||||
*/
|
||||
final readonly class CssSelector
|
||||
{
|
||||
public function __construct(
|
||||
public string $value
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromString(string $selector): self
|
||||
{
|
||||
return new self(trim($selector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet die CSS-Spezifität des Selektors
|
||||
*/
|
||||
public function calculateSpecificity(): int
|
||||
{
|
||||
$specificity = 0;
|
||||
|
||||
// IDs (#id) = 100
|
||||
preg_match_all('/#[a-zA-Z][\w-]*/', $this->value, $ids);
|
||||
$specificity += count($ids[0]) * 100;
|
||||
|
||||
// Classes (.class), Attributes ([attr]) und Pseudo-Classes (:hover) = 10
|
||||
preg_match_all('/\.[a-zA-Z][\w-]*/', $this->value, $classes);
|
||||
$specificity += count($classes[0]) * 10;
|
||||
|
||||
preg_match_all('/\[[^\]]*\]/', $this->value, $attributes);
|
||||
$specificity += count($attributes[0]) * 10;
|
||||
|
||||
preg_match_all('/:(?!not\(|where\(|is\()[a-zA-Z][\w-]*/', $this->value, $pseudoClasses);
|
||||
$specificity += count($pseudoClasses[0]) * 10;
|
||||
|
||||
// Elements (div, span) und Pseudo-Elements (::before) = 1
|
||||
$elements = preg_replace('/[#.\[\]:][^#.\[\]\s]*/', '', $this->value);
|
||||
preg_match_all('/[a-zA-Z][\w-]*/', $elements, $elementMatches);
|
||||
$specificity += count($elementMatches[0]);
|
||||
|
||||
return $specificity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysiert den Selektor-Typ
|
||||
*/
|
||||
public function getType(): CssSelectorType
|
||||
{
|
||||
if (str_starts_with($this->value, '.')) {
|
||||
return CssSelectorType::CLASS_SELECTOR;
|
||||
}
|
||||
|
||||
if (str_starts_with($this->value, '#')) {
|
||||
return CssSelectorType::ID;
|
||||
}
|
||||
|
||||
if (str_contains($this->value, '[')) {
|
||||
return CssSelectorType::ATTRIBUTE;
|
||||
}
|
||||
|
||||
if (str_contains($this->value, ':')) {
|
||||
return CssSelectorType::PSEUDO;
|
||||
}
|
||||
|
||||
if (preg_match('/^[a-zA-Z][\w-]*$/', $this->value)) {
|
||||
return CssSelectorType::ELEMENT;
|
||||
}
|
||||
|
||||
return CssSelectorType::COMPLEX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert alle CSS-Klassennamen aus dem Selektor
|
||||
*/
|
||||
public function extractClasses(): array
|
||||
{
|
||||
preg_match_all('/\.([a-zA-Z][\w-]*)/', $this->value, $matches);
|
||||
|
||||
return $matches[1] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert alle IDs aus dem Selektor
|
||||
*/
|
||||
public function extractIds(): array
|
||||
{
|
||||
preg_match_all('/#([a-zA-Z][\w-]*)/', $this->value, $matches);
|
||||
|
||||
return $matches[1] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert alle Element-Namen aus dem Selektor
|
||||
*/
|
||||
public function extractElements(): array
|
||||
{
|
||||
$cleaned = preg_replace('/[#.\[\]:][^#.\[\]\s]*/', '', $this->value);
|
||||
preg_match_all('/[a-zA-Z][\w-]*/', $cleaned, $matches);
|
||||
|
||||
return array_filter($matches[0], fn ($element) => ! empty($element));
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
18
src/Framework/Design/ValueObjects/CssSelectorType.php
Normal file
18
src/Framework/Design/ValueObjects/CssSelectorType.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
/**
|
||||
* Enum für CSS Selektor Typen
|
||||
*/
|
||||
enum CssSelectorType: string
|
||||
{
|
||||
case CLASS_SELECTOR = 'class';
|
||||
case ID = 'id';
|
||||
case ELEMENT = 'element';
|
||||
case ATTRIBUTE = 'attribute';
|
||||
case PSEUDO = 'pseudo';
|
||||
case COMPLEX = 'complex';
|
||||
}
|
||||
89
src/Framework/Design/ValueObjects/CustomProperty.php
Normal file
89
src/Framework/Design/ValueObjects/CustomProperty.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
/**
|
||||
* Repräsentiert eine CSS Custom Property (CSS Variable)
|
||||
*/
|
||||
final readonly class CustomProperty
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $value
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromDeclaration(string $declaration): self
|
||||
{
|
||||
// Parse "--property-name: value" format
|
||||
if (preg_match('/--([^:]+):\s*([^;]+)/', $declaration, $matches)) {
|
||||
return new self(trim($matches[1]), trim($matches[2]));
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Invalid CSS custom property declaration');
|
||||
}
|
||||
|
||||
public function hasValueType(string $type): bool
|
||||
{
|
||||
return match($type) {
|
||||
'color' => $this->isColorValue(),
|
||||
'size' => $this->isSizeValue(),
|
||||
'number' => $this->isNumberValue(),
|
||||
default => false
|
||||
};
|
||||
}
|
||||
|
||||
public function getValueAs(string $type): mixed
|
||||
{
|
||||
return match($type) {
|
||||
'color' => $this->getColorValue(),
|
||||
'size' => $this->getSizeValue(),
|
||||
'number' => $this->getNumberValue(),
|
||||
default => $this->value
|
||||
};
|
||||
}
|
||||
|
||||
private function isColorValue(): bool
|
||||
{
|
||||
return preg_match('/^(#[0-9a-fA-F]{3,8}|rgb|hsl|oklch|color)/', $this->value) === 1;
|
||||
}
|
||||
|
||||
private function isSizeValue(): bool
|
||||
{
|
||||
return preg_match('/^\d+(\.\d+)?(px|em|rem|%|vh|vw)$/', $this->value) === 1;
|
||||
}
|
||||
|
||||
private function isNumberValue(): bool
|
||||
{
|
||||
return is_numeric($this->value);
|
||||
}
|
||||
|
||||
private function getColorValue(): CssColor
|
||||
{
|
||||
if ($this->isColorValue()) {
|
||||
$format = match(true) {
|
||||
str_starts_with($this->value, '#') => ColorFormat::HEX,
|
||||
str_starts_with($this->value, 'rgb') => ColorFormat::RGB,
|
||||
str_starts_with($this->value, 'hsl') => ColorFormat::HSL,
|
||||
str_starts_with($this->value, 'oklch') => ColorFormat::OKLCH,
|
||||
default => ColorFormat::HEX
|
||||
};
|
||||
|
||||
return new CssColor($this->value, $format);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Value is not a color');
|
||||
}
|
||||
|
||||
private function getSizeValue(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
private function getNumberValue(): float
|
||||
{
|
||||
return (float) $this->value;
|
||||
}
|
||||
}
|
||||
220
src/Framework/Design/ValueObjects/DesignSystemAnalysis.php
Normal file
220
src/Framework/Design/ValueObjects/DesignSystemAnalysis.php
Normal file
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
use App\Framework\Design\Analyzer\ColorAnalysisResult;
|
||||
use App\Framework\Design\Analyzer\ComponentDetectionResult;
|
||||
use App\Framework\Design\Analyzer\ConventionCheckResult;
|
||||
use App\Framework\Design\Analyzer\TokenAnalysisResult;
|
||||
|
||||
/**
|
||||
* Complete Design System Analysis Result
|
||||
*/
|
||||
final readonly class DesignSystemAnalysis
|
||||
{
|
||||
public function __construct(
|
||||
public TokenAnalysisResult $tokenAnalysis,
|
||||
public ColorAnalysisResult $colorAnalysis,
|
||||
public ComponentDetectionResult $componentAnalysis,
|
||||
public ConventionCheckResult $conventionAnalysis,
|
||||
public array $metadata = []
|
||||
) {
|
||||
}
|
||||
|
||||
public static function empty(): self
|
||||
{
|
||||
return new self(
|
||||
tokenAnalysis: new TokenAnalysisResult(0, [], [], [], [], [], []),
|
||||
colorAnalysis: new ColorAnalysisResult(0, [], [], [], [], []),
|
||||
componentAnalysis: new ComponentDetectionResult(0, [], [], [], [], []),
|
||||
conventionAnalysis: new ConventionCheckResult(100, [], [], [], 'none'),
|
||||
metadata: []
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the overall design system maturity level
|
||||
*/
|
||||
public function getMaturityLevel(): string
|
||||
{
|
||||
$score = $this->getOverallDesignSystemScore();
|
||||
|
||||
return match(true) {
|
||||
$score >= 90 => 'Mature',
|
||||
$score >= 70 => 'Established',
|
||||
$score >= 50 => 'Developing',
|
||||
$score >= 30 => 'Emerging',
|
||||
default => 'Basic'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates overall design system score (0-100)
|
||||
*/
|
||||
public function getOverallDesignSystemScore(): float
|
||||
{
|
||||
$tokenCoverage = $this->tokenAnalysis->getTokenCoverage();
|
||||
$tokenScore = $tokenCoverage['usage_percentage'] ?? 0;
|
||||
$colorScore = $this->colorAnalysis->getConsistencyScore();
|
||||
$componentScore = $this->componentAnalysis->getConsistencyScore();
|
||||
$conventionScore = $this->conventionAnalysis->overallScore;
|
||||
|
||||
return ($tokenScore + $colorScore + $componentScore + $conventionScore) / 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets critical issues that need immediate attention
|
||||
*/
|
||||
public function getCriticalIssues(): array
|
||||
{
|
||||
$issues = [];
|
||||
|
||||
// Token issues
|
||||
$tokenCoverage = $this->tokenAnalysis->getTokenCoverage();
|
||||
if (($tokenCoverage['usage_percentage'] ?? 0) < 30) {
|
||||
$issues[] = [
|
||||
'type' => 'tokens',
|
||||
'severity' => 'critical',
|
||||
'message' => 'Very low design token usage',
|
||||
'impact' => 'Inconsistent styling and maintenance difficulties',
|
||||
];
|
||||
}
|
||||
|
||||
// Color issues - check contrast compliance
|
||||
if ($this->colorAnalysis->getContrastComplianceScore() < 80) {
|
||||
$issues[] = [
|
||||
'type' => 'colors',
|
||||
'severity' => 'critical',
|
||||
'message' => 'WCAG accessibility violations found',
|
||||
'impact' => 'Poor accessibility for users',
|
||||
];
|
||||
}
|
||||
|
||||
// Convention issues
|
||||
if ($this->conventionAnalysis->overallScore < 40) {
|
||||
$issues[] = [
|
||||
'type' => 'conventions',
|
||||
'severity' => 'critical',
|
||||
'message' => 'Poor naming convention consistency',
|
||||
'impact' => 'Decreased developer productivity and maintenance',
|
||||
];
|
||||
}
|
||||
|
||||
return $issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets quick wins for easy improvements
|
||||
*/
|
||||
public function getQuickWins(): array
|
||||
{
|
||||
$wins = [];
|
||||
|
||||
// Duplicate color removal
|
||||
if (count($this->colorAnalysis->duplicateColors) > 0) {
|
||||
$wins[] = [
|
||||
'type' => 'colors',
|
||||
'effort' => 'low',
|
||||
'impact' => 'medium',
|
||||
'message' => 'Remove duplicate color values',
|
||||
'action' => 'Consolidate ' . count($this->colorAnalysis->duplicateColors) . ' duplicate colors',
|
||||
];
|
||||
}
|
||||
|
||||
// Utility class organization based on pattern diversity
|
||||
if ($this->componentAnalysis->getPatternDiversity() > 80) {
|
||||
$wins[] = [
|
||||
'type' => 'components',
|
||||
'effort' => 'low',
|
||||
'impact' => 'high',
|
||||
'message' => 'Standardize component patterns',
|
||||
'action' => 'Choose one primary CSS methodology for better consistency',
|
||||
];
|
||||
}
|
||||
|
||||
return $wins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets development roadmap based on analysis
|
||||
*/
|
||||
public function getDevelopmentRoadmap(): array
|
||||
{
|
||||
$roadmap = [];
|
||||
|
||||
$score = $this->getOverallDesignSystemScore();
|
||||
|
||||
if ($score < 30) {
|
||||
$roadmap[] = [
|
||||
'phase' => 1,
|
||||
'title' => 'Foundation Setup',
|
||||
'priority' => 'critical',
|
||||
'tasks' => [
|
||||
'Establish core design tokens',
|
||||
'Define color palette',
|
||||
'Set naming conventions',
|
||||
'Create basic component library',
|
||||
],
|
||||
'timeline' => '2-4 weeks',
|
||||
];
|
||||
}
|
||||
|
||||
if ($score >= 30 && $score < 60) {
|
||||
$roadmap[] = [
|
||||
'phase' => 2,
|
||||
'title' => 'System Expansion',
|
||||
'priority' => 'high',
|
||||
'tasks' => [
|
||||
'Expand token coverage',
|
||||
'Improve component organization',
|
||||
'Add responsive design tokens',
|
||||
'Implement consistent spacing scale',
|
||||
],
|
||||
'timeline' => '4-6 weeks',
|
||||
];
|
||||
}
|
||||
|
||||
if ($score >= 60) {
|
||||
$roadmap[] = [
|
||||
'phase' => 3,
|
||||
'title' => 'Optimization & Polish',
|
||||
'priority' => 'medium',
|
||||
'tasks' => [
|
||||
'Optimize token usage',
|
||||
'Refine component APIs',
|
||||
'Add advanced theming',
|
||||
'Implement design system documentation',
|
||||
],
|
||||
'timeline' => '6-8 weeks',
|
||||
];
|
||||
}
|
||||
|
||||
return $roadmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports analysis as array for JSON serialization
|
||||
*/
|
||||
public function exportReport(): array
|
||||
{
|
||||
return [
|
||||
'overall_score' => $this->getOverallDesignSystemScore(),
|
||||
'maturity_level' => $this->getMaturityLevel(),
|
||||
'critical_issues' => $this->getCriticalIssues(),
|
||||
'quick_wins' => $this->getQuickWins(),
|
||||
'roadmap' => $this->getDevelopmentRoadmap(),
|
||||
'detailed_analysis' => [
|
||||
'tokens' => $this->tokenAnalysis->toArray(),
|
||||
'colors' => $this->colorAnalysis->toArray(),
|
||||
'components' => $this->componentAnalysis->toArray(),
|
||||
'conventions' => $this->conventionAnalysis->toArray(),
|
||||
],
|
||||
'metadata' => array_merge($this->metadata, [
|
||||
'generated_at' => date('c'),
|
||||
'version' => '1.0',
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
146
src/Framework/Design/ValueObjects/DesignToken.php
Normal file
146
src/Framework/Design/ValueObjects/DesignToken.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
/**
|
||||
* Design Token Value Object
|
||||
*/
|
||||
final readonly class DesignToken
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public DesignTokenType $type,
|
||||
public mixed $value,
|
||||
public string $description,
|
||||
public array $metadata = []
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory für Color Token
|
||||
*/
|
||||
public static function color(string $name, CssColor $color, string $description = ''): self
|
||||
{
|
||||
return new self(
|
||||
name: $name,
|
||||
type: DesignTokenType::COLOR,
|
||||
value: $color,
|
||||
description: $description ?: "Color: " . ucwords(str_replace(['-', '_'], ' ', $name)),
|
||||
metadata: ['source' => 'css_custom_property']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory für Spacing Token
|
||||
*/
|
||||
public static function spacing(string $name, string|int $value, string $description = ''): self
|
||||
{
|
||||
return new self(
|
||||
name: $name,
|
||||
type: DesignTokenType::SPACING,
|
||||
value: $value,
|
||||
description: $description ?: "Spacing: " . ucwords(str_replace(['-', '_'], ' ', $name)),
|
||||
metadata: ['source' => 'css_custom_property']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory für Typography Token
|
||||
*/
|
||||
public static function typography(string $name, mixed $value, string $description = ''): self
|
||||
{
|
||||
return new self(
|
||||
name: $name,
|
||||
type: DesignTokenType::TYPOGRAPHY,
|
||||
value: $value,
|
||||
description: $description ?: "Typography: " . ucwords(str_replace(['-', '_'], ' ', $name)),
|
||||
metadata: ['source' => 'css_custom_property']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert zu Array für Export/Serialisierung
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'type' => $this->type->value,
|
||||
'value' => $this->serializeValue(),
|
||||
'description' => $this->description,
|
||||
'metadata' => $this->metadata,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt CSS Custom Property String
|
||||
*/
|
||||
public function toCssCustomProperty(): string
|
||||
{
|
||||
$value = match($this->type) {
|
||||
DesignTokenType::COLOR => $this->value instanceof CssColor ? $this->value->toString() : (string) $this->value,
|
||||
default => (string) $this->value
|
||||
};
|
||||
|
||||
return "--{$this->name}: {$value};";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt CSS var() Referenz zurück
|
||||
*/
|
||||
public function toCssVar(): string
|
||||
{
|
||||
return "var(--{$this->name})";
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob Token einen bestimmten Wert-Typ hat
|
||||
*/
|
||||
public function hasValueType(string $type): bool
|
||||
{
|
||||
return match($type) {
|
||||
'color' => $this->value instanceof CssColor,
|
||||
'string' => is_string($this->value),
|
||||
'int' => is_int($this->value),
|
||||
'float' => is_float($this->value),
|
||||
'array' => is_array($this->value),
|
||||
default => false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt Wert als bestimmten Typ zurück
|
||||
*/
|
||||
public function getValueAs(string $type): mixed
|
||||
{
|
||||
return match($type) {
|
||||
'string' => (string) $this->value,
|
||||
'int' => (int) $this->value,
|
||||
'float' => (float) $this->value,
|
||||
'array' => is_array($this->value) ? $this->value : [$this->value],
|
||||
'color' => $this->value instanceof CssColor ? $this->value : null,
|
||||
default => $this->value
|
||||
};
|
||||
}
|
||||
|
||||
private function serializeValue(): mixed
|
||||
{
|
||||
if ($this->value instanceof CssColor) {
|
||||
return [
|
||||
'original' => $this->value->originalValue,
|
||||
'format' => $this->value->format->value,
|
||||
'hex' => $this->value->toHex(),
|
||||
'rgb' => $this->value->toRGB()?->toArray(),
|
||||
];
|
||||
}
|
||||
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->toCssVar();
|
||||
}
|
||||
}
|
||||
21
src/Framework/Design/ValueObjects/DesignTokenType.php
Normal file
21
src/Framework/Design/ValueObjects/DesignTokenType.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
/**
|
||||
* Enum für Design Token Typen
|
||||
*/
|
||||
enum DesignTokenType: string
|
||||
{
|
||||
case COLOR = 'color';
|
||||
case SPACING = 'spacing';
|
||||
case TYPOGRAPHY = 'typography';
|
||||
case SHADOW = 'shadow';
|
||||
case RADIUS = 'radius';
|
||||
case OPACITY = 'opacity';
|
||||
case BORDER = 'border';
|
||||
case ANIMATION = 'animation';
|
||||
case BREAKPOINT = 'breakpoint';
|
||||
}
|
||||
19
src/Framework/Design/ValueObjects/TokenCategory.php
Normal file
19
src/Framework/Design/ValueObjects/TokenCategory.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Design\ValueObjects;
|
||||
|
||||
/**
|
||||
* Kategorien für Design Tokens
|
||||
*/
|
||||
enum TokenCategory: string
|
||||
{
|
||||
case COLOR = 'color';
|
||||
case TYPOGRAPHY = 'typography';
|
||||
case SPACING = 'spacing';
|
||||
case BORDER = 'border';
|
||||
case SHADOW = 'shadow';
|
||||
case ANIMATION = 'animation';
|
||||
case OTHER = 'other';
|
||||
}
|
||||
Reference in New Issue
Block a user