Skip to content

Commit 1ebcef6

Browse files
committed
Builder generation fix
1 parent 3bf02e0 commit 1ebcef6

4 files changed

Lines changed: 107 additions & 3 deletions

File tree

src/Config/PhpBuilderGeneratorConfig.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class PhpBuilderGeneratorConfig
1515
private string $outputDir = 'generated/php-builder-generator/';
1616
private string $phpVersion = '8.2';
1717
private string $namespaceSuffix = '';
18+
private string $builderSuffix = 'Builder';
1819
private bool $autoGenerate = true;
1920

2021
public static function configure(): self
@@ -63,6 +64,13 @@ public function namespaceSuffix(string $suffix): self
6364
return $this;
6465
}
6566

67+
public function builderSuffix(string $suffix): self
68+
{
69+
$this->builderSuffix = $suffix;
70+
71+
return $this;
72+
}
73+
6674
public function autoGenerate(bool $value): self
6775
{
6876
$this->autoGenerate = $value;
@@ -97,6 +105,11 @@ public function getNamespaceSuffix(): string
97105
return $this->namespaceSuffix;
98106
}
99107

108+
public function getBuilderSuffix(): string
109+
{
110+
return $this->builderSuffix;
111+
}
112+
100113
public function isAutoGenerate(): bool
101114
{
102115
return $this->autoGenerate;

src/Generator/BuilderGenerator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public function generate(GenerationContext $context): array
2424
$classContext = $context->classContext;
2525
$builderConfig = $classContext->builderConfig;
2626

27-
$builderClassName = $builderConfig->className ?? $classContext->getShortName() . 'Builder';
27+
$builderClassName = $builderConfig->className ?? $classContext->getShortName() . $context->config->getBuilderSuffix();
2828
$builderNamespace = $this->getBuilderNamespace($classContext, $context);
2929
$properties = $classContext->getBuilderProperties();
3030

src/Service/BuilderService.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ private function resolveClasses(PhpBuilderGeneratorConfig $config): array
109109
foreach ($config->getScanDirectories() as $scan) {
110110
$discovered = $this->discoverClassesInDirectory($scan['dir']);
111111
foreach ($discovered as $className) {
112-
if (!isset($classes[$className])) {
112+
if (!isset($classes[$className]) && !$this->isBuilderClass($className, $config->getBuilderSuffix())) {
113113
$classes[$className] = $scan['config'];
114114
}
115115
}
@@ -118,6 +118,20 @@ private function resolveClasses(PhpBuilderGeneratorConfig $config): array
118118
return $classes;
119119
}
120120

121+
/**
122+
* Returns true when the class is itself a generated builder and should not
123+
* receive a builder of its own. Generated builders are named with the
124+
* configured builder suffix (default "Builder"). Scanning the output directory
125+
* alongside the source directory would otherwise produce FooBuilderBuilder
126+
* on subsequent runs.
127+
*/
128+
private function isBuilderClass(string $className, string $builderSuffix): bool
129+
{
130+
$shortName = substr($className, (int) strrpos($className, '\\') + 1);
131+
132+
return str_ends_with($shortName, $builderSuffix);
133+
}
134+
121135
/**
122136
* @return class-string[]
123137
*/

tests/Integration/BuilderGenerationTest.php

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,5 +134,82 @@ public function testCleanGeneratedFiles(): void
134134
$deleted = $this->service->clean($config);
135135
$this->assertGreaterThan(0, $deleted);
136136
}
137-
}
138137

138+
public function testNoBuilderBuilderGeneratedWhenOutputDirIsAlsoScanned(): void
139+
{
140+
$config = PhpBuilderGeneratorConfig::configure()
141+
->scanDirectory(__DIR__ . '/../Fixtures')
142+
->outputDir($this->outputDir);
143+
144+
$firstPass = $this->service->generateBuilders($config);
145+
$this->assertGreaterThan(0, $firstPass);
146+
147+
$firstPassBuilderFiles = $this->findFilesRecursively($this->outputDir, 'Builder.php');
148+
$this->assertNotEmpty($firstPassBuilderFiles, 'First pass should produce Builder files');
149+
150+
$config2 = PhpBuilderGeneratorConfig::configure()
151+
->scanDirectory(__DIR__ . '/../Fixtures')
152+
->scanDirectory($this->outputDir)
153+
->outputDir($this->outputDir);
154+
155+
$this->service->generateBuilders($config2);
156+
157+
$builderBuilderFiles = $this->findFilesRecursively($this->outputDir, 'BuilderBuilder.php');
158+
$this->assertEmpty(
159+
$builderBuilderFiles,
160+
'No *BuilderBuilder files should be generated when outputDir is also scanned: ' .
161+
implode(', ', $builderBuilderFiles)
162+
);
163+
}
164+
165+
public function testCustomBuilderSuffixIsRespectedWhenFilteringScannedClasses(): void
166+
{
167+
$config = PhpBuilderGeneratorConfig::configure()
168+
->scanDirectory(__DIR__ . '/../Fixtures')
169+
->outputDir($this->outputDir)
170+
->builderSuffix('Factory');
171+
172+
$firstPass = $this->service->generateBuilders($config);
173+
$this->assertGreaterThan(0, $firstPass);
174+
175+
$factoryFiles = $this->findFilesRecursively($this->outputDir, 'Factory.php');
176+
$this->assertNotEmpty($factoryFiles, 'First pass should produce *Factory files');
177+
178+
$config2 = PhpBuilderGeneratorConfig::configure()
179+
->scanDirectory(__DIR__ . '/../Fixtures')
180+
->scanDirectory($this->outputDir)
181+
->outputDir($this->outputDir)
182+
->builderSuffix('Factory');
183+
184+
$this->service->generateBuilders($config2);
185+
186+
$factoryFactoryFiles = $this->findFilesRecursively($this->outputDir, 'FactoryFactory.php');
187+
$this->assertEmpty(
188+
$factoryFactoryFiles,
189+
'No *FactoryFactory files should be generated: ' . implode(', ', $factoryFactoryFiles)
190+
);
191+
}
192+
193+
/**
194+
* @return string[]
195+
*/
196+
private function findFilesRecursively(string $dir, string $suffix): array
197+
{
198+
if (!is_dir($dir)) {
199+
return [];
200+
}
201+
202+
$result = [];
203+
$iterator = new \RecursiveIteratorIterator(
204+
new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS)
205+
);
206+
207+
foreach ($iterator as $file) {
208+
if ($file->isFile() && str_ends_with($file->getFilename(), $suffix)) {
209+
$result[] = $file->getPathname();
210+
}
211+
}
212+
213+
return $result;
214+
}
215+
}

0 commit comments

Comments
 (0)