Skip to content
This repository was archived by the owner on Feb 28, 2025. It is now read-only.

Commit f6159d2

Browse files
committed
Add fluent builder for Laravel
1 parent 63dd036 commit f6159d2

File tree

4 files changed

+973
-0
lines changed

4 files changed

+973
-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: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MongoDB\CodeGenerator;
6+
7+
use MongoDB\BSON\Decimal128;
8+
use MongoDB\BSON\Document;
9+
use MongoDB\BSON\Int64;
10+
use MongoDB\BSON\PackedArray;
11+
use MongoDB\BSON\Serializable;
12+
use MongoDB\BSON\Timestamp;
13+
use MongoDB\BSON\Type;
14+
use MongoDB\Builder\Expression\ArrayFieldPath;
15+
use MongoDB\Builder\Expression\FieldPath;
16+
use MongoDB\Builder\Expression\ResolvesToArray;
17+
use MongoDB\Builder\Expression\ResolvesToObject;
18+
use MongoDB\Builder\Pipeline;
19+
use MongoDB\Builder\Stage;
20+
use MongoDB\Builder\Type\AccumulatorInterface;
21+
use MongoDB\Builder\Type\ExpressionInterface;
22+
use MongoDB\Builder\Type\Optional;
23+
use MongoDB\Builder\Type\QueryInterface;
24+
use MongoDB\Builder\Type\Sort;
25+
use MongoDB\CodeGenerator\Definition\GeneratorDefinition;
26+
use MongoDB\Model\BSONArray;
27+
use Nette\PhpGenerator\ClassType;
28+
use Nette\PhpGenerator\Method;
29+
use Nette\PhpGenerator\PhpNamespace;
30+
use Nette\PhpGenerator\TraitType;
31+
use RuntimeException;
32+
use stdClass;
33+
use Throwable;
34+
35+
use function array_key_last;
36+
use function array_map;
37+
use function assert;
38+
use function implode;
39+
use function sprintf;
40+
41+
/**
42+
* Generates a fluent factory class for aggregation pipeline stages.
43+
* The method definition is based on the manually edited static class
44+
* that imports the stage factory trait.
45+
*/
46+
class FluentStageFactoryGenerator extends OperatorGenerator
47+
{
48+
/**
49+
* All public of this class are duplicated as instance methods of the
50+
* fluent factory class.
51+
*/
52+
private const FACTORY_CLASS = Stage::class;
53+
54+
public function generate(GeneratorDefinition $definition): void
55+
{
56+
$this->writeFile($this->createFluentFactoryClass($definition));
57+
}
58+
59+
private function createFluentFactoryClass(GeneratorDefinition $definition): PhpNamespace
60+
{
61+
$namespace = new PhpNamespace($definition->namespace);
62+
$class = $namespace->addClass('FluentFactory');
63+
64+
$namespace->addUse(Pipeline::class);
65+
$namespace->addUse(Decimal128::class);
66+
$namespace->addUse(Document::class);
67+
$namespace->addUse(Int64::class);
68+
$namespace->addUse(PackedArray::class);
69+
$namespace->addUse(Serializable::class);
70+
$namespace->addUse(Timestamp::class);
71+
$namespace->addUse(Type::class);
72+
$namespace->addUse(ArrayFieldPath::class);
73+
$namespace->addUse(FieldPath::class);
74+
$namespace->addUse(ResolvesToArray::class);
75+
$namespace->addUse(ResolvesToObject::class);
76+
$namespace->addUse(Pipeline::class);
77+
$namespace->addUse(AccumulatorInterface::class);
78+
$namespace->addUse(ExpressionInterface::class);
79+
$namespace->addUse(Optional::class);
80+
$namespace->addUse(QueryInterface::class);
81+
$namespace->addUse(Sort::class);
82+
$namespace->addUse(BSONArray::class);
83+
$namespace->addUse(stdClass::class);
84+
$namespace->addUse(self::FACTORY_CLASS);
85+
$class->addProperty('pipeline')
86+
->setType('array')
87+
->setValue([]);
88+
$class->addMethod('getPipeline')
89+
->setReturnType(Pipeline::class)
90+
->setBody(<<<'PHP'
91+
return new Pipeline(...$this->pipeline);
92+
PHP);
93+
94+
$staticFactory = ClassType::from(self::FACTORY_CLASS);
95+
assert($staticFactory instanceof ClassType);
96+
foreach ($staticFactory->getMethods() as $method) {
97+
if (! $method->isPublic()) {
98+
continue;
99+
}
100+
101+
try {
102+
$this->addMethod($method, $namespace, $class);
103+
} catch (Throwable $e) {
104+
throw new RuntimeException(sprintf('Failed to generate class for operator "%s"', $operator->name), 0, $e);
105+
}
106+
}
107+
108+
$staticFactory = TraitType::from(Stage\FactoryTrait::class);
109+
assert($staticFactory instanceof TraitType);
110+
foreach ($staticFactory->getMethods() as $method) {
111+
if (! $method->isPublic()) {
112+
continue;
113+
}
114+
115+
try {
116+
$this->addMethod($method, $namespace, $class);
117+
} catch (Throwable $e) {
118+
throw new RuntimeException(sprintf('Failed to generate class for operator "%s"', $operator->name), 0, $e);
119+
}
120+
}
121+
122+
return $namespace;
123+
}
124+
125+
private function addMethod(Method $factoryMethod, PhpNamespace $namespace, ClassType $class): void
126+
{
127+
if ($class->hasMethod($factoryMethod->getName())) {
128+
return;
129+
}
130+
131+
$method = $class->addMethod($factoryMethod->getName());
132+
133+
$method->setComment($factoryMethod->getComment());
134+
$method->setParameters($factoryMethod->getParameters());
135+
136+
$args = array_map(
137+
fn ($param) => '$' . $param->getName(),
138+
$factoryMethod->getParameters(),
139+
);
140+
141+
if ($factoryMethod->isVariadic()) {
142+
$method->setVariadic();
143+
$args[array_key_last($args)] = '...' . $args[array_key_last($args)];
144+
}
145+
146+
$method->setReturnType('static');
147+
$method->setBody(sprintf(
148+
<<<'PHP'
149+
$this->pipeline[] = %s::%s(%s);
150+
151+
return $this;
152+
PHP,
153+
'Stage',
154+
$factoryMethod->getName(),
155+
implode(',', $args),
156+
));
157+
}
158+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MongoDB\Builder;
6+
7+
use MongoDB\Builder\Stage\FluentFactory;
8+
use MongoDB\Builder\Type\Sort;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class FluentPipelineFactoryTest extends TestCase
12+
{
13+
public function testFluentPipelineFactory(): void
14+
{
15+
$pipeline = (new FluentFactory())
16+
->match(x: Query::eq(1))
17+
->project(_id: false, x: true)
18+
->sort(x: Sort::Asc)
19+
->getPipeline();
20+
21+
$this->assertInstanceof(Pipeline::class, $pipeline);
22+
$this->assertCount(3, $pipeline->getIterator());
23+
}
24+
}

0 commit comments

Comments
 (0)