-
Notifications
You must be signed in to change notification settings - Fork 4
Add fluent builder for Laravel #67
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,163 @@ | ||||||
<?php | ||||||
|
||||||
declare(strict_types=1); | ||||||
|
||||||
namespace MongoDB\CodeGenerator; | ||||||
|
||||||
use MongoDB\BSON\Decimal128; | ||||||
use MongoDB\BSON\Document; | ||||||
use MongoDB\BSON\Int64; | ||||||
use MongoDB\BSON\PackedArray; | ||||||
use MongoDB\BSON\Serializable; | ||||||
use MongoDB\BSON\Timestamp; | ||||||
use MongoDB\BSON\Type; | ||||||
use MongoDB\Builder\Expression\ArrayFieldPath; | ||||||
use MongoDB\Builder\Expression\FieldPath; | ||||||
use MongoDB\Builder\Expression\ResolvesToArray; | ||||||
use MongoDB\Builder\Expression\ResolvesToObject; | ||||||
use MongoDB\Builder\Pipeline; | ||||||
use MongoDB\Builder\Stage; | ||||||
use MongoDB\Builder\Type\AccumulatorInterface; | ||||||
use MongoDB\Builder\Type\ExpressionInterface; | ||||||
use MongoDB\Builder\Type\FieldQueryInterface; | ||||||
use MongoDB\Builder\Type\Optional; | ||||||
use MongoDB\Builder\Type\QueryInterface; | ||||||
use MongoDB\Builder\Type\Sort; | ||||||
use MongoDB\Builder\Type\StageInterface; | ||||||
use MongoDB\CodeGenerator\Definition\GeneratorDefinition; | ||||||
use MongoDB\Model\BSONArray; | ||||||
use Nette\PhpGenerator\ClassType; | ||||||
use Nette\PhpGenerator\Method; | ||||||
use Nette\PhpGenerator\PhpNamespace; | ||||||
use Nette\PhpGenerator\TraitType; | ||||||
use RuntimeException; | ||||||
use stdClass; | ||||||
use Throwable; | ||||||
|
||||||
use function array_key_last; | ||||||
use function array_map; | ||||||
use function assert; | ||||||
use function implode; | ||||||
use function sprintf; | ||||||
|
||||||
/** | ||||||
* Generates a fluent factory class for aggregation pipeline stages. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Make a note to update other references of "class" to "trait" below. For instance, This very class might also be better named There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I want to keep "Stage" in the generator class name. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should the generated file be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The file is in the Stage namespace. |
||||||
* The method definition is based on the manually edited static class | ||||||
* that imports the stage factory trait. | ||||||
jmikola marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
*/ | ||||||
class FluentStageFactoryGenerator extends OperatorGenerator | ||||||
{ | ||||||
/** | ||||||
* All public of this class are duplicated as instance methods of the | ||||||
GromNaN marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
* fluent factory class. | ||||||
*/ | ||||||
private const FACTORY_CLASS = Stage::class; | ||||||
jmikola marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
public function generate(GeneratorDefinition $definition): void | ||||||
{ | ||||||
$this->writeFile($this->createFluentFactoryClass($definition)); | ||||||
} | ||||||
|
||||||
private function createFluentFactoryClass(GeneratorDefinition $definition): PhpNamespace | ||||||
{ | ||||||
$namespace = new PhpNamespace($definition->namespace); | ||||||
$class = $namespace->addClass('FluentFactory'); | ||||||
|
||||||
$namespace->addUse(StageInterface::class); | ||||||
$namespace->addUse(FieldQueryInterface::class); | ||||||
$namespace->addUse(Pipeline::class); | ||||||
$namespace->addUse(Decimal128::class); | ||||||
$namespace->addUse(Document::class); | ||||||
$namespace->addUse(Int64::class); | ||||||
$namespace->addUse(PackedArray::class); | ||||||
$namespace->addUse(Serializable::class); | ||||||
$namespace->addUse(Timestamp::class); | ||||||
$namespace->addUse(Type::class); | ||||||
$namespace->addUse(ArrayFieldPath::class); | ||||||
$namespace->addUse(FieldPath::class); | ||||||
$namespace->addUse(ResolvesToArray::class); | ||||||
$namespace->addUse(ResolvesToObject::class); | ||||||
$namespace->addUse(Pipeline::class); | ||||||
$namespace->addUse(AccumulatorInterface::class); | ||||||
$namespace->addUse(ExpressionInterface::class); | ||||||
$namespace->addUse(Optional::class); | ||||||
$namespace->addUse(QueryInterface::class); | ||||||
$namespace->addUse(Sort::class); | ||||||
$namespace->addUse(BSONArray::class); | ||||||
$namespace->addUse(stdClass::class); | ||||||
jmikola marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
$namespace->addUse(self::FACTORY_CLASS); | ||||||
$class->addProperty('pipeline') | ||||||
->setType('array') | ||||||
->setComment('@var list<StageInterface>') | ||||||
->setValue([]); | ||||||
$class->addMethod('getPipeline') | ||||||
->setReturnType(Pipeline::class) | ||||||
->setBody(<<<'PHP' | ||||||
return new Pipeline(...$this->pipeline); | ||||||
PHP); | ||||||
|
||||||
$staticFactory = ClassType::from(self::FACTORY_CLASS); | ||||||
assert($staticFactory instanceof ClassType); | ||||||
foreach ($staticFactory->getMethods() as $method) { | ||||||
if (! $method->isPublic()) { | ||||||
continue; | ||||||
} | ||||||
|
||||||
GromNaN marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
try { | ||||||
$this->addMethod($method, $namespace, $class); | ||||||
} catch (Throwable $e) { | ||||||
GromNaN marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
throw new RuntimeException(sprintf('Failed to generate class for operator "%s"', $operator->name), 0, $e); | ||||||
} | ||||||
} | ||||||
|
||||||
$staticFactory = TraitType::from(Stage\FactoryTrait::class); | ||||||
assert($staticFactory instanceof TraitType); | ||||||
foreach ($staticFactory->getMethods() as $method) { | ||||||
if (! $method->isPublic()) { | ||||||
continue; | ||||||
} | ||||||
|
||||||
try { | ||||||
$this->addMethod($method, $namespace, $class); | ||||||
} catch (Throwable $e) { | ||||||
throw new RuntimeException(sprintf('Failed to generate class for operator "%s"', $operator->name), 0, $e); | ||||||
} | ||||||
} | ||||||
|
||||||
return $namespace; | ||||||
} | ||||||
|
||||||
private function addMethod(Method $factoryMethod, PhpNamespace $namespace, ClassType $class): void | ||||||
{ | ||||||
if ($class->hasMethod($factoryMethod->getName())) { | ||||||
return; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense to throw here? What is the legitimate case where the method already exists and you'd want to NOP? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noted. In this case, a comment would be helpful to clarify that you're giving preference to the redefined Stage methods instead of those from the trait. And this assumes you add the Stage methods before the trait (no need to repeat that, it's clear from the order of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's essentially what I said in the comment above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, my mistake. I just realized this entire thread was incorrectly applied to the early return for non-public methods. I see the comment below for skipping overridden methods. 👍 |
||||||
} | ||||||
|
||||||
$method = $class->addMethod($factoryMethod->getName()); | ||||||
|
||||||
$method->setComment($factoryMethod->getComment()); | ||||||
$method->setParameters($factoryMethod->getParameters()); | ||||||
|
||||||
$args = array_map( | ||||||
fn ($param) => '$' . $param->getName(), | ||||||
$factoryMethod->getParameters(), | ||||||
GromNaN marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
); | ||||||
|
||||||
if ($factoryMethod->isVariadic()) { | ||||||
$method->setVariadic(); | ||||||
$args[array_key_last($args)] = '...' . $args[array_key_last($args)]; | ||||||
} | ||||||
|
||||||
$method->setReturnType('static'); | ||||||
$method->setBody(sprintf( | ||||||
<<<'PHP' | ||||||
$this->pipeline[] = %s::%s(%s); | ||||||
|
||||||
return $this; | ||||||
PHP, | ||||||
'Stage', | ||||||
GromNaN marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
$factoryMethod->getName(), | ||||||
implode(',', $args), | ||||||
)); | ||||||
} | ||||||
} |
Uh oh!
There was an error while loading. Please reload this page.