Skip to content

Commit 59cdf0e

Browse files
authored
Feat: Add Stickiness Resolver Middleware (#7)
* Feat: Add Stickiness Resolver Middleware * Doc: Update README * Test: Add feature tests
1 parent 26e241d commit 59cdf0e

File tree

6 files changed

+323
-4
lines changed

6 files changed

+323
-4
lines changed

README.md

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,82 @@ class DatabaseServiceProvider extends ServiceProvider
180180
}
181181
```
182182

183-
| | Source |
184-
|:---:|:---:|
185-
| `IpBasedResolver`<br>**(Default)**| Remote IP address |
186-
| `AuthBasedResolver` | Authenticated User ID |
183+
| | Source | Middleware |
184+
|:---:|:---:|:---:|
185+
| `IpBasedResolver`<br>**(Default)**| Remote IP address | |
186+
| `AuthBasedResolver` | Authenticated User ID | Required |
187+
188+
You must add **`ResolveStickinessOnResolvedConnections`** middleware before `Authenticate`
189+
when you use `AuthBasedResolver`.
190+
191+
```diff
192+
--- a/app/Http/Kernel.php
193+
+++ b/app/Http/Kernel.php
194+
<?php
195+
196+
namespace App\Http;
197+
198+
use Illuminate\Foundation\Http\Kernel as HttpKernel;
199+
200+
class Kernel extends HttpKernel
201+
{
202+
/* ... */
203+
204+
/**
205+
* The application's route middleware groups.
206+
*
207+
* @var array
208+
*/
209+
protected $middlewareGroups = [
210+
'web' => [
211+
\App\Http\Middleware\EncryptCookies::class,
212+
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
213+
\Illuminate\Session\Middleware\StartSession::class,
214+
// \Illuminate\Session\Middleware\AuthenticateSession::class,
215+
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
216+
\App\Http\Middleware\VerifyCsrfToken::class,
217+
\Illuminate\Routing\Middleware\SubstituteBindings::class,
218+
],
219+
220+
'api' => [
221+
'throttle:60,1',
222+
\Illuminate\Routing\Middleware\SubstituteBindings::class,
223+
],
224+
+
225+
+ 'auth' => [
226+
+ \App\Http\Middleware\Authenticate::class,
227+
+ \Mpyw\LaravelCachedDatabaseStickiness\Http\Middleware\ResolveStickinessOnResolvedConnections::class,
228+
+ ],
229+
+
230+
+ 'auth.basic' => [
231+
+ \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
232+
+ \Mpyw\LaravelCachedDatabaseStickiness\Http\Middleware\ResolveStickinessOnResolvedConnections::class,
233+
+ ],
234+
];
235+
236+
/**
237+
* The application's route middleware.
238+
*
239+
* These middleware may be assigned to groups or used individually.
240+
*
241+
* @var array
242+
*/
243+
protected $routeMiddleware = [
244+
- 'auth' => \App\Http\Middleware\Authenticate::class,
245+
- 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
246+
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
247+
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
248+
'can' => \Illuminate\Auth\Middleware\Authorize::class,
249+
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
250+
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
251+
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
252+
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
253+
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
254+
];
255+
256+
/* ... */
257+
}
258+
```
187259

188260
### Customize Worker Behavior
189261

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Mpyw\LaravelCachedDatabaseStickiness\Http\Middleware;
4+
5+
use Closure;
6+
use Illuminate\Database\DatabaseManager;
7+
use Illuminate\Http\Request;
8+
use Mpyw\LaravelCachedDatabaseStickiness\StickinessManager;
9+
10+
class ResolveStickinessOnResolvedConnections
11+
{
12+
/**
13+
* @var \Mpyw\LaravelCachedDatabaseStickiness\StickinessManager
14+
*/
15+
protected $stickiness;
16+
17+
/**
18+
* @var \Illuminate\Database\DatabaseManager
19+
*/
20+
protected $db;
21+
22+
/**
23+
* ResolveStickinessOnResolvedConnections constructor.
24+
*
25+
* @param \Mpyw\LaravelCachedDatabaseStickiness\StickinessManager $stickiness
26+
* @param \Illuminate\Database\DatabaseManager $db
27+
*/
28+
public function __construct(StickinessManager $stickiness, DatabaseManager $db)
29+
{
30+
$this->stickiness = $stickiness;
31+
$this->db = $db;
32+
}
33+
34+
/**
35+
* @param \Illuminate\Http\Request $request
36+
* @param \Closure $next
37+
* @return mixed
38+
*/
39+
public function handle(Request $request, Closure $next)
40+
{
41+
foreach ($this->db->getConnections() as $connection) {
42+
$this->stickiness->resolveRecordsModified($connection);
43+
}
44+
45+
return $next($request);
46+
}
47+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
3+
namespace Mpyw\LaravelCachedDatabaseStickiness\Tests\Feature\Http\Middleware;
4+
5+
use Illuminate\Auth\Middleware\Authenticate;
6+
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
7+
use Illuminate\Contracts\Foundation\Application;
8+
use Illuminate\Contracts\Http\Kernel as KernelContract;
9+
use Illuminate\Routing\Router;
10+
use Illuminate\Support\Facades\DB;
11+
use Illuminate\Support\Facades\Hash;
12+
use Illuminate\Support\Facades\Route;
13+
use Mpyw\LaravelCachedDatabaseStickiness\ConnectionServiceProvider;
14+
use Mpyw\LaravelCachedDatabaseStickiness\Http\Middleware\ResolveStickinessOnResolvedConnections;
15+
use Mpyw\LaravelCachedDatabaseStickiness\StickinessResolvers\AuthBasedResolver;
16+
use Mpyw\LaravelCachedDatabaseStickiness\StickinessResolvers\StickinessResolverInterface;
17+
use Mpyw\LaravelCachedDatabaseStickiness\StickinessServiceProvider;
18+
use Mpyw\LaravelCachedDatabaseStickiness\Tests\Stubs\Models\User;
19+
use Orchestra\Testbench\Http\Kernel;
20+
use Orchestra\Testbench\TestCase;
21+
use PDO;
22+
use ReflectionProperty;
23+
24+
abstract class MiddlewareTest extends TestCase
25+
{
26+
/**
27+
* @var null|bool
28+
*/
29+
protected $withMiddleware;
30+
31+
/**
32+
* @var string
33+
*/
34+
protected $database;
35+
36+
/**
37+
* @param \Illuminate\Foundation\Application $app
38+
* @return array
39+
*/
40+
protected function getPackageProviders($app): array
41+
{
42+
return [
43+
StickinessServiceProvider::class,
44+
ConnectionServiceProvider::class,
45+
];
46+
}
47+
48+
/**
49+
* @param \Illuminate\Foundation\Application $app
50+
*/
51+
protected function getEnvironmentSetUp($app): void
52+
{
53+
$app['config']->set('database.default', 'test');
54+
$app['config']->set('database.connections.test', [
55+
'driver' => 'sqlite',
56+
'database' => $this->database = tempnam(storage_path(''), 'sqlite_'),
57+
'sticky' => true,
58+
]);
59+
$app['config']->set('auth.providers.users.model', User::class);
60+
$app->bind(StickinessResolverInterface::class, AuthBasedResolver::class);
61+
}
62+
63+
protected function setUp(): void
64+
{
65+
parent::setUp();
66+
67+
$pdo = new PDO("sqlite:$this->database", null, null, [
68+
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
69+
]);
70+
$pdo->exec('drop table if exists users');
71+
$pdo->exec('create table users(
72+
id integer primary key autoincrement,
73+
email text not null,
74+
password text not null,
75+
created_at datetime,
76+
updated_at datetime
77+
)');
78+
$stmt = $pdo->prepare('insert into users(email, password) values (?, ?)');
79+
$stmt->execute(['[email protected]', Hash::make('password')]);
80+
81+
Route::middleware('auth.basic')->get('/', function () {
82+
return ['message' => 'ok'];
83+
});
84+
}
85+
86+
protected function tearDown(): void
87+
{
88+
parent::tearDown();
89+
90+
@unlink($this->database);
91+
}
92+
93+
protected function resolveApplicationHttpKernel($app)
94+
{
95+
$app->singleton(KernelContract::class, function ($app) {
96+
return new class($app, $app['router'], $this->withMiddleware) extends Kernel {
97+
public function __construct(Application $app, Router $router, bool $withMiddleware)
98+
{
99+
$this->middlewareGroups['auth'] = array_filter([
100+
Authenticate::class,
101+
$withMiddleware ? ResolveStickinessOnResolvedConnections::class : null,
102+
]);
103+
$this->middlewareGroups['auth.basic'] = array_filter([
104+
AuthenticateWithBasicAuth::class,
105+
$withMiddleware ? ResolveStickinessOnResolvedConnections::class : null,
106+
]);
107+
108+
unset($this->routeMiddleware['auth'], $this->routeMiddleware['auth.basic']);
109+
110+
parent::__construct($app, $router);
111+
}
112+
};
113+
});
114+
}
115+
116+
protected function getRecordsModifiedViaReflection()
117+
{
118+
/* @var \Illuminate\Database\Connection $connection */
119+
$connection = DB::connection();
120+
121+
/* @noinspection PhpUnhandledExceptionInspection */
122+
$property = new ReflectionProperty($connection, 'recordsModified');
123+
$property->setAccessible(true);
124+
return $property->getValue($connection);
125+
}
126+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace Mpyw\LaravelCachedDatabaseStickiness\Tests\Feature\Http\Middleware;
4+
5+
use Illuminate\Contracts\Cache\Repository as CacheRepository;
6+
7+
class WithMiddlewareTest extends MiddlewareTest
8+
{
9+
protected $withMiddleware = true;
10+
11+
public function testWithMiddlewareWhenCacheExists(): void
12+
{
13+
$this->mock(CacheRepository::class)
14+
->shouldReceive('has')
15+
->with('database-stickiness:connection=test,resolver=auth,id=1')
16+
->once()
17+
->andReturnTrue();
18+
19+
$this->get('/', [
20+
'Authorization' => 'Basic ' . base64_encode('[email protected]:password'),
21+
])->assertSuccessful();
22+
23+
$this->assertTrue($this->getRecordsModifiedViaReflection());
24+
}
25+
26+
public function testWithMiddlewareWhenCacheDoesNotExist(): void
27+
{
28+
$this->mock(CacheRepository::class)
29+
->shouldReceive('has')
30+
->with('database-stickiness:connection=test,resolver=auth,id=1')
31+
->once()
32+
->andReturnFalse();
33+
34+
$this->get('/', [
35+
'Authorization' => 'Basic ' . base64_encode('[email protected]:password'),
36+
])->assertSuccessful();
37+
38+
$this->assertFalse($this->getRecordsModifiedViaReflection());
39+
}
40+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Mpyw\LaravelCachedDatabaseStickiness\Tests\Feature\Http\Middleware;
4+
5+
use Illuminate\Contracts\Cache\Repository as CacheRepository;
6+
7+
class WithoutMiddlewareTest extends MiddlewareTest
8+
{
9+
protected $withMiddleware = false;
10+
11+
public function testWithoutMiddleware(): void
12+
{
13+
$this->mock(CacheRepository::class)
14+
->shouldNotReceive('has');
15+
16+
$this->get('/', [
17+
'Authorization' => 'Basic ' . base64_encode('[email protected]:password'),
18+
])->assertSuccessful();
19+
20+
$this->assertFalse($this->getRecordsModifiedViaReflection());
21+
}
22+
}

tests/Stubs/Models/User.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Mpyw\LaravelCachedDatabaseStickiness\Tests\Stubs\Models;
4+
5+
use Illuminate\Auth\Authenticatable;
6+
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
7+
use Illuminate\Database\Eloquent\Model;
8+
9+
class User extends Model implements UserContract
10+
{
11+
use Authenticatable;
12+
}

0 commit comments

Comments
 (0)