-
Notifications
You must be signed in to change notification settings - Fork 4
Add fluent builder for Laravel #67
Changes from all 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,132 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace MongoDB\CodeGenerator; | ||
|
||
use MongoDB\Builder\Pipeline; | ||
use MongoDB\Builder\Stage; | ||
use MongoDB\Builder\Type\StageInterface; | ||
use MongoDB\CodeGenerator\Definition\GeneratorDefinition; | ||
use Nette\PhpGenerator\ClassType; | ||
use Nette\PhpGenerator\Method; | ||
use Nette\PhpGenerator\Parameter; | ||
use Nette\PhpGenerator\PhpFile; | ||
use Nette\PhpGenerator\PhpNamespace; | ||
use Nette\PhpGenerator\TraitType; | ||
use ReflectionClass; | ||
|
||
use function array_key_last; | ||
use function array_map; | ||
use function assert; | ||
use function file_get_contents; | ||
use function implode; | ||
use function sprintf; | ||
|
||
/** | ||
* Generates a fluent factory trait for aggregation pipeline stages. | ||
* The method definition is based on all the public static methods | ||
* of the Stage class. | ||
*/ | ||
class FluentStageFactoryGenerator extends OperatorGenerator | ||
{ | ||
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->createFluentFactoryTrait($definition)); | ||
} | ||
|
||
private function createFluentFactoryTrait(GeneratorDefinition $definition): PhpNamespace | ||
{ | ||
$namespace = new PhpNamespace($definition->namespace); | ||
$trait = $namespace->addTrait('FluentFactoryTrait'); | ||
|
||
$namespace->addUse(self::FACTORY_CLASS); | ||
$namespace->addUse(StageInterface::class); | ||
$namespace->addUse(Pipeline::class); | ||
|
||
$trait->addProperty('pipeline') | ||
->setType('array') | ||
->setComment('@var list<StageInterface>') | ||
->setValue([]); | ||
$trait->addMethod('getPipeline') | ||
->setReturnType(Pipeline::class) | ||
->setBody(<<<'PHP' | ||
return new Pipeline(...$this->pipeline); | ||
PHP); | ||
|
||
$this->addUsesFrom(self::FACTORY_CLASS, $namespace); | ||
$staticFactory = ClassType::from(self::FACTORY_CLASS); | ||
assert($staticFactory instanceof ClassType); | ||
|
||
// Import the methods customized in the factory class | ||
foreach ($staticFactory->getMethods() as $method) { | ||
$this->addMethod($method, $trait); | ||
} | ||
|
||
// Import the other methods provided by the generated trait | ||
foreach ($staticFactory->getTraits() as $usedTrait) { | ||
$this->addUsesFrom($usedTrait->getName(), $namespace); | ||
$staticFactory = TraitType::from($usedTrait->getName()); | ||
assert($staticFactory instanceof TraitType); | ||
foreach ($staticFactory->getMethods() as $method) { | ||
$this->addMethod($method, $trait); | ||
} | ||
} | ||
|
||
return $namespace; | ||
} | ||
|
||
private function addMethod(Method $factoryMethod, TraitType $trait): void | ||
{ | ||
// Non-public methods are not part of the API | ||
if (! $factoryMethod->isPublic()) { | ||
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. 👍 |
||
} | ||
|
||
// Some methods can be overridden in the class, so we skip them | ||
// when importing the methods provided by the trait. | ||
if ($trait->hasMethod($factoryMethod->getName())) { | ||
return; | ||
} | ||
|
||
$method = $trait->addMethod($factoryMethod->getName()); | ||
|
||
$method->setComment($factoryMethod->getComment()); | ||
$method->setParameters($factoryMethod->getParameters()); | ||
|
||
$args = array_map( | ||
fn (Parameter $param): string => '$' . $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, | ||
(new ReflectionClass(self::FACTORY_CLASS))->getShortName(), | ||
$factoryMethod->getName(), | ||
implode(', ', $args), | ||
)); | ||
} | ||
|
||
private static function addUsesFrom(string $classLike, PhpNamespace $namespace): void | ||
{ | ||
$file = PhpFile::fromCode(file_get_contents((new ReflectionClass($classLike))->getFileName())); | ||
|
||
foreach ($file->getNamespaces() as $ns) { | ||
foreach ($ns->getUses() as $use) { | ||
$namespace->addUse($use); | ||
} | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.