mirror of
https://github.com/doctrine/mongodb-maker-bundle.git
synced 2026-03-23 22:42:07 +01:00
Support adding search indexes to documents (#17)
* Create test trait for skipping functional tests So that tests can be skipped based on feature support * Support adding search indexes to Documents
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
The <info>%command.name%</info> command adds an index to an existing document class.
|
||||
Supports regular, unique, and search indexes.
|
||||
|
||||
<info>php %command.full_name% BlogPost</info>
|
||||
|
||||
@@ -6,6 +6,7 @@ parameters:
|
||||
- tests
|
||||
excludePaths:
|
||||
- tests/tmp
|
||||
- tests/fixtures/utils/MongoDBFunctionalTestCase.php
|
||||
|
||||
ignoreErrors:
|
||||
- identifier: class.notFound
|
||||
|
||||
20
src/Maker/Enum/IndexType.php
Normal file
20
src/Maker/Enum/IndexType.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Bundle\MongoDBMakerBundle\Maker\Enum;
|
||||
|
||||
use function array_map;
|
||||
|
||||
enum IndexType: string
|
||||
{
|
||||
case Regular = 'Regular';
|
||||
case Unique = 'Unique';
|
||||
case Search = 'Search';
|
||||
|
||||
/** @return string[] */
|
||||
public static function stringValues(): array
|
||||
{
|
||||
return array_map(static fn ($case) => $case->value, self::cases());
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,16 @@ declare(strict_types=1);
|
||||
namespace Doctrine\Bundle\MongoDBMakerBundle\Maker;
|
||||
|
||||
use Doctrine\Bundle\MongoDBBundle\DoctrineMongoDBBundle;
|
||||
use Doctrine\Bundle\MongoDBMakerBundle\Maker\Enum\IndexType;
|
||||
use Doctrine\Bundle\MongoDBMakerBundle\MongoDB\ClassSourceManipulator;
|
||||
use Doctrine\Bundle\MongoDBMakerBundle\MongoDB\MongoDBHelper;
|
||||
use Doctrine\ODM\MongoDB\Mapping\Attribute\Id;
|
||||
use Doctrine\ODM\MongoDB\Mapping\Attribute\SearchIndex;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use ReflectionClass;
|
||||
use ReflectionProperty;
|
||||
use RuntimeException;
|
||||
use Symfony\Bundle\MakerBundle\ConsoleStyle;
|
||||
use Symfony\Bundle\MakerBundle\DependencyBuilder;
|
||||
use Symfony\Bundle\MakerBundle\FileManager;
|
||||
@@ -24,7 +27,6 @@ use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
|
||||
use function array_filter;
|
||||
use function array_key_first;
|
||||
@@ -36,6 +38,7 @@ use function dirname;
|
||||
use function file_get_contents;
|
||||
use function in_array;
|
||||
use function preg_match;
|
||||
use function reset;
|
||||
use function sprintf;
|
||||
use function strtolower;
|
||||
|
||||
@@ -130,7 +133,7 @@ final class MakeIndex extends AbstractMaker implements MakerInterface
|
||||
sourceCode: $this->fileManager->getFileContents($documentPath),
|
||||
);
|
||||
|
||||
$this->addIndexRecursive($io, $manipulator, $fields, $documentPath);
|
||||
$this->addIndexRecursive($io, $manipulator, $reflectionClass->getProperties(), $documentPath);
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
@@ -141,19 +144,27 @@ final class MakeIndex extends AbstractMaker implements MakerInterface
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/** @param string[] $fields */
|
||||
/**
|
||||
* @param ReflectionProperty[] $fields
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function addIndexRecursive(ConsoleStyle $io, ClassSourceManipulator $manipulator, array $fields, string $documentPath): void
|
||||
{
|
||||
$io->writeln('');
|
||||
|
||||
$unique = $io->askQuestion(new ConfirmationQuestion('Should this index be unique?', false));
|
||||
$keys = $this->askForKeys($io, $fields);
|
||||
$question = new ChoiceQuestion(
|
||||
'What type of index do you want to create?',
|
||||
IndexType::stringValues(),
|
||||
IndexType::Regular->value,
|
||||
);
|
||||
|
||||
$this->createAttribute($unique, $keys, $manipulator);
|
||||
$typeStr = $io->askQuestion($question);
|
||||
$type = IndexType::from($typeStr);
|
||||
|
||||
$this->createAttribute($io, $type, $fields, $manipulator);
|
||||
$this->fileManager->dumpFile($documentPath, $manipulator->getSourceCode());
|
||||
|
||||
$io->info('Index added.');
|
||||
|
||||
$createAnother = $io->ask('Would you like to add another index to this document? (y/n)', 'n', static function ($answer) {
|
||||
if (! in_array(strtolower($answer), ['y', 'n'], true)) {
|
||||
throw new InvalidArgumentException('Please enter "y" or "n".');
|
||||
@@ -176,13 +187,29 @@ final class MakeIndex extends AbstractMaker implements MakerInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,string> $keys
|
||||
|
||||
* @param ReflectionProperty[] $fields
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function createAttribute(bool $unique, array $keys, ClassSourceManipulator $manipulator): void
|
||||
private function createAttribute(ConsoleStyle $io, IndexType $type, array $fields, ClassSourceManipulator $manipulator): void
|
||||
{
|
||||
$attributeClass = $unique ? 'ODM\UniqueIndex' : 'ODM\Index';
|
||||
match ($type) {
|
||||
IndexType::Regular => $this->createIndexAttribute('ODM\Index', $io, $fields, $manipulator),
|
||||
IndexType::Unique => $this->createIndexAttribute('ODM\UniqueIndex', $io, $fields, $manipulator),
|
||||
IndexType::Search => $this->createSearchIndexAttribute($io, $manipulator, $fields),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionProperty[] $fields
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function createIndexAttribute(string $attributeClass, ConsoleStyle $io, array $fields, ClassSourceManipulator $manipulator): void
|
||||
{
|
||||
$fields = self::filterIdentifiers($fields);
|
||||
$keys = $this->askForKeys($io, $fields);
|
||||
|
||||
$createClassAttribute = count($keys) > 1;
|
||||
|
||||
if ($createClassAttribute) {
|
||||
@@ -193,20 +220,66 @@ final class MakeIndex extends AbstractMaker implements MakerInterface
|
||||
|
||||
$field = array_key_first($keys);
|
||||
$manipulator->addAttributeToProperty($attributeClass, (string) $field, ['order' => $keys[$field]]);
|
||||
|
||||
$io->info('Index added.');
|
||||
}
|
||||
|
||||
/** @param ReflectionProperty[] $fields */
|
||||
private function createSearchIndexAttribute(
|
||||
ConsoleStyle $io,
|
||||
ClassSourceManipulator $manipulator,
|
||||
array $fields,
|
||||
): void {
|
||||
$name = $io->ask('What is the name of the index?', 'default');
|
||||
|
||||
$isDynamic = $io->confirm('Is the search index dynamic?');
|
||||
|
||||
if ($isDynamic) {
|
||||
$manipulator->addAttributeToClass(SearchIndex::class, ['dynamic' => true, 'name' => $name]);
|
||||
} else {
|
||||
$selectedFields = self::selectFields($fields, $io);
|
||||
|
||||
$options = [];
|
||||
foreach ($selectedFields as $field) {
|
||||
$property = array_filter($fields, static fn (ReflectionProperty $prop) => $prop->name === $field);
|
||||
$property = reset($property);
|
||||
|
||||
if (! $property) {
|
||||
throw new RuntimeException(sprintf('Field "%s" not found.', $field));
|
||||
}
|
||||
|
||||
$type = MongoDBHelper::guessSearchIndexTypeForProperty($property);
|
||||
|
||||
$options[$field] = $type ? ['type' => $type] : [];
|
||||
}
|
||||
|
||||
$manipulator->addAttributeToClass(SearchIndex::class, ['fields' => $options, 'name' => $name]);
|
||||
}
|
||||
|
||||
$io->info(sprintf(
|
||||
'Search index added.%s',
|
||||
$isDynamic ? '' : ' Remember to review the generated field mappings in the `SearchIndex` attribute and adjust them as needed.',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $fields
|
||||
* @param ReflectionProperty[] $properties
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private static function getPropertyNames(array $properties): array
|
||||
{
|
||||
return array_map(static fn (ReflectionProperty $prop) => $prop->name, $properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionProperty[] $fields
|
||||
*
|
||||
* @return array<string,string> $keys
|
||||
*/
|
||||
private function askForKeys(ConsoleStyle $io, array $fields): array
|
||||
{
|
||||
$question = new ChoiceQuestion('Select one or more keys for the index (or <return> to finish)', $fields);
|
||||
$question->setMultiselect(true);
|
||||
|
||||
$selection = $io->askQuestion($question);
|
||||
|
||||
$selection = self::selectFields($fields, $io);
|
||||
$keys = [];
|
||||
|
||||
foreach ($selection as $key) {
|
||||
@@ -221,4 +294,32 @@ final class MakeIndex extends AbstractMaker implements MakerInterface
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionProperty[] $properties
|
||||
*
|
||||
* @return ReflectionProperty[]
|
||||
*/
|
||||
private static function filterIdentifiers(array $properties): array
|
||||
{
|
||||
/** Filter any identifiers. We don't consider them valid candidates for regular and unique indexes */
|
||||
return array_values(array_filter(
|
||||
$properties,
|
||||
static fn (ReflectionProperty $prop) => empty($prop->getAttributes(Id::class)),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionProperty[] $fields
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private static function selectFields(array $fields, ConsoleStyle $io): array
|
||||
{
|
||||
$options = self::getPropertyNames($fields);
|
||||
$question = new ChoiceQuestion('Select one or more fields for the index (or <return> to finish)', $options);
|
||||
$question->setMultiselect(true);
|
||||
|
||||
return $io->askQuestion($question);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,19 +6,25 @@ namespace Doctrine\Bundle\MongoDBMakerBundle\MongoDB;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\ODM\MongoDB\DocumentManager;
|
||||
use Doctrine\ODM\MongoDB\Mapping\Attribute\EmbedMany;
|
||||
use Doctrine\ODM\MongoDB\Mapping\Attribute\EmbedOne;
|
||||
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
|
||||
use Doctrine\ODM\MongoDB\Types\Type;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory;
|
||||
use MongoDB\BSON\ObjectId;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionProperty;
|
||||
use Symfony\Bundle\MakerBundle\Str;
|
||||
use Symfony\Bundle\MakerBundle\Util\ClassNameDetails;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
use Throwable;
|
||||
|
||||
use function array_filter;
|
||||
use function array_first;
|
||||
use function array_flip;
|
||||
use function array_keys;
|
||||
@@ -27,6 +33,7 @@ use function assert;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function sort;
|
||||
use function sprintf;
|
||||
use function str_contains;
|
||||
@@ -233,4 +240,63 @@ final class MongoDBHelper
|
||||
|
||||
return sprintf('Type::%s', $constants[$fieldType]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a property, make a best-effort guess for its search index type.
|
||||
*/
|
||||
public static function guessSearchIndexTypeForProperty(ReflectionProperty $property): string|null
|
||||
{
|
||||
if (! $property->hasType()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = $property->getType();
|
||||
assert($type instanceof ReflectionNamedType);
|
||||
$typeString = $type->getName();
|
||||
|
||||
if ($type->isBuiltin()) {
|
||||
if ($typeString === 'array' && self::hasEmbeddingAttribute($property)) {
|
||||
return 'embeddedDocuments';
|
||||
}
|
||||
|
||||
return self::deriveSearchIndexTypeFromNativeType($typeString);
|
||||
}
|
||||
|
||||
if ($typeString === Uuid::class) {
|
||||
return 'uuid';
|
||||
}
|
||||
|
||||
if ($typeString === ObjectId::class) {
|
||||
return 'objectId';
|
||||
}
|
||||
|
||||
if (in_array($typeString, [DateTime::class, DateTimeImmutable::class, DateTimeInterface::class])) {
|
||||
return 'date';
|
||||
}
|
||||
|
||||
if (self::hasEmbeddingAttribute($property)) {
|
||||
return 'document';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function deriveSearchIndexTypeFromNativeType(string $type): string|null
|
||||
{
|
||||
return match ($type) {
|
||||
'bool' => 'bool',
|
||||
'float', 'int' => 'number',
|
||||
'string' => 'string',
|
||||
'object' => 'document',
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
private static function hasEmbeddingAttribute(ReflectionProperty $property): bool
|
||||
{
|
||||
return ! empty(array_filter(
|
||||
$property->getAttributes(),
|
||||
static fn ($attr) => in_array($attr->getName(), [EmbedMany::class, EmbedOne::class]),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ class MakeIndexTest extends MakerTestCase
|
||||
{
|
||||
return self::buildMakerTest()
|
||||
->preRun(static function (MakerTestRunner $runner) use ($withDatabase): void {
|
||||
$runner->runConsole('doctrine:mongodb:schema:update', ['--force' => true]);
|
||||
|
||||
if (! $withDatabase) {
|
||||
return;
|
||||
}
|
||||
@@ -39,6 +41,16 @@ class MakeIndexTest extends MakerTestCase
|
||||
sprintf('make-index/documents/User.php'),
|
||||
sprintf('src/Document/User.php'),
|
||||
);
|
||||
|
||||
$runner->copy(
|
||||
sprintf('make-index/documents/SearchableUser.php'),
|
||||
sprintf('src/Document/SearchableUser.php'),
|
||||
);
|
||||
|
||||
$runner->copy(
|
||||
sprintf('utils/MongoDBFunctionalTestCase.php'),
|
||||
sprintf('src/MongoDBFunctionalTestCase.php'),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -55,7 +67,7 @@ class MakeIndexTest extends MakerTestCase
|
||||
$runner->runMaker([
|
||||
// document class name
|
||||
'User',
|
||||
// index should not be unique
|
||||
// should be a regular index
|
||||
'',
|
||||
// add an index on `lastName`
|
||||
'1',
|
||||
@@ -80,7 +92,7 @@ class MakeIndexTest extends MakerTestCase
|
||||
$runner->runMaker([
|
||||
// document class name
|
||||
'User',
|
||||
// index should not be unique
|
||||
// should be a regular index
|
||||
'',
|
||||
// add an index on `lastName`
|
||||
'1',
|
||||
@@ -89,7 +101,7 @@ class MakeIndexTest extends MakerTestCase
|
||||
// create another index
|
||||
'y',
|
||||
// index should be unique
|
||||
'y',
|
||||
'Unique',
|
||||
// add an index on `firstName`
|
||||
'0',
|
||||
// `desc` order for `firstName`
|
||||
@@ -115,7 +127,7 @@ class MakeIndexTest extends MakerTestCase
|
||||
$runner->runMaker([
|
||||
// document class name
|
||||
'User',
|
||||
// index should be unique
|
||||
// should be a regular index
|
||||
'',
|
||||
// select `firstName` and `lastName` as keys for the index
|
||||
'0,1',
|
||||
@@ -135,6 +147,30 @@ class MakeIndexTest extends MakerTestCase
|
||||
]);
|
||||
}),
|
||||
];
|
||||
|
||||
yield 'it creates search indexes' => [
|
||||
self::createMakeIndexTest()
|
||||
->run(static function (MakerTestRunner $runner): void {
|
||||
$runner->runMaker([
|
||||
// document class name
|
||||
'SearchableUser',
|
||||
// should be a search index
|
||||
'Search',
|
||||
// Default index name
|
||||
'',
|
||||
// should be statically mapped
|
||||
'n',
|
||||
'lastName',
|
||||
]);
|
||||
|
||||
self::runSearchIndexTest($runner, [
|
||||
'default' => [
|
||||
'type' => 'search',
|
||||
'fields' => ['lastName' => 'string'],
|
||||
],
|
||||
]);
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $data */
|
||||
@@ -148,4 +184,16 @@ class MakeIndexTest extends MakerTestCase
|
||||
|
||||
$runner->runTests();
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $data */
|
||||
private static function runSearchIndexTest(MakerTestRunner $runner, array $data = []): void
|
||||
{
|
||||
$runner->renderTemplateFile(
|
||||
'make-index/GeneratedSearchIndexesTest.php.twig',
|
||||
'tests/GeneratedSearchIndexesTest.php',
|
||||
['data' => $data],
|
||||
);
|
||||
|
||||
$runner->runTests();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ use Symfony\Bundle\MakerBundle\Test\MakerTestKernel as MakerBundleTestKernel;
|
||||
use Symfony\Component\Config\Loader\LoaderInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
use function getenv;
|
||||
|
||||
class MakerTestKernel extends MakerBundleTestKernel
|
||||
{
|
||||
public function registerBundles(): iterable
|
||||
@@ -24,10 +26,11 @@ class MakerTestKernel extends MakerBundleTestKernel
|
||||
parent::registerContainerConfiguration($loader);
|
||||
|
||||
$loader->load(static function (ContainerBuilder $container): void {
|
||||
$uri = getenv('MONGODB_URI') ?: 'mongodb://localhost:27017';
|
||||
$container->loadFromExtension('doctrine_mongodb', [
|
||||
'default_connection' => 'default',
|
||||
'connections' => [
|
||||
'default' => ['server' => 'mongodb://localhost:27017'],
|
||||
'default' => ['server' => $uri],
|
||||
],
|
||||
'document_managers' => [
|
||||
'default' => ['auto_mapping' => true],
|
||||
|
||||
@@ -15,23 +15,25 @@ namespace Doctrine\Bundle\MongoDBMakerBundle\Tests\MongoDB;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\Bundle\MongoDBMakerBundle\MongoDB\MongoDBHelper;
|
||||
use Doctrine\ODM\MongoDB\Configuration;
|
||||
use Doctrine\ODM\MongoDB\DocumentManager;
|
||||
use Doctrine\ODM\MongoDB\Mapping\Attribute as ODM;
|
||||
use Doctrine\ODM\MongoDB\Mapping\Driver\AttributeDriver;
|
||||
use Doctrine\ODM\MongoDB\Types\Type;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use Generator;
|
||||
use MongoDB\BSON\ObjectId;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\MockObject\Stub;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionProperty;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
use function class_exists;
|
||||
use function get_debug_type;
|
||||
|
||||
class MongoDBHelperTest extends TestCase
|
||||
{
|
||||
private MongoDBHelper $helper;
|
||||
@@ -213,6 +215,82 @@ class MongoDBHelperTest extends TestCase
|
||||
$this->assertSame('Type::STRING', MongoDBHelper::getTypeConstant(Type::STRING));
|
||||
$this->assertNull(MongoDBHelper::getTypeConstant('unknown_type'));
|
||||
}
|
||||
|
||||
#[DataProvider('guessSearchIndexTypeForPropertyDataProvider')]
|
||||
public function testGuessSearchIndexTypeForProperty(ReflectionProperty $property, string|null $expected): void
|
||||
{
|
||||
$this->assertSame($expected, MongoDBHelper::guessSearchIndexTypeForProperty($property));
|
||||
}
|
||||
|
||||
public static function guessSearchIndexTypeForPropertyDataProvider(): Generator
|
||||
{
|
||||
$class = new ReflectionClass(TypeMockClass::class);
|
||||
|
||||
yield 'bool property' => [
|
||||
$class->getProperty('bool'),
|
||||
'bool',
|
||||
];
|
||||
|
||||
yield 'string property' => [
|
||||
$class->getProperty('string'),
|
||||
'string',
|
||||
];
|
||||
|
||||
yield 'float property' => [
|
||||
$class->getProperty('float'),
|
||||
'number',
|
||||
];
|
||||
|
||||
yield 'int property' => [
|
||||
$class->getProperty('int'),
|
||||
'number',
|
||||
];
|
||||
|
||||
yield 'Uuid property' => [
|
||||
$class->getProperty('uuid'),
|
||||
'uuid',
|
||||
];
|
||||
|
||||
yield 'ObjectId property' => [
|
||||
$class->getProperty('someObjectId'),
|
||||
'objectId',
|
||||
];
|
||||
|
||||
yield 'DateTime' => [
|
||||
$class->getProperty('dateTime'),
|
||||
'date',
|
||||
];
|
||||
|
||||
yield 'DateTimeImmutable property' => [
|
||||
$class->getProperty('dateTimeImmutable'),
|
||||
'date',
|
||||
];
|
||||
|
||||
yield 'DateTimeInterface property' => [
|
||||
$class->getProperty('dateTimeInterface'),
|
||||
'date',
|
||||
];
|
||||
|
||||
yield 'array of objects property' => [
|
||||
$class->getProperty('arrayOfEmbeddedDocuments'),
|
||||
'embeddedDocuments',
|
||||
];
|
||||
|
||||
yield 'embedded document property' => [
|
||||
$class->getProperty('embeddedDocument'),
|
||||
'document',
|
||||
];
|
||||
|
||||
yield 'nested arrays property' => [
|
||||
$class->getProperty('nestedArrays'),
|
||||
null,
|
||||
];
|
||||
|
||||
yield 'mixed property' => [
|
||||
$class->getProperty('mixed'),
|
||||
null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class CustomType extends Type
|
||||
@@ -222,3 +300,31 @@ class CustomType extends Type
|
||||
return 'foo';
|
||||
}
|
||||
}
|
||||
|
||||
class TypeMockClass
|
||||
{
|
||||
public ?bool $bool;
|
||||
public ?string $string;
|
||||
public ?float $float;
|
||||
public ?int $int;
|
||||
public ?Uuid $uuid;
|
||||
public ?ObjectId $someObjectId;
|
||||
public ?DateTime $dateTime;
|
||||
public ?DateTimeImmutable $dateTimeImmutable;
|
||||
public ?DateTimeInterface $dateTimeInterface;
|
||||
|
||||
/** @var string[] */
|
||||
public array $arrayOfStrings;
|
||||
|
||||
/** @var object[] */
|
||||
#[ODM\EmbedMany]
|
||||
public array $arrayOfEmbeddedDocuments;
|
||||
|
||||
#[ODM\EmbedOne]
|
||||
public object $embeddedDocument;
|
||||
|
||||
/** @var array<string[]> */
|
||||
public array $nestedArrays;
|
||||
|
||||
public mixed $mixed;
|
||||
}
|
||||
|
||||
57
tests/fixtures/make-index/GeneratedSearchIndexesTest.php.twig
vendored
Normal file
57
tests/fixtures/make-index/GeneratedSearchIndexesTest.php.twig
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests;
|
||||
|
||||
use App\MongoDBFunctionalTestCase;
|
||||
use App\Document\SearchableUser;
|
||||
use Doctrine\ODM\MongoDB\DocumentManager;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
class GeneratedDocumentTest extends KernelTestCase
|
||||
{
|
||||
use MongoDBFunctionalTestCase;
|
||||
|
||||
public function setup(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
/** @var DocumentManager $dm */
|
||||
$dm = self::$kernel->getContainer()
|
||||
->get('doctrine_mongodb')
|
||||
->getManager();
|
||||
|
||||
self::skipIfSearchIndexesNotSupported($dm);
|
||||
|
||||
$schemaManager = $dm->getSchemaManager();
|
||||
$schemaManager->dropDocumentCollection(SearchableUser::class);
|
||||
}
|
||||
|
||||
public function testGeneratedDocument()
|
||||
{
|
||||
self::bootKernel();
|
||||
/** @var DocumentManager $dm */
|
||||
$dm = self::$kernel->getContainer()
|
||||
->get('doctrine_mongodb')
|
||||
->getManager();
|
||||
|
||||
$schemaManager = $dm->getSchemaManager();
|
||||
|
||||
$schemaManager->createDocumentCollection(SearchableUser::class);
|
||||
$schemaManager->createDocumentSearchIndexes(SearchableUser::class);
|
||||
$iterator = $dm->getDocumentCollection(SearchableUser::class)->listSearchIndexes();
|
||||
|
||||
while ($current = $iterator->current()) {
|
||||
if (! isset($indexes[$current['name']])) {
|
||||
$indexes[$current['name']]['type'] = $current['type'] ?? null;
|
||||
foreach ($current['latestDefinition']['mappings']['fields'] as $fieldName => $mapping) {
|
||||
$indexes[$current['name']]['fields'][$fieldName] = $mapping['type'];
|
||||
}
|
||||
}
|
||||
|
||||
$iterator->next();
|
||||
}
|
||||
|
||||
$expected = json_decode('{{ data|json_encode|raw }}', true);
|
||||
|
||||
$this->assertSame($expected, $indexes);
|
||||
}
|
||||
}
|
||||
20
tests/fixtures/make-index/documents/SearchableUser.php
vendored
Normal file
20
tests/fixtures/make-index/documents/SearchableUser.php
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Document;
|
||||
|
||||
use Doctrine\ODM\MongoDB\Mapping\Attribute as ODM;
|
||||
|
||||
#[ODM\Document]
|
||||
class SearchableUser
|
||||
{
|
||||
#[ODM\Field]
|
||||
public ?string $firstName = null;
|
||||
|
||||
#[ODM\Id]
|
||||
public ?int $id = null;
|
||||
|
||||
#[ODM\Field]
|
||||
public ?string $lastName = null;
|
||||
}
|
||||
33
tests/fixtures/utils/MongoDBFunctionalTestCase.php
vendored
Normal file
33
tests/fixtures/utils/MongoDBFunctionalTestCase.php
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
use Doctrine\ODM\MongoDB\DocumentManager;
|
||||
use MongoDB\Driver\Command;
|
||||
use MongoDB\Driver\Exception\ServerException;
|
||||
|
||||
/**
|
||||
* This trait can be used inside generated tests.
|
||||
* Copy it over to the test file system and use it in the generated test class.
|
||||
*/
|
||||
trait MongoDBFunctionalTestCase
|
||||
{
|
||||
public static function skipIfSearchIndexesNotSupported(DocumentManager $dm): void
|
||||
{
|
||||
try {
|
||||
$db = $dm->getClient()->selectDatabase($dm->getConfiguration()->getDefaultDB());
|
||||
$db->dropCollection(__METHOD__);
|
||||
$db->createCollection(__METHOD__);
|
||||
$db->getCollection(__METHOD__)->dropSearchIndex('nonexistent-index');
|
||||
} catch (ServerException $exception) {
|
||||
// Code 27 = Search index does not exist, which indicates that the feature is supported
|
||||
if ($exception->getCode() === 27) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::markTestSkipped($exception->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user