diff --git a/.gitignore b/.gitignore index cf97dc4..e864b22 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ /composer.lock /phpcs.xml /phpunit.xml +/tests/tmp /var /vendor diff --git a/composer.json b/composer.json index 862e60d..99ba6fc 100644 --- a/composer.json +++ b/composer.json @@ -3,11 +3,14 @@ "description": "Symfony MakerBundle for MongoDB ODM", "keywords": ["mongodb", "maker", "odm", "dev"], "type": "symfony-bundle", + "repositories": [ + { "type": "github", "url": "https://github.com/GromNaN/symfony-maker-bundle" } + ], "require": { "php": "^8.4", "ext-mongodb": "^2.1", "doctrine/mongodb-odm-bundle": "^5.5", - "symfony/maker-bundle": "^1.65", + "symfony/maker-bundle": "dev-develop", "symfony/http-kernel": "^7.4|^8" }, "require-dev": { @@ -15,7 +18,8 @@ "phpstan/phpstan": "^2.1.30", "phpstan/phpstan-phpunit": "^2.0.7", "phpunit/phpunit": "^12.5", - "symfony/framework-bundle": "^7.4|^8" + "symfony/framework-bundle": "^7.4|^8", + "twig/twig": "^3.22" }, "license": "MIT", "autoload": { diff --git a/config/help/MakeDocument.txt b/config/help/MakeDocument.txt new file mode 100644 index 0000000..2c00e42 --- /dev/null +++ b/config/help/MakeDocument.txt @@ -0,0 +1,3 @@ +The %command.name% command creates or updates a document and repository class. + +php %command.full_name% BlogPost diff --git a/config/makers.php b/config/makers.php index 9ae41b1..7218941 100644 --- a/config/makers.php +++ b/config/makers.php @@ -14,11 +14,29 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Doctrine\Bundle\MongoDBMakerBundle\Maker\MakeDocument; +use Doctrine\Bundle\MongoDBMakerBundle\MongoDB\DocumentClassGenerator; +use Doctrine\Bundle\MongoDBMakerBundle\MongoDB\MongoDBHelper; return static function (ContainerConfigurator $container): void { $services = $container->services(); + $services->set('doctrine_mongodb_maker.mongodb_helper', MongoDBHelper::class) + ->args([ + service('doctrine_mongodb'), + ]); + + $services->set('doctrine_mongodb_maker.document_class_generator', DocumentClassGenerator::class) + ->args([ + service('maker.generator'), + service('doctrine_mongodb_maker.mongodb_helper'), + ]); + $services->set('doctrine_mongodb_maker.maker.make_document', MakeDocument::class) - ->args([]) + ->args([ + service('maker.file_manager'), + service('doctrine_mongodb_maker.mongodb_helper'), + service('maker.generator'), + service('doctrine_mongodb_maker.document_class_generator'), + ]) ->tag('maker.command'); }; diff --git a/phpcs.xml.dist b/phpcs.xml.dist index b302b90..25e0b31 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -13,7 +13,9 @@ config src - tests + tests/TestKernel.php + tests/BundleTest.php + tests/Maker diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 347e7a5..2d2b268 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -4,6 +4,8 @@ parameters: paths: - src - tests + excludePaths: + - tests/tmp includes: - vendor/phpstan/phpstan-phpunit/extension.neon diff --git a/phpunit.xml.dist b/phpunit.xml.dist index bebf77d..59969e8 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -6,6 +6,11 @@ failOnAllIssues="true" displayDetailsOnAllIssues="true" > + + + + + ./tests/ diff --git a/src/Maker/MakeDocument.php b/src/Maker/MakeDocument.php index af799bd..b0467b5 100644 --- a/src/Maker/MakeDocument.php +++ b/src/Maker/MakeDocument.php @@ -4,39 +4,407 @@ namespace Doctrine\Bundle\MongoDBMakerBundle\Maker; +use Doctrine\Bundle\MongoDBBundle\DependencyInjection\Compiler\DoctrineMongoDBMappingsPass; +use Doctrine\Bundle\MongoDBMakerBundle\MongoDB\DocumentClassGenerator; +use Doctrine\Bundle\MongoDBMakerBundle\MongoDB\MongoDBHelper; +use Doctrine\ODM\MongoDB\Types\Type; +use InvalidArgumentException; +use ReflectionClass; +use ReflectionProperty; use Symfony\Bundle\MakerBundle\ConsoleStyle; use Symfony\Bundle\MakerBundle\DependencyBuilder; +use Symfony\Bundle\MakerBundle\FileManager; use Symfony\Bundle\MakerBundle\Generator; use Symfony\Bundle\MakerBundle\InputAwareMakerInterface; use Symfony\Bundle\MakerBundle\InputConfiguration; use Symfony\Bundle\MakerBundle\Maker\AbstractMaker; +use Symfony\Bundle\MakerBundle\Maker\Common\UidTrait; +use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\ClassDetails; +use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassProperty; +use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; +use Symfony\Bundle\MakerBundle\Validator; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Question\Question; + +use function array_key_exists; +use function array_keys; +use function array_map; +use function class_exists; +use function dirname; +use function file_get_contents; +use function implode; +use function in_array; +use function preg_match; +use function sprintf; +use function str_starts_with; +use function substr; final class MakeDocument extends AbstractMaker implements InputAwareMakerInterface { + use UidTrait; + + private Generator $generator; + private DocumentClassGenerator $documentClassGenerator; + + public function __construct( + private FileManager $fileManager, + private MongoDBHelper $mongoDBHelper, + Generator|null $generator = null, + DocumentClassGenerator|null $documentClassGenerator = null, + ) { + if ($generator === null) { + $this->generator = new Generator($fileManager, 'App\\'); + } else { + $this->generator = $generator; + } + + if ($documentClassGenerator === null) { + $this->documentClassGenerator = new DocumentClassGenerator($this->generator, $this->mongoDBHelper); + } else { + $this->documentClassGenerator = $documentClassGenerator; + } + } + public static function getCommandName(): string { - return 'doctrine:mongodb:make:document'; + return 'make:document'; } public static function getCommandDescription(): string { - return 'Creates a new MongoDB ODM document class'; + return 'Create or update a MongoDB ODM document class'; } public function configureCommand(Command $command, InputConfiguration $inputConfig): void { - // TODO: Implement configureCommand() method. + $command + ->addArgument('name', InputArgument::OPTIONAL, sprintf('Class name of the document to create or update (e.g. %s)', Str::asClassName(Str::getRandomTerm()))) + ->addOption('regenerate', null, InputOption::VALUE_NONE, 'Instead of adding new fields, simply generate the methods (e.g. getter/setter) for existing fields') + ->addOption('overwrite', null, InputOption::VALUE_NONE, 'Overwrite any existing getter/setter methods') + ->setHelp(file_get_contents(dirname(__DIR__, 2) . '/config/help/MakeDocument.txt')); + + $this->addWithUuidOption($command); + + $inputConfig->setArgumentAsNonInteractive('name'); } - public function configureDependencies(DependencyBuilder $dependencies, InputInterface|null $input = null): void + public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void { - // TODO: Implement configureDependencies() method. + $documentClassName = $input->getArgument('name'); + if ($documentClassName && empty($this->verifyDocumentName($documentClassName))) { + return; + } + + if ($input->getOption('regenerate')) { + $io->block([ + 'This command will generate any missing methods (e.g. getters & setters) for a class or all classes in a namespace.', + 'To overwrite any existing methods, re-run this command with the --overwrite flag', + ], null, 'fg=yellow'); + $classOrNamespace = $io->ask('Enter a class or namespace to regenerate', $this->getDocumentNamespace(), Validator::notBlank(...)); + + $input->setArgument('name', $classOrNamespace); + + return; + } + + $this->checkIsUsingUid($input); + + $argument = $command->getDefinition()->getArgument('name'); + $question = $this->createDocumentClassQuestion($argument->getDescription()); + $documentClassName ??= $io->askQuestion($question); + + while ($dangerous = $this->verifyDocumentName($documentClassName)) { + if ($io->confirm(sprintf('"%s" contains one or more non-ASCII characters, which are potentially problematic with some database. It is recommended to use only ASCII characters for document names. Continue anyway?', $documentClassName), false)) { + break; + } + + $documentClassName = $io->askQuestion($question); + } + + $input->setArgument('name', $documentClassName); } public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void { - // TODO: Implement generate() method. + $overwrite = $input->getOption('overwrite'); + + // the regenerate option has entirely custom behavior + if ($input->getOption('regenerate')) { + $io->comment('Document regeneration is not yet implemented.'); + $this->writeSuccessMessage($io); + + return; + } + + $documentClassDetails = $generator->createClassNameDetails( + $input->getArgument('name'), + 'Document\\', + ); + + $classExists = class_exists($documentClassDetails->getFullName()); + if (! $classExists) { + $documentPath = $this->documentClassGenerator->generateDocumentClass( + documentClassDetails: $documentClassDetails, + idType: $this->getIdType(), + ); + + $generator->writeChanges(); + } + + if ($classExists) { + $documentPath = $this->getPathOfClass($documentClassDetails->getFullName()); + $io->text('Your document already exists! So let\'s add some new fields!'); + } else { + $io->text([ + '', + 'Document generated! Now let\'s add some fields!', + 'You can always add more fields later manually or by re-running this command.', + ]); + } + + $currentFields = $this->getPropertyNames($documentClassDetails->getFullName()); + $manipulator = $this->createClassManipulator($documentPath, $io, $overwrite); + + $isFirstField = true; + while (true) { + $newField = $this->askForNextField($io, $currentFields, $documentClassDetails->getFullName(), $isFirstField); + $isFirstField = false; + + if ($newField === null) { + break; + } + + if ($newField instanceof ClassProperty) { + $manipulator->addEntityField($newField); + $currentFields[] = $newField->propertyName; + } + + $this->fileManager->dumpFile($documentPath, $manipulator->getSourceCode()); + } + + $this->writeSuccessMessage($io); + $io->text([ + 'Next: Add more fields with the same command, or start using your document!', + '', + ]); + } + + public function configureDependencies(DependencyBuilder $dependencies, InputInterface|null $input = null): void + { + $dependencies->addClassDependency( + DoctrineMongoDBMappingsPass::class, + 'mongodb-odm-bundle', + ); + } + + /** @param string[] $fields */ + private function askForNextField(ConsoleStyle $io, array $fields, string $documentClass, bool $isFirstField): ClassProperty|null + { + $io->writeln(''); + + if ($isFirstField) { + $questionText = 'New property name (press to stop adding fields)'; + } else { + $questionText = 'Add another property? Enter the property name (or press to stop adding fields)'; + } + + $fieldName = $io->ask($questionText, null, function ($name) use ($fields) { + // allow it to be empty + if (! $name) { + return $name; + } + + if (in_array($name, $fields, true)) { + throw new InvalidArgumentException(sprintf('The "%s" property already exists.', $name)); + } + + return Validator::validateDoctrineFieldName($name, $this->mongoDBHelper->getRegistry()); + }); + + if (! $fieldName) { + return null; + } + + $defaultType = 'string'; + // try to guess the type by the field name prefix/suffix + $snakeCasedField = Str::asSnakeCase($fieldName); + + $suffix = substr($snakeCasedField, -3); + if ($suffix === '_at') { + $defaultType = 'date_immutable'; + } elseif ($suffix === '_id') { + $defaultType = 'int'; + } elseif (str_starts_with($snakeCasedField, 'is_')) { + $defaultType = 'bool'; + } elseif (str_starts_with($snakeCasedField, 'has_')) { + $defaultType = 'bool'; + } + + $type = null; + $types = $this->getTypesMap(); + + $allValidTypes = array_keys($types); + + while ($type === null) { + $question = new Question('Field type (enter ? to see all types)', $defaultType); + $question->setAutocompleterValues($allValidTypes); + $type = $io->askQuestion($question); + + if ($type === '?') { + $this->printAvailableTypes($io); + $io->writeln(''); + + $type = null; + } elseif (! in_array($type, $allValidTypes, true)) { + $this->printAvailableTypes($io); + $io->error(sprintf('Invalid type "%s".', $type)); + $io->writeln(''); + + $type = null; + } + } + + // this is a normal field + $classProperty = new ClassProperty(propertyName: $fieldName, type: $type); + + if ($type === 'string') { + // MongoDB doesn't have length constraints at the database level + // but we can still track it for validation purposes + $classProperty->length = null; + } + + if ($io->confirm('Can this field be null in the database (nullable)', false)) { + $classProperty->nullable = true; + } + + return $classProperty; + } + + private function printAvailableTypes(ConsoleStyle $io): void + { + $allTypes = $this->getTypesMap(); + + $typesTable = [ + 'main' => [ + 'string' => [], + 'int' => [], + 'float' => [], + 'bool' => [], + ], + 'array_object' => [ + 'hash' => [], + 'collection' => [], + 'object_id' => [], + ], + 'date_time' => [ + 'date' => ['date_immutable'], + 'timestamp' => [], + ], + ]; + + $printSection = static function (array $sectionTypes) use ($io, &$allTypes): void { + foreach ($sectionTypes as $mainType => $subTypes) { + if (! array_key_exists($mainType, $allTypes)) { + continue; + } + + foreach ($subTypes as $key => $potentialType) { + if (! array_key_exists($potentialType, $allTypes)) { + unset($subTypes[$key]); + } + + unset($allTypes[$potentialType]); + } + + unset($allTypes[$mainType]); + + $line = sprintf(' * %s', $mainType); + + if ($subTypes !== []) { + $line .= sprintf(' or %s', implode(' or ', array_map( + static fn ($subType) => sprintf('%s', $subType), + $subTypes, + ))); + } + + $io->writeln($line); + } + + $io->writeln(''); + }; + + $io->writeln('Main Types'); + $printSection($typesTable['main']); + + $io->writeln('Array/Object Types'); + $printSection($typesTable['array_object']); + + $io->writeln('Date/Time Types'); + $printSection($typesTable['date_time']); + + $io->writeln('Other Types'); + $allTypes = array_map(static fn () => [], $allTypes); + $printSection($allTypes); + } + + private function createDocumentClassQuestion(string $questionText): Question + { + $question = new Question($questionText); + $question->setValidator(Validator::notBlank(...)); + $question->setAutocompleterValues($this->mongoDBHelper->getDocumentsForAutocomplete()); + + return $question; + } + + /** @return string[] */ + private function verifyDocumentName(string $documentName): array + { + preg_match('/([^\x00-\x7F]+)/u', $documentName, $matches); + + return $matches; + } + + private function createClassManipulator(string $path, ConsoleStyle $io, bool $overwrite): ClassSourceManipulator + { + $manipulator = new ClassSourceManipulator( + sourceCode: $this->fileManager->getFileContents($path), + overwrite: $overwrite, + ); + + $manipulator->setIo($io); + + return $manipulator; + } + + private function getPathOfClass(string $class): string + { + return (new ClassDetails($class))->getPath(); + } + + /** @return string[] */ + private function getPropertyNames(string $class): array + { + if (! class_exists($class)) { + return []; + } + + $reflClass = new ReflectionClass($class); + + return array_map(static fn (ReflectionProperty $prop) => $prop->getName(), $reflClass->getProperties()); + } + + private function getDocumentNamespace(): string + { + return $this->mongoDBHelper->getDocumentNamespace(); + } + + /** @return array */ + private function getTypesMap(): array + { + return Type::getTypesMap(); } } diff --git a/src/MongoDB/DocumentClassGenerator.php b/src/MongoDB/DocumentClassGenerator.php new file mode 100644 index 0000000..ffaba12 --- /dev/null +++ b/src/MongoDB/DocumentClassGenerator.php @@ -0,0 +1,92 @@ +generator->createClassNameDetails( + $documentClassDetails->getRelativeName(), + 'Repository\\', + 'Repository', + ); + + $collectionName = $this->mongoDBHelper->getPotentialCollectionName($documentClassDetails->getFullName()); + + $useStatements = new UseStatementGenerator([ + $repoClassDetails->getFullName(), + ['Doctrine\\ODM\\MongoDB\\Mapping\\Annotations' => 'ODM'], + ]); + + $templatePath = dirname(__DIR__, 2) . '/templates/mongodb/Document.tpl.php'; + $documentPath = $this->generator->generateClass( + $documentClassDetails->getFullName(), + $templatePath, + [ + 'use_statements' => $useStatements, + 'repository_class_name' => $repoClassDetails->getShortName(), + 'should_escape_collection_name' => $this->mongoDBHelper->isKeyword($collectionName), + 'collection_name' => $collectionName, + 'id_type' => $idType, + ], + ); + + if ($generateRepositoryClass) { + $this->generateRepositoryClass( + $repoClassDetails->getFullName(), + $documentClassDetails->getFullName(), + true, + ); + } + + return $documentPath; + } + + public function generateRepositoryClass( + string $repositoryClass, + string $documentClass, + bool $includeExampleComments = true, + ): void { + $shortDocumentClass = Str::getShortClassName($documentClass); + + $useStatements = new UseStatementGenerator([ + $documentClass, + DocumentManager::class, + DocumentRepository::class, + ]); + + $templatePath = dirname(__DIR__, 2) . '/templates/mongodb/Repository.tpl.php'; + $this->generator->generateClass( + $repositoryClass, + $templatePath, + [ + 'use_statements' => $useStatements, + 'document_class_name' => $shortDocumentClass, + 'include_example_comments' => $includeExampleComments, + ], + ); + } +} diff --git a/src/MongoDB/MongoDBHelper.php b/src/MongoDB/MongoDBHelper.php new file mode 100644 index 0000000..20a3f32 --- /dev/null +++ b/src/MongoDB/MongoDBHelper.php @@ -0,0 +1,171 @@ +registry; + } + + public function getPotentialCollectionName(string $className): string + { + $shortClassName = Str::getShortClassName($className); + + return Str::asSnakeCase($shortClassName); + } + + public function isKeyword(string $name): bool + { + return in_array(lcfirst($name), self::MONGODB_KEYWORDS, true); + } + + public function getDocumentNamespace(): string + { + $documentManager = $this->registry->getManager(); + + if (! $documentManager instanceof DocumentManager) { + return 'App\\Document'; + } + + $configuration = $documentManager->getConfiguration(); + $documentNamespaces = []; + + foreach ($configuration->getMetadataDriverImpl()->getAllClassNames() as $className) { + $parts = explode('\\', $className); + if (count($parts) <= 1) { + continue; + } + + array_pop($parts); + $namespace = implode('\\', $parts); + $documentNamespaces[$namespace] = true; + } + + $namespaces = array_keys($documentNamespaces); + + // Prefer 'Document' namespace if it exists + foreach ($namespaces as $namespace) { + if (str_contains($namespace, '\\Document')) { + return $namespace; + } + } + + return $namespaces[0] ?? 'App\\Document'; + } + + /** @return string[] */ + public function getDocumentsForAutocomplete(): array + { + $documentManager = $this->registry->getManager(); + + if (! $documentManager instanceof DocumentManager) { + return []; + } + + $configuration = $documentManager->getConfiguration(); + $allDocuments = []; + + foreach ($configuration->getMetadataDriverImpl()->getAllClassNames() as $className) { + $allDocuments[] = $className; + $allDocuments[] = Str::getShortClassName($className); + } + + return $allDocuments; + } +} diff --git a/templates/mongodb/Document.tpl.php b/templates/mongodb/Document.tpl.php new file mode 100644 index 0000000..720bd6a --- /dev/null +++ b/templates/mongodb/Document.tpl.php @@ -0,0 +1,43 @@ + + + +namespace ; + + + +#[ODM\Document(repositoryClass: ::class)] +#[ODM\Collection(name: '')] + +class +{ + + #[ODM\Id(strategy: 'UUID')] + private ?string $id = null; + + public function getId(): ?string + { + return $this->id; + } + + #[ODM\Id(strategy: 'UUID')] + private ?string $id = null; + + public function getId(): ?string + { + return $this->id; + } + + #[ODM\Id] + private ?string $id = null; + + public function getId(): ?string + { + return $this->id; + } + +} + diff --git a/templates/mongodb/Repository.tpl.php b/templates/mongodb/Repository.tpl.php new file mode 100644 index 0000000..cb68f65 --- /dev/null +++ b/templates/mongodb/Repository.tpl.php @@ -0,0 +1,40 @@ + + +namespace ; + + + +/** + * @extends DocumentRepository<> + */ +class extends DocumentRepository +{ + public function __construct(DocumentManager $dm) + { + parent::__construct($dm, $dm->getUnitOfWork(), $dm->getClassMetadata(::class)); + } + + + /** + * @return [] + */ + public function findByExampleField(mixed $value): array + { + return $this->createQueryBuilder() + ->field('exampleField')->equals($value) + ->getQuery() + ->execute() + ->toArray(); + } + + public function findOneBySomeField(mixed $value): ? + + { + return $this->createQueryBuilder() + ->field('exampleField')->equals($value) + ->getQuery() + ->getSingleResult(); + } + +} + diff --git a/tests/Maker/MakeDocumentTest.php b/tests/Maker/MakeDocumentTest.php new file mode 100644 index 0000000..1e0198d --- /dev/null +++ b/tests/Maker/MakeDocumentTest.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Doctrine\Bundle\MongoDBMakerBundle\Tests\Maker; + +use Doctrine\Bundle\MongoDBMakerBundle\Maker\MakeDocument; +use Generator; +use Symfony\Bundle\MakerBundle\Test\MakerTestCase; +use Symfony\Bundle\MakerBundle\Test\MakerTestDetails; +use Symfony\Bundle\MakerBundle\Test\MakerTestRunner; +use Symfony\Component\HttpKernel\KernelInterface; + +use function getenv; +use function sprintf; +use function strpos; +use function substr; + +class MakeDocumentTest extends MakerTestCase +{ + protected function getMakerClass(): string + { + return MakeDocument::class; + } + + private static function createMakeDocumentTest(bool $withDatabase = true): MakerTestDetails + { + return self::buildMakerTest() + ->preRun(static function (MakerTestRunner $runner) use ($withDatabase): void { + // @todo We have to re-install the bundles to get the recipe-contrib applied + // https://github.com/symfony/flex/issues/1076 + // https://github.com/symfony/recipes/pull/1509 + $runner->runProcess('composer remove --no-scripts --dev doctrine/mongodb-maker-bundle'); + $runner->runProcess('composer config extra.symfony.allow-contrib true'); + $runner->runProcess('composer require --dev doctrine/mongodb-maker-bundle'); + + if (! $withDatabase) { + return; + } + + $runner->replaceInFile( + '.env', + 'mongodb://localhost:27017', + (string) getenv('MONGODB_URI'), + ); + }); + } + + protected function createKernel(): KernelInterface + { + return new MakerTestKernel('dev', true); + } + + public static function getTestDetails(): Generator + { + yield 'it_creates_a_new_class_basic' => [ + self::createMakeDocumentTest() + ->run(static function (MakerTestRunner $runner): void { + $runner->runMaker([ + // document class name + 'User', + // add not additional fields + '', + ]); + + self::runDocumentTest($runner); + }), + ]; + } + + /** @param array $data */ + private static function runDocumentTest(MakerTestRunner $runner, array $data = []): void + { + $runner->renderTemplateFile( + 'make-document/GeneratedDocumentTest.php.twig', + 'tests/GeneratedDocumentTest.php', + ['data' => $data], + ); + + //$runner->updateSchema(); + $runner->runTests(); + } + + private static function runCustomTest(MakerTestRunner $runner, string $filename, bool $withDatabase = true): void + { + $runner->copy( + 'make-document/tests/' . $filename, + 'tests/GeneratedDocumentTest.php', + ); + + if ($withDatabase) { + $runner->updateSchema(); + } + + $runner->runTests(); + } + + private static function copyDocument(MakerTestRunner $runner, string $filename): void + { + $documentClassName = substr( + $filename, + 0, + strpos($filename, '-'), + ); + + $runner->copy( + sprintf('make-document/documents/attributes/%s', $filename), + sprintf('src/Document/%s.php', $documentClassName), + ); + } + + private static function copyDocumentDirectory(MakerTestRunner $runner, string $directory): void + { + $runner->copy( + sprintf('make-document/%s/attributes', $directory), + '', + ); + } +} diff --git a/tests/Maker/MakerTestKernel.php b/tests/Maker/MakerTestKernel.php new file mode 100644 index 0000000..401df75 --- /dev/null +++ b/tests/Maker/MakerTestKernel.php @@ -0,0 +1,38 @@ +load(static function (ContainerBuilder $container): void { + $container->loadFromExtension('doctrine_mongodb', [ + 'default_connection' => 'default', + 'connections' => [ + 'default' => ['server' => 'mongodb://localhost:27017'], + ], + 'document_managers' => [ + 'default' => ['auto_mapping' => true], + ], + ]); + }); + } +} diff --git a/tests/TestKernel.php b/tests/TestKernel.php new file mode 100644 index 0000000..a83fc6d --- /dev/null +++ b/tests/TestKernel.php @@ -0,0 +1,62 @@ +load(static function (ContainerBuilder $container): void { + $container->loadFromExtension('framework', ['secret' => 'S0ME_SECRET']); + $container->loadFromExtension('doctrine_mongodb', [ + 'default_connection' => 'default', + 'connections' => [ + 'default' => ['server' => 'mongodb://localhost:27017'], + ], + 'document_managers' => [ + 'default' => ['auto_mapping' => true], + ], + ]); + $container->loadFromExtension('maker', []); + $container->loadFromExtension('mongodb_maker', ['generate_final_documents' => true]); + }); + } + + public function process(ContainerBuilder $container): void + { + /** + * Makes all makers public to help the tests + * + * @see \Symfony\Bundle\MakerBundle\Test\MakerTestKernel::process() + */ + foreach ($container->findTaggedServiceIds(MakeCommandRegistrationPass::MAKER_TAG) as $id => $tags) { + $defn = $container->getDefinition($id); + $defn->setPublic(true); + } + } +} diff --git a/tests/fixtures/make-document/GeneratedDocumentTest.php.twig b/tests/fixtures/make-document/GeneratedDocumentTest.php.twig new file mode 100644 index 0000000..f457764 --- /dev/null +++ b/tests/fixtures/make-document/GeneratedDocumentTest.php.twig @@ -0,0 +1,35 @@ +getContainer() + ->get('doctrine_mongodb') + ->getManager(); + + $dm->createQueryBuilder(User::class) + ->remove() + ->getQuery() + ->execute(); + + $user = new User(); + {% for field, value in data %} + $user->{{ field }} = '{{ value }}'; + {% endfor %} + $dm->persist($user); + $dm->flush(); + + $actualUser = $dm->getRepository(User::class)->findAll(); + + $this->assertcount(1, $actualUser); + } +} diff --git a/tests/fixtures/make-document/documents/User-basic.php b/tests/fixtures/make-document/documents/User-basic.php new file mode 100644 index 0000000..4ee4c77 --- /dev/null +++ b/tests/fixtures/make-document/documents/User-basic.php @@ -0,0 +1,17 @@ +