Skip to content

Commit cbdbc4f

Browse files
committed
refactor: This commit introduces significant architectural improvements to the template system, implementing established design patterns for better
maintainability, extensibility, and testability.
1 parent d162c52 commit cbdbc4f

14 files changed

+1515
-345
lines changed
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Butschster\ContextGenerator\Template\Analysis\Analyzer;
6+
7+
use Butschster\ContextGenerator\Application\FSPath;
8+
use Butschster\ContextGenerator\Template\Analysis\AnalysisResult;
9+
use Butschster\ContextGenerator\Template\Analysis\ProjectAnalyzerInterface;
10+
use Butschster\ContextGenerator\Template\Analysis\Util\ComposerFileReader;
11+
use Butschster\ContextGenerator\Template\Analysis\Util\ProjectStructureDetector;
12+
13+
/**
14+
* Abstract base class for framework-specific analyzers
15+
* Provides common functionality for detecting PHP and JavaScript frameworks
16+
*/
17+
abstract class AbstractFrameworkAnalyzer implements ProjectAnalyzerInterface
18+
{
19+
protected ?ProjectAnalyzerInterface $nextAnalyzer = null;
20+
21+
public function __construct(
22+
protected readonly ComposerFileReader $composerReader,
23+
protected readonly ProjectStructureDetector $structureDetector,
24+
) {}
25+
26+
/**
27+
* Set the next analyzer in the chain
28+
*/
29+
public function setNext(?ProjectAnalyzerInterface $analyzer): void
30+
{
31+
$this->nextAnalyzer = $analyzer;
32+
}
33+
34+
/**
35+
* Get the next analyzer in the chain
36+
*/
37+
public function getNext(): ?ProjectAnalyzerInterface
38+
{
39+
return $this->nextAnalyzer;
40+
}
41+
42+
public function analyze(FSPath $projectRoot): ?AnalysisResult
43+
{
44+
if (!$this->canAnalyze($projectRoot)) {
45+
return null;
46+
}
47+
48+
$composer = $this->composerReader->readComposerFile($projectRoot);
49+
50+
if ($composer === null || !$this->hasFrameworkPackages($composer)) {
51+
return null;
52+
}
53+
54+
$confidence = $this->calculateConfidence($projectRoot, $composer);
55+
$metadata = $this->buildMetadata($projectRoot, $composer);
56+
57+
return new AnalysisResult(
58+
analyzerName: $this->getName(),
59+
detectedType: $this->getFrameworkType(),
60+
confidence: \min($confidence, 1.0),
61+
suggestedTemplates: [$this->getFrameworkType()],
62+
metadata: $metadata,
63+
);
64+
}
65+
66+
public function canAnalyze(FSPath $projectRoot): bool
67+
{
68+
// Must have composer.json to be a PHP framework
69+
if (!$projectRoot->join('composer.json')->exists()) {
70+
return false;
71+
}
72+
73+
$composer = $this->composerReader->readComposerFile($projectRoot);
74+
return $composer !== null && $this->hasFrameworkPackages($composer);
75+
}
76+
77+
/**
78+
* Get framework-specific packages to look for
79+
*
80+
* @return array<string>
81+
*/
82+
abstract protected function getFrameworkPackages(): array;
83+
84+
/**
85+
* Get framework-specific directories that indicate this framework
86+
*
87+
* @return array<string>
88+
*/
89+
abstract protected function getFrameworkDirectories(): array;
90+
91+
/**
92+
* Get framework-specific files that indicate this framework
93+
*
94+
* @return array<string>
95+
*/
96+
abstract protected function getFrameworkFiles(): array;
97+
98+
/**
99+
* Get the base confidence score for having framework packages
100+
*/
101+
protected function getBaseConfidence(): float
102+
{
103+
return 0.6;
104+
}
105+
106+
/**
107+
* Get the weight for directory structure matching
108+
*/
109+
protected function getDirectoryWeight(): float
110+
{
111+
return 0.2;
112+
}
113+
114+
/**
115+
* Get the weight for file matching
116+
*/
117+
protected function getFileWeight(): float
118+
{
119+
return 0.2;
120+
}
121+
122+
/**
123+
* Get the framework type identifier (usually same as getName())
124+
*/
125+
protected function getFrameworkType(): string
126+
{
127+
return $this->getName();
128+
}
129+
130+
/**
131+
* Check if composer.json contains framework-specific packages
132+
*/
133+
protected function hasFrameworkPackages(array $composer): bool
134+
{
135+
foreach ($this->getFrameworkPackages() as $package) {
136+
if ($this->composerReader->hasPackage($composer, $package)) {
137+
return true;
138+
}
139+
}
140+
141+
return false;
142+
}
143+
144+
/**
145+
* Calculate confidence score based on framework indicators
146+
*/
147+
protected function calculateConfidence(FSPath $projectRoot, array $composer): float
148+
{
149+
$confidence = $this->getBaseConfidence();
150+
151+
// Check for framework-specific files
152+
$fileScore = $this->checkFrameworkFiles($projectRoot);
153+
$confidence += $fileScore * $this->getFileWeight();
154+
155+
// Check for framework-specific directories
156+
$existingDirs = $this->structureDetector->detectExistingDirectories($projectRoot);
157+
$directoryScore = $this->structureDetector->getPatternMatchConfidence(
158+
$existingDirs,
159+
$this->getFrameworkDirectories(),
160+
);
161+
$confidence += $directoryScore * $this->getDirectoryWeight();
162+
163+
// Allow subclasses to add custom confidence calculations
164+
$confidence += $this->getAdditionalConfidence($projectRoot, $composer, $existingDirs);
165+
166+
return $confidence;
167+
}
168+
169+
/**
170+
* Check for framework-specific files and return confidence score
171+
*/
172+
protected function checkFrameworkFiles(FSPath $projectRoot): float
173+
{
174+
$frameworkFiles = $this->getFrameworkFiles();
175+
176+
if (empty($frameworkFiles)) {
177+
return 0.0;
178+
}
179+
180+
$found = 0;
181+
foreach ($frameworkFiles as $file) {
182+
if ($projectRoot->join($file)->exists()) {
183+
$found++;
184+
}
185+
}
186+
187+
return $found / \count($frameworkFiles);
188+
}
189+
190+
/**
191+
* Allow subclasses to add framework-specific confidence calculations
192+
*/
193+
protected function getAdditionalConfidence(
194+
FSPath $projectRoot,
195+
array $composer,
196+
array $existingDirectories,
197+
): float {
198+
return 0.0;
199+
}
200+
201+
/**
202+
* Build metadata for the analysis result
203+
*/
204+
protected function buildMetadata(FSPath $projectRoot, array $composer): array
205+
{
206+
$existingDirs = $this->structureDetector->detectExistingDirectories($projectRoot);
207+
208+
return [
209+
'composer' => $composer,
210+
'frameworkPackages' => $this->getDetectedPackages($composer),
211+
'existingDirectories' => $existingDirs,
212+
'frameworkDirectoriesFound' => \array_intersect($existingDirs, $this->getFrameworkDirectories()),
213+
'frameworkFilesFound' => $this->getDetectedFiles($projectRoot),
214+
'directoryScore' => $this->structureDetector->getPatternMatchConfidence(
215+
$existingDirs,
216+
$this->getFrameworkDirectories(),
217+
),
218+
'fileScore' => $this->checkFrameworkFiles($projectRoot),
219+
];
220+
}
221+
222+
/**
223+
* Get detected framework packages from composer.json
224+
*/
225+
protected function getDetectedPackages(array $composer): array
226+
{
227+
$detected = [];
228+
foreach ($this->getFrameworkPackages() as $package) {
229+
if ($this->composerReader->hasPackage($composer, $package)) {
230+
$detected[$package] = $this->composerReader->getPackageVersion($composer, $package);
231+
}
232+
}
233+
return $detected;
234+
}
235+
236+
/**
237+
* Get detected framework files
238+
*/
239+
protected function getDetectedFiles(FSPath $projectRoot): array
240+
{
241+
$detected = [];
242+
foreach ($this->getFrameworkFiles() as $file) {
243+
if ($projectRoot->join($file)->exists()) {
244+
$detected[] = $file;
245+
}
246+
}
247+
return $detected;
248+
}
249+
}

0 commit comments

Comments
 (0)