Skip to content

Commit b10df66

Browse files
committed
Add Scout utility class for customizable job dispatch
Introduces Scout utility class with: - Static $makeSearchableJob and $removeFromSearchJob properties - makeSearchableUsing() and removeFromSearchUsing() setters - engine() convenience method for engine access - resetJobClasses() for test isolation Updates Searchable trait to dispatch jobs via Scout::$makeSearchableJob and Scout::$removeFromSearchJob, enabling users to customize job classes for logging, monitoring, retry behavior, etc.
1 parent 2f076e3 commit b10df66

File tree

4 files changed

+244
-4
lines changed

4 files changed

+244
-4
lines changed

src/scout/src/Scout.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Hypervel\Scout;
6+
7+
use Hypervel\Scout\Jobs\MakeSearchable;
8+
use Hypervel\Scout\Jobs\RemoveFromSearch;
9+
10+
/**
11+
* Scout utility class for job customization and engine access.
12+
*
13+
* Provides static configuration for customizing the job classes used
14+
* when indexing models via the queue. Set these in a service provider
15+
* boot method to use custom job implementations.
16+
*
17+
* Note: These static properties are set at boot time and read during
18+
* request handling. This is safe in Swoole/coroutine environments
19+
* because they store class names (strings), not stateful objects.
20+
*/
21+
class Scout
22+
{
23+
/**
24+
* The job class that makes models searchable.
25+
*
26+
* @var class-string<MakeSearchable>
27+
*/
28+
public static string $makeSearchableJob = MakeSearchable::class;
29+
30+
/**
31+
* The job class that removes models from the search index.
32+
*
33+
* @var class-string<RemoveFromSearch>
34+
*/
35+
public static string $removeFromSearchJob = RemoveFromSearch::class;
36+
37+
/**
38+
* Get a Scout engine instance by name.
39+
*/
40+
public static function engine(?string $name = null): Engine
41+
{
42+
return app(EngineManager::class)->engine($name);
43+
}
44+
45+
/**
46+
* Specify the job class that should make models searchable.
47+
*
48+
* @param class-string<MakeSearchable> $class
49+
*/
50+
public static function makeSearchableUsing(string $class): void
51+
{
52+
static::$makeSearchableJob = $class;
53+
}
54+
55+
/**
56+
* Specify the job class that should remove models from the search index.
57+
*
58+
* @param class-string<RemoveFromSearch> $class
59+
*/
60+
public static function removeFromSearchUsing(string $class): void
61+
{
62+
static::$removeFromSearchJob = $class;
63+
}
64+
65+
/**
66+
* Reset the job classes to their defaults.
67+
*
68+
* Useful for testing to ensure clean state between tests.
69+
*/
70+
public static function resetJobClasses(): void
71+
{
72+
static::$makeSearchableJob = MakeSearchable::class;
73+
static::$removeFromSearchJob = RemoveFromSearch::class;
74+
}
75+
}

src/scout/src/Searchable.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
use Hypervel\Database\Eloquent\Builder as EloquentBuilder;
1414
use Hypervel\Database\Eloquent\Collection;
1515
use Hypervel\Database\Eloquent\SoftDeletes;
16-
use Hypervel\Scout\Jobs\MakeSearchable;
17-
use Hypervel\Scout\Jobs\RemoveFromSearch;
1816
use Hypervel\Support\Collection as BaseCollection;
1917

2018
/**
@@ -151,7 +149,8 @@ public function queueMakeSearchable(Collection $models): void
151149
}
152150

153151
if (static::getScoutConfig('queue.enabled', false)) {
154-
$pendingDispatch = MakeSearchable::dispatch($models)
152+
$jobClass = Scout::$makeSearchableJob;
153+
$pendingDispatch = $jobClass::dispatch($models)
155154
->onConnection($models->first()->syncWithSearchUsing())
156155
->onQueue($models->first()->syncWithSearchUsingQueue());
157156

@@ -195,7 +194,8 @@ public function queueRemoveFromSearch(Collection $models): void
195194
}
196195

197196
if (static::getScoutConfig('queue.enabled', false)) {
198-
$pendingDispatch = RemoveFromSearch::dispatch($models)
197+
$jobClass = Scout::$removeFromSearchJob;
198+
$pendingDispatch = $jobClass::dispatch($models)
199199
->onConnection($models->first()->syncWithSearchUsing())
200200
->onQueue($models->first()->syncWithSearchUsingQueue());
201201

tests/Scout/Unit/QueueDispatchTest.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Hypervel\Database\Eloquent\Collection;
99
use Hypervel\Scout\Jobs\MakeSearchable;
1010
use Hypervel\Scout\Jobs\RemoveFromSearch;
11+
use Hypervel\Scout\Scout;
1112
use Hypervel\Support\Facades\Bus;
1213
use Hypervel\Tests\Scout\Models\SearchableModel;
1314
use Hypervel\Tests\Scout\ScoutTestCase;
@@ -20,6 +21,12 @@
2021
*/
2122
class QueueDispatchTest extends ScoutTestCase
2223
{
24+
protected function tearDown(): void
25+
{
26+
Scout::resetJobClasses();
27+
parent::tearDown();
28+
}
29+
2330
public function testQueueMakeSearchableDispatchesJobWhenQueueEnabled(): void
2431
{
2532
$this->app->get(ConfigInterface::class)->set('scout.queue.enabled', true);
@@ -144,4 +151,50 @@ public function testEmptyCollectionDoesNotDispatchRemoveFromSearchJob(): void
144151

145152
Bus::assertNotDispatched(RemoveFromSearch::class);
146153
}
154+
155+
public function testQueueMakeSearchableDispatchesCustomJobClass(): void
156+
{
157+
$this->app->get(ConfigInterface::class)->set('scout.queue.enabled', true);
158+
159+
Scout::makeSearchableUsing(TestCustomMakeSearchable::class);
160+
161+
Bus::fake([TestCustomMakeSearchable::class]);
162+
163+
$model = new SearchableModel(['title' => 'Test', 'body' => 'Content']);
164+
$model->id = 1;
165+
166+
$model->queueMakeSearchable(new Collection([$model]));
167+
168+
Bus::assertDispatched(TestCustomMakeSearchable::class);
169+
}
170+
171+
public function testQueueRemoveFromSearchDispatchesCustomJobClass(): void
172+
{
173+
$this->app->get(ConfigInterface::class)->set('scout.queue.enabled', true);
174+
175+
Scout::removeFromSearchUsing(TestCustomRemoveFromSearch::class);
176+
177+
Bus::fake([TestCustomRemoveFromSearch::class]);
178+
179+
$model = new SearchableModel(['title' => 'Test', 'body' => 'Content']);
180+
$model->id = 1;
181+
182+
$model->queueRemoveFromSearch(new Collection([$model]));
183+
184+
Bus::assertDispatched(TestCustomRemoveFromSearch::class);
185+
}
186+
}
187+
188+
/**
189+
* Custom job class for testing custom MakeSearchable dispatch.
190+
*/
191+
class TestCustomMakeSearchable extends MakeSearchable
192+
{
193+
}
194+
195+
/**
196+
* Custom job class for testing custom RemoveFromSearch dispatch.
197+
*/
198+
class TestCustomRemoveFromSearch extends RemoveFromSearch
199+
{
147200
}

tests/Scout/Unit/ScoutTest.php

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Hypervel\Tests\Scout\Unit;
6+
7+
use Hypervel\Scout\Engine;
8+
use Hypervel\Scout\EngineManager;
9+
use Hypervel\Scout\Jobs\MakeSearchable;
10+
use Hypervel\Scout\Jobs\RemoveFromSearch;
11+
use Hypervel\Scout\Scout;
12+
use Hypervel\Tests\Scout\ScoutTestCase;
13+
use Mockery as m;
14+
15+
/**
16+
* Tests for the Scout utility class.
17+
*
18+
* @internal
19+
* @coversNothing
20+
*/
21+
class ScoutTest extends ScoutTestCase
22+
{
23+
protected function tearDown(): void
24+
{
25+
Scout::resetJobClasses();
26+
m::close();
27+
parent::tearDown();
28+
}
29+
30+
public function testDefaultMakeSearchableJobClass(): void
31+
{
32+
$this->assertSame(MakeSearchable::class, Scout::$makeSearchableJob);
33+
}
34+
35+
public function testDefaultRemoveFromSearchJobClass(): void
36+
{
37+
$this->assertSame(RemoveFromSearch::class, Scout::$removeFromSearchJob);
38+
}
39+
40+
public function testMakeSearchableUsingChangesJobClass(): void
41+
{
42+
Scout::makeSearchableUsing(CustomMakeSearchable::class);
43+
44+
$this->assertSame(CustomMakeSearchable::class, Scout::$makeSearchableJob);
45+
}
46+
47+
public function testRemoveFromSearchUsingChangesJobClass(): void
48+
{
49+
Scout::removeFromSearchUsing(CustomRemoveFromSearch::class);
50+
51+
$this->assertSame(CustomRemoveFromSearch::class, Scout::$removeFromSearchJob);
52+
}
53+
54+
public function testResetJobClassesRestoresDefaults(): void
55+
{
56+
Scout::makeSearchableUsing(CustomMakeSearchable::class);
57+
Scout::removeFromSearchUsing(CustomRemoveFromSearch::class);
58+
59+
Scout::resetJobClasses();
60+
61+
$this->assertSame(MakeSearchable::class, Scout::$makeSearchableJob);
62+
$this->assertSame(RemoveFromSearch::class, Scout::$removeFromSearchJob);
63+
}
64+
65+
public function testEngineMethodReturnsEngineFromManager(): void
66+
{
67+
$engine = m::mock(Engine::class);
68+
69+
$manager = m::mock(EngineManager::class);
70+
$manager->shouldReceive('engine')
71+
->with('meilisearch')
72+
->once()
73+
->andReturn($engine);
74+
75+
$this->app->instance(EngineManager::class, $manager);
76+
77+
$result = Scout::engine('meilisearch');
78+
79+
$this->assertSame($engine, $result);
80+
}
81+
82+
public function testEngineMethodWithNullUsesDefaultEngine(): void
83+
{
84+
$engine = m::mock(Engine::class);
85+
86+
$manager = m::mock(EngineManager::class);
87+
$manager->shouldReceive('engine')
88+
->with(null)
89+
->once()
90+
->andReturn($engine);
91+
92+
$this->app->instance(EngineManager::class, $manager);
93+
94+
$result = Scout::engine();
95+
96+
$this->assertSame($engine, $result);
97+
}
98+
}
99+
100+
/**
101+
* Custom job class for testing makeSearchableUsing().
102+
*/
103+
class CustomMakeSearchable extends MakeSearchable
104+
{
105+
}
106+
107+
/**
108+
* Custom job class for testing removeFromSearchUsing().
109+
*/
110+
class CustomRemoveFromSearch extends RemoveFromSearch
111+
{
112+
}

0 commit comments

Comments
 (0)