Files
michaelschiemer/src/Framework/Design/Analyzer/ComponentDetector.php
Michael Schiemer 55a330b223 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
2025-08-11 20:13:26 +02:00

398 lines
13 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Design\Analyzer;
use App\Framework\Design\Parser\ClassNameParser;
use App\Framework\Design\ValueObjects\ComponentPatternType;
use App\Framework\Design\ValueObjects\CssParseResult;
/**
* Erkennt Component-Patterns in CSS
*/
final readonly class ComponentDetector
{
public function __construct(
private ClassNameParser $classNameParser = new ClassNameParser()
) {
}
/**
* Erkennt alle Components im CSS
*/
public function detectComponents(CssParseResult $parseResult): ComponentDetectionResult
{
$classNames = array_values($parseResult->classNames);
if (empty($classNames)) {
return new ComponentDetectionResult(
totalComponents: 0,
bemComponents: [],
utilityComponents: [],
traditionalComponents: [],
patternStatistics: [],
recommendations: ['No CSS classes found to analyze for component patterns.']
);
}
// Pattern-Erkennung
$patterns = $this->classNameParser->detectPatterns($classNames);
// Gruppierung nach Pattern-Typ
$bemComponents = [];
$utilityComponents = [];
$traditionalComponents = [];
foreach ($patterns as $pattern) {
switch ($pattern->type) {
case ComponentPatternType::BEM:
$bemComponents[] = $pattern;
break;
case ComponentPatternType::UTILITY:
$utilityComponents[] = $pattern;
break;
case ComponentPatternType::COMPONENT:
$traditionalComponents[] = $pattern;
break;
}
}
// Statistiken erstellen
$statistics = $this->generatePatternStatistics($patterns, $classNames);
// Empfehlungen generieren
$recommendations = $this->generateRecommendations($bemComponents, $utilityComponents, $traditionalComponents, $classNames);
return new ComponentDetectionResult(
totalComponents: count($patterns),
bemComponents: $bemComponents,
utilityComponents: $utilityComponents,
traditionalComponents: $traditionalComponents,
patternStatistics: $statistics,
recommendations: $recommendations
);
}
/**
* Analysiert spezifisch BEM-Patterns
*/
public function analyzeBemPatterns(CssParseResult $parseResult): array
{
$bemClasses = $parseResult->getBemClasses();
$analysis = [];
foreach ($bemClasses as $className) {
$block = $className->getBemBlock();
if (! isset($analysis[$block])) {
$analysis[$block] = [
'block' => $block,
'elements' => [],
'modifiers' => [],
'total_classes' => 0,
'completeness' => 'unknown',
];
}
$analysis[$block]['total_classes']++;
if ($className->isBemElement()) {
$element = $className->getBemElement();
if ($element && ! in_array($element, $analysis[$block]['elements'])) {
$analysis[$block]['elements'][] = $element;
}
}
if ($className->isBemModifier()) {
$modifier = $className->getBemModifier();
if ($modifier && ! in_array($modifier, $analysis[$block]['modifiers'])) {
$analysis[$block]['modifiers'][] = $modifier;
}
}
}
// Completeness-Bewertung
foreach ($analysis as $block => &$data) {
$hasElements = ! empty($data['elements']);
$hasModifiers = ! empty($data['modifiers']);
if (! $hasElements && ! $hasModifiers) {
$data['completeness'] = 'block_only';
} elseif ($hasElements && ! $hasModifiers) {
$data['completeness'] = 'block_with_elements';
} elseif (! $hasElements && $hasModifiers) {
$data['completeness'] = 'block_with_modifiers';
} else {
$data['completeness'] = 'full_bem';
}
}
return $analysis;
}
/**
* Analysiert Utility-Class-Patterns
*/
public function analyzeUtilityPatterns(CssParseResult $parseResult): array
{
$utilityClasses = $parseResult->getUtilityClasses();
$analysis = [];
foreach ($utilityClasses as $className) {
$category = $className->getUtilityCategory();
if (! $category) {
continue;
}
if (! isset($analysis[$category])) {
$analysis[$category] = [
'category' => $category,
'classes' => [],
'count' => 0,
'coverage' => 'unknown',
];
}
$analysis[$category]['classes'][] = $className->name;
$analysis[$category]['count']++;
}
// Coverage-Bewertung für verschiedene Kategorien
foreach ($analysis as $category => &$data) {
$data['coverage'] = $this->assessUtilityCoverage($category, $data['classes']);
}
return $analysis;
}
/**
* Findet potenzielle Component-Candidates
*/
public function findComponentCandidates(CssParseResult $parseResult): array
{
$candidates = [];
$selectorFrequency = [];
// Analysiere Selektor-Häufigkeiten
foreach ($parseResult->rules as $rule) {
foreach ($rule->selectors as $selector) {
if ($selector->getType()->value === 'class') {
$classes = $selector->extractClasses();
foreach ($classes as $class) {
if (! isset($selectorFrequency[$class])) {
$selectorFrequency[$class] = 0;
}
$selectorFrequency[$class]++;
}
}
}
}
// Finde Klassen mit hoher Verwendung (potenzielle Components)
foreach ($selectorFrequency as $className => $frequency) {
if ($frequency >= 3) { // Threshold für Component-Candidate
$candidates[] = [
'class' => $className,
'frequency' => $frequency,
'potential' => $this->assessComponentPotential($className, $frequency),
'suggestions' => $this->getComponentSuggestions($className, $frequency),
];
}
}
// Sortiere nach Häufigkeit
usort($candidates, fn ($a, $b) => $b['frequency'] <=> $a['frequency']);
return $candidates;
}
/**
* Generiert Pattern-Statistiken
*/
private function generatePatternStatistics(array $patterns, array $classNames): array
{
$totalClasses = count($classNames);
$statistics = [
'total_patterns' => count($patterns),
'total_classes' => $totalClasses,
'pattern_distribution' => [],
'complexity_analysis' => [],
];
$patternTypes = [];
foreach ($patterns as $pattern) {
$type = $pattern->type->value;
if (! isset($patternTypes[$type])) {
$patternTypes[$type] = 0;
}
$patternTypes[$type]++;
}
foreach ($patternTypes as $type => $count) {
$percentage = $totalClasses > 0 ? round(($count / $totalClasses) * 100, 1) : 0;
$statistics['pattern_distribution'][$type] = [
'count' => $count,
'percentage' => $percentage,
];
}
// Complexity Analysis
foreach ($patterns as $pattern) {
$complexity = $pattern->getComplexity();
if (! isset($statistics['complexity_analysis'][$complexity])) {
$statistics['complexity_analysis'][$complexity] = 0;
}
$statistics['complexity_analysis'][$complexity]++;
}
return $statistics;
}
/**
* Generiert Empfehlungen
*/
private function generateRecommendations(array $bemComponents, array $utilityComponents, array $traditionalComponents, array $classNames): array
{
$recommendations = [];
$totalClasses = count($classNames);
// BEM-Empfehlungen
if (empty($bemComponents) && $totalClasses > 10) {
$recommendations[] = 'Consider adopting BEM methodology for better component organization and naming consistency.';
} elseif (! empty($bemComponents)) {
$blocksWithoutElements = 0;
foreach ($bemComponents as $component) {
if (empty($component->metadata['elements'])) {
$blocksWithoutElements++;
}
}
if ($blocksWithoutElements > count($bemComponents) / 2) {
$recommendations[] = 'Many BEM blocks lack elements. Consider breaking down components into smaller, reusable parts.';
}
}
// Utility-Empfehlungen
if (! empty($utilityComponents)) {
$utilityCount = array_sum(array_map(fn ($comp) => $comp->metadata['class_count'], $utilityComponents));
$utilityPercentage = round(($utilityCount / $totalClasses) * 100, 1);
if ($utilityPercentage > 70) {
$recommendations[] = 'High percentage of utility classes detected. Consider consolidating repetitive patterns into components.';
} elseif ($utilityPercentage < 10 && $totalClasses > 20) {
$recommendations[] = 'Low usage of utility classes. Consider adding utility classes for common spacing, colors, and typography.';
}
}
// Mixed Pattern Empfehlungen
$hasMultiplePatterns = (! empty($bemComponents) ? 1 : 0) +
(! empty($utilityComponents) ? 1 : 0) +
(! empty($traditionalComponents) ? 1 : 0);
if ($hasMultiplePatterns >= 3) {
$recommendations[] = 'Multiple CSS methodologies detected. Consider standardizing on one primary approach for consistency.';
}
// Spezifische Pattern-Empfehlungen
if (! empty($traditionalComponents)) {
$componentTypes = array_unique(array_map(fn ($comp) => $comp->metadata['component_type'], $traditionalComponents));
if (in_array('button', $componentTypes) && in_array('form', $componentTypes)) {
$recommendations[] = 'Consider creating a design system with consistent button and form component patterns.';
}
}
if (empty($recommendations)) {
$recommendations[] = 'Component patterns look well organized. Consider documenting the design system for team consistency.';
}
return $recommendations;
}
/**
* Bewertet Utility-Coverage
*/
private function assessUtilityCoverage(string $category, array $classes): string
{
$expectedCoverage = [
'spacing' => ['xs', 'sm', 'md', 'lg', 'xl'],
'color' => ['primary', 'secondary', 'success', 'warning', 'error'],
'typography' => ['xs', 'sm', 'base', 'lg', 'xl'],
'sizing' => ['sm', 'md', 'lg', 'full'],
];
if (! isset($expectedCoverage[$category])) {
return 'unknown';
}
$expected = $expectedCoverage[$category];
$hasExpected = 0;
foreach ($expected as $expectedClass) {
foreach ($classes as $actualClass) {
if (str_contains($actualClass, $expectedClass)) {
$hasExpected++;
break;
}
}
}
$percentage = count($expected) > 0 ? ($hasExpected / count($expected)) : 0;
if ($percentage >= 0.8) {
return 'excellent';
} elseif ($percentage >= 0.6) {
return 'good';
} elseif ($percentage >= 0.4) {
return 'fair';
} else {
return 'poor';
}
}
/**
* Bewertet Component-Potenzial
*/
private function assessComponentPotential(string $className, int $frequency): string
{
if ($frequency >= 10) {
return 'high';
} elseif ($frequency >= 6) {
return 'medium';
} else {
return 'low';
}
}
/**
* Gibt Component-Verbesserungsvorschläge
*/
private function getComponentSuggestions(string $className, int $frequency): array
{
$suggestions = [];
if ($frequency >= 8) {
$suggestions[] = 'Consider extracting into a reusable component or design token.';
}
if (str_contains($className, 'btn') || str_contains($className, 'button')) {
$suggestions[] = 'Consider creating button variants (primary, secondary, outline).';
}
if (str_contains($className, 'card')) {
$suggestions[] = 'Consider standardizing card padding, borders, and shadows.';
}
return $suggestions;
}
}