Skip to content

Commit 12d951c

Browse files
authored
Add fluent builder for Laravel (#67)
1 parent 0622bb2 commit 12d951c

File tree

4 files changed

+960
-0
lines changed

4 files changed

+960
-0
lines changed

generator/config/definitions.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace MongoDB\CodeGenerator\Config;
66

7+
use MongoDB\CodeGenerator\FluentStageFactoryGenerator;
78
use MongoDB\CodeGenerator\OperatorClassGenerator;
89
use MongoDB\CodeGenerator\OperatorFactoryGenerator;
910
use MongoDB\CodeGenerator\OperatorTestGenerator;
@@ -18,6 +19,7 @@
1819
OperatorClassGenerator::class,
1920
OperatorFactoryGenerator::class,
2021
OperatorTestGenerator::class,
22+
FluentStageFactoryGenerator::class,
2123
],
2224
],
2325

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MongoDB\CodeGenerator;
6+
7+
use MongoDB\Builder\Pipeline;
8+
use MongoDB\Builder\Stage;
9+
use MongoDB\Builder\Type\StageInterface;
10+
use MongoDB\CodeGenerator\Definition\GeneratorDefinition;
11+
use Nette\PhpGenerator\ClassType;
12+
use Nette\PhpGenerator\Method;
13+
use Nette\PhpGenerator\Parameter;
14+
use Nette\PhpGenerator\PhpFile;
15+
use Nette\PhpGenerator\PhpNamespace;
16+
use Nette\PhpGenerator\TraitType;
17+
use ReflectionClass;
18+
19+
use function array_key_last;
20+
use function array_map;
21+
use function assert;
22+
use function file_get_contents;
23+
use function implode;
24+
use function sprintf;
25+
26+
/**
27+
* Generates a fluent factory trait for aggregation pipeline stages.
28+
* The method definition is based on all the public static methods
29+
* of the Stage class.
30+
*/
31+
class FluentStageFactoryGenerator extends OperatorGenerator
32+
{
33+
private const FACTORY_CLASS = Stage::class;
34+
35+
public function generate(GeneratorDefinition $definition): void
36+
{
37+
$this->writeFile($this->createFluentFactoryTrait($definition));
38+
}
39+
40+
private function createFluentFactoryTrait(GeneratorDefinition $definition): PhpNamespace
41+
{
42+
$namespace = new PhpNamespace($definition->namespace);
43+
$trait = $namespace->addTrait('FluentFactoryTrait');
44+
45+
$namespace->addUse(self::FACTORY_CLASS);
46+
$namespace->addUse(StageInterface::class);
47+
$namespace->addUse(Pipeline::class);
48+
49+
$trait->addProperty('pipeline')
50+
->setType('array')
51+
->setComment('@var list<StageInterface>')
52+
->setValue([]);
53+
$trait->addMethod('getPipeline')
54+
->setReturnType(Pipeline::class)
55+
->setBody(<<<'PHP'
56+
return new Pipeline(...$this->pipeline);
57+
PHP);
58+
59+
$this->addUsesFrom(self::FACTORY_CLASS, $namespace);
60+
$staticFactory = ClassType::from(self::FACTORY_CLASS);
61+
assert($staticFactory instanceof ClassType);
62+
63+
// Import the methods customized in the factory class
64+
foreach ($staticFactory->getMethods() as $method) {
65+
$this->addMethod($method, $trait);
66+
}
67+
68+
// Import the other methods provided by the generated trait
69+
foreach ($staticFactory->getTraits() as $usedTrait) {
70+
$this->addUsesFrom($usedTrait->getName(), $namespace);
71+
$staticFactory = TraitType::from($usedTrait->getName());
72+
assert($staticFactory instanceof TraitType);
73+
foreach ($staticFactory->getMethods() as $method) {
74+
$this->addMethod($method, $trait);
75+
}
76+
}
77+
78+
return $namespace;
79+
}
80+
81+
private function addMethod(Method $factoryMethod, TraitType $trait): void
82+
{
83+
// Non-public methods are not part of the API
84+
if (! $factoryMethod->isPublic()) {
85+
return;
86+
}
87+
88+
// Some methods can be overridden in the class, so we skip them
89+
// when importing the methods provided by the trait.
90+
if ($trait->hasMethod($factoryMethod->getName())) {
91+
return;
92+
}
93+
94+
$method = $trait->addMethod($factoryMethod->getName());
95+
96+
$method->setComment($factoryMethod->getComment());
97+
$method->setParameters($factoryMethod->getParameters());
98+
99+
$args = array_map(
100+
fn (Parameter $param): string => '$' . $param->getName(),
101+
$factoryMethod->getParameters(),
102+
);
103+
104+
if ($factoryMethod->isVariadic()) {
105+
$method->setVariadic();
106+
$args[array_key_last($args)] = '...' . $args[array_key_last($args)];
107+
}
108+
109+
$method->setReturnType('static');
110+
$method->setBody(sprintf(
111+
<<<'PHP'
112+
$this->pipeline[] = %s::%s(%s);
113+
114+
return $this;
115+
PHP,
116+
(new ReflectionClass(self::FACTORY_CLASS))->getShortName(),
117+
$factoryMethod->getName(),
118+
implode(', ', $args),
119+
));
120+
}
121+
122+
private static function addUsesFrom(string $classLike, PhpNamespace $namespace): void
123+
{
124+
$file = PhpFile::fromCode(file_get_contents((new ReflectionClass($classLike))->getFileName()));
125+
126+
foreach ($file->getNamespaces() as $ns) {
127+
foreach ($ns->getUses() as $use) {
128+
$namespace->addUse($use);
129+
}
130+
}
131+
}
132+
}

0 commit comments

Comments
 (0)