Skip to content

Commit 61df6f5

Browse files
authored
Merge branch '5.x' into l12-compatibility
2 parents 7680465 + cb3b32c commit 61df6f5

File tree

6 files changed

+107
-15
lines changed

6 files changed

+107
-15
lines changed

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@
5151
},
5252
"require-dev": {
5353
"mongodb/builder": "^0.2",
54-
"laravel/scout": "^11",
54+
"laravel/scout": "^10.3",
5555
"league/flysystem-gridfs": "^3.28",
5656
"league/flysystem-read-only": "^3.0",
5757
"phpunit/phpunit": "^10.3|^11.5.3",
5858
"orchestra/testbench": "^8.0|^9.0|^10.0",
59-
"mockery/mockery": "^1.4.4",
59+
"mockery/mockery": "^1.4.4@stable",
6060
"doctrine/coding-standard": "12.0.x-dev",
6161
"spatie/laravel-query-builder": "^5.6",
6262
"phpstan/phpstan": "^1.10|^2.1",

docs/includes/fundamentals/as-avs/AtlasSearchTest.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use MongoDB\Driver\Exception\ServerException;
1212
use MongoDB\Laravel\Schema\Builder;
1313
use MongoDB\Laravel\Tests\TestCase;
14+
use PHPUnit\Framework\Attributes\Group;
1415

1516
use function array_map;
1617
use function mt_getrandmax;
@@ -19,6 +20,7 @@
1920
use function srand;
2021
use function usleep;
2122

23+
#[Group('atlas-search')]
2224
class AtlasSearchTest extends TestCase
2325
{
2426
private array $vectors;
@@ -84,7 +86,7 @@ protected function setUp(): void
8486
do {
8587
$ready = true;
8688
usleep(10_000);
87-
foreach ($collection->listSearchIndexes() as $index) {
89+
foreach ($moviesCollection->listSearchIndexes() as $index) {
8890
if ($index['status'] !== 'READY') {
8991
$ready = false;
9092
}
@@ -102,7 +104,7 @@ public function testSimpleSearch(): void
102104
$movies = Movie::search(
103105
sort: ['title' => 1],
104106
operator: Search::text('title', 'dream'),
105-
)->get();
107+
)->all();
106108
// end-search-query
107109

108110
$this->assertNotNull($movies);
@@ -113,10 +115,10 @@ public function testSimpleSearch(): void
113115
* @runInSeparateProcess
114116
* @preserveGlobalState disabled
115117
*/
116-
public function autocompleteSearchTest(): void
118+
public function testAutocompleteSearch(): void
117119
{
118120
// start-auto-query
119-
$movies = Movie::autocomplete('title', 'jak')->get();
121+
$movies = Movie::autocomplete('title', 'jak')->all();
120122
// end-auto-query
121123

122124
$this->assertNotNull($movies);
@@ -127,9 +129,9 @@ public function autocompleteSearchTest(): void
127129
* @runInSeparateProcess
128130
* @preserveGlobalState disabled
129131
*/
130-
public function vectorSearchTest(): void
132+
public function testVectorSearch(): void
131133
{
132-
$results = Book::vectorSearch(
134+
$results = Movie::vectorSearch(
133135
index: 'vector',
134136
path: 'vector4',
135137
queryVector: $this->vectors[0],
@@ -141,7 +143,7 @@ public function vectorSearchTest(): void
141143
);
142144

143145
$this->assertNotNull($results);
144-
$this->assertSame('C', $results->first()->title);
146+
$this->assertSame('D', $results->first()->title);
145147
}
146148

147149
/** Generates random vectors using fixed seed to make tests deterministic */

src/MongoDBServiceProvider.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,11 @@ private function registerScoutEngine(): void
167167
$connectionName = $app->get('config')->get('scout.mongodb.connection', 'mongodb');
168168
$connection = $app->get('db')->connection($connectionName);
169169
$softDelete = (bool) $app->get('config')->get('scout.soft_delete', false);
170+
$indexDefinitions = $app->get('config')->get('scout.mongodb.index-definitions', []);
170171

171172
assert($connection instanceof Connection, new InvalidArgumentException(sprintf('The connection "%s" is not a MongoDB connection.', $connectionName)));
172173

173-
return new ScoutEngine($connection->getMongoDB(), $softDelete);
174+
return new ScoutEngine($connection->getMongoDB(), $softDelete, $indexDefinitions);
174175
});
175176

176177
return $engineManager;

src/Scout/ScoutEngine.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Illuminate\Database\Eloquent\SoftDeletes;
1010
use Illuminate\Support\Collection;
1111
use Illuminate\Support\LazyCollection;
12+
use InvalidArgumentException;
1213
use Laravel\Scout\Builder;
1314
use Laravel\Scout\Engines\Engine;
1415
use Laravel\Scout\Searchable;
@@ -66,9 +67,11 @@ final class ScoutEngine extends Engine
6667

6768
private const TYPEMAP = ['root' => 'object', 'document' => 'bson', 'array' => 'bson'];
6869

70+
/** @param array<string, array> $indexDefinitions */
6971
public function __construct(
7072
private Database $database,
7173
private bool $softDelete,
74+
private array $indexDefinitions = [],
7275
) {
7376
}
7477

@@ -435,14 +438,16 @@ public function createIndex($name, array $options = []): void
435438
{
436439
assert(is_string($name), new TypeError(sprintf('Argument #1 ($name) must be of type string, %s given', get_debug_type($name))));
437440

441+
$definition = $this->indexDefinitions[$name] ?? self::DEFAULT_DEFINITION;
442+
if (! isset($definition['mappings'])) {
443+
throw new InvalidArgumentException(sprintf('Invalid search index definition for collection "%s", the "mappings" key is required. Find documentation at https://www.mongodb.com/docs/manual/reference/command/createSearchIndexes/#search-index-definition-syntax', $name));
444+
}
445+
438446
// Ensure the collection exists before creating the search index
439447
$this->database->createCollection($name);
440448

441449
$collection = $this->database->selectCollection($name);
442-
$collection->createSearchIndex(
443-
self::DEFAULT_DEFINITION,
444-
['name' => self::INDEX_NAME],
445-
);
450+
$collection->createSearchIndex($definition, ['name' => self::INDEX_NAME]);
446451

447452
if ($options['wait'] ?? true) {
448453
$this->wait(function () use ($collection) {

tests/Scout/ScoutEngineTest.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22

33
namespace MongoDB\Laravel\Tests\Scout;
44

5+
use ArrayIterator;
56
use Closure;
67
use DateTimeImmutable;
78
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
89
use Illuminate\Support\Collection as LaravelCollection;
910
use Illuminate\Support\LazyCollection;
1011
use Laravel\Scout\Builder;
1112
use Laravel\Scout\Jobs\RemoveFromSearch;
13+
use LogicException;
1214
use Mockery as m;
15+
use MongoDB\BSON\Document;
1316
use MongoDB\BSON\UTCDateTime;
1417
use MongoDB\Collection;
1518
use MongoDB\Database;
@@ -31,6 +34,82 @@ class ScoutEngineTest extends TestCase
3134
{
3235
private const EXPECTED_TYPEMAP = ['root' => 'object', 'document' => 'bson', 'array' => 'bson'];
3336

37+
public function testCreateIndexInvalidDefinition(): void
38+
{
39+
$database = m::mock(Database::class);
40+
$engine = new ScoutEngine($database, false, ['collection_invalid' => ['foo' => 'bar']]);
41+
42+
$this->expectException(LogicException::class);
43+
$this->expectExceptionMessage('Invalid search index definition for collection "collection_invalid", the "mappings" key is required.');
44+
$engine->createIndex('collection_invalid');
45+
}
46+
47+
public function testCreateIndex(): void
48+
{
49+
$collectionName = 'collection_custom';
50+
$expectedDefinition = [
51+
'mappings' => [
52+
'dynamic' => true,
53+
],
54+
];
55+
56+
$database = m::mock(Database::class);
57+
$collection = m::mock(Collection::class);
58+
$database->shouldReceive('createCollection')
59+
->once()
60+
->with($collectionName);
61+
$database->shouldReceive('selectCollection')
62+
->with($collectionName)
63+
->andReturn($collection);
64+
$collection->shouldReceive('createSearchIndex')
65+
->once()
66+
->with($expectedDefinition, ['name' => 'scout']);
67+
$collection->shouldReceive('listSearchIndexes')
68+
->once()
69+
->with(['name' => 'scout', 'typeMap' => ['root' => 'bson']])
70+
->andReturn(new ArrayIterator([Document::fromPHP(['name' => 'scout', 'status' => 'READY'])]));
71+
72+
$engine = new ScoutEngine($database, false, []);
73+
$engine->createIndex($collectionName);
74+
}
75+
76+
public function testCreateIndexCustomDefinition(): void
77+
{
78+
$collectionName = 'collection_custom';
79+
$expectedDefinition = [
80+
'mappings' => [
81+
[
82+
'analyzer' => 'lucene.standard',
83+
'fields' => [
84+
[
85+
'name' => 'wildcard',
86+
'type' => 'string',
87+
],
88+
],
89+
],
90+
],
91+
];
92+
93+
$database = m::mock(Database::class);
94+
$collection = m::mock(Collection::class);
95+
$database->shouldReceive('createCollection')
96+
->once()
97+
->with($collectionName);
98+
$database->shouldReceive('selectCollection')
99+
->with($collectionName)
100+
->andReturn($collection);
101+
$collection->shouldReceive('createSearchIndex')
102+
->once()
103+
->with($expectedDefinition, ['name' => 'scout']);
104+
$collection->shouldReceive('listSearchIndexes')
105+
->once()
106+
->with(['name' => 'scout', 'typeMap' => ['root' => 'bson']])
107+
->andReturn(new ArrayIterator([Document::fromPHP(['name' => 'scout', 'status' => 'READY'])]));
108+
109+
$engine = new ScoutEngine($database, false, [$collectionName => $expectedDefinition]);
110+
$engine->createIndex($collectionName);
111+
}
112+
34113
/** @param callable(): Builder $builder */
35114
#[DataProvider('provideSearchPipelines')]
36115
public function testSearch(Closure $builder, array $expectedPipeline): void

tests/Scout/ScoutIntegrationTest.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use function array_merge;
1818
use function count;
1919
use function env;
20+
use function iterator_to_array;
2021
use function Orchestra\Testbench\artisan;
2122
use function range;
2223
use function sprintf;
@@ -38,6 +39,9 @@ protected function getEnvironmentSetUp($app): void
3839

3940
$app['config']->set('scout.driver', 'mongodb');
4041
$app['config']->set('scout.prefix', 'prefix_');
42+
$app['config']->set('scout.mongodb.index-definitions', [
43+
'prefix_scout_users' => ['mappings' => ['dynamic' => true, 'fields' => ['bool_field' => ['type' => 'boolean']]]],
44+
]);
4145
}
4246

4347
public function setUp(): void
@@ -103,8 +107,9 @@ public function testItCanCreateTheCollection()
103107

104108
self::assertSame(44, $collection->countDocuments());
105109

106-
$searchIndexes = $collection->listSearchIndexes(['name' => 'scout']);
110+
$searchIndexes = $collection->listSearchIndexes(['name' => 'scout', 'typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array']]);
107111
self::assertCount(1, $searchIndexes);
112+
self::assertSame(['mappings' => ['dynamic' => true, 'fields' => ['bool_field' => ['type' => 'boolean']]]], iterator_to_array($searchIndexes)[0]['latestDefinition']);
108113

109114
// Wait for all documents to be indexed asynchronously
110115
$i = 100;

0 commit comments

Comments
 (0)