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:
397
src/Framework/Design/Analyzer/ComponentDetector.php
Normal file
397
src/Framework/Design/Analyzer/ComponentDetector.php
Normal file
@@ -0,0 +1,397 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user