Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,33 @@ class Role extends Model
}
```

### Refreshing Data on Each Request (Laravel Octane)

When running under [Laravel Octane](https://laravel.com/docs/octane), the application process is kept alive between requests. By default, Sushi configures its SQLite connection once per process and reuses it for subsequent requests — which is usually desirable.

If you need `getRows()` to be called again on every new request (for example, when your rows are sourced from a dynamic, per-request data source), you can override `shouldRefreshDataOnEachRequest()`:

```php
class LiveSettings extends Model
{
use \Sushi\Sushi;

public function getRows(): array
{
return cache()->get('live-settings', []);
}

protected static function shouldRefreshDataOnEachRequest(): bool
{
return true;
}
}
```

With this enabled, Sushi will detect when a new HTTP request starts (via the Laravel request object) and automatically re-run `getRows()`, rebuilding the in-memory SQLite table for that request.

> Note: This has no effect outside of Octane (i.e. traditional FPM deployments), since each request already starts a fresh process.

### Caching ->getRows()

If you choose to use your own ->getRows() method, the rows will NOT be cached between requests by default.
Expand Down
43 changes: 43 additions & 0 deletions src/Sushi.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
trait Sushi
{
protected static $sushiConnection;
protected static $sushiOctaneListenerRegistered = false;

public function getRows()
{
Expand All @@ -32,8 +33,17 @@ protected function sushiShouldCache()
return property_exists(static::class, 'rows');
}

protected static function shouldRefreshDataOnEachRequest(): bool
{
return false;
}

public static function resolveConnection($connection = null)
{
if (static::$sushiConnection === null && static::shouldRefreshDataOnEachRequest()) {
static::configureSushiConnection();
}

return static::$sushiConnection;
}

Expand Down Expand Up @@ -68,12 +78,45 @@ public static function bootSushi()
if (method_exists(static::class, 'whenBooted')) {
static::whenBooted(function () {
static::configureSushiConnection();
static::registerOctaneRefreshListener();
});
} else {
static::configureSushiConnection();
static::registerOctaneRefreshListener();
}
}

protected static function registerOctaneRefreshListener(): void
{
if (! static::shouldRefreshDataOnEachRequest()) {
return;
}

if (static::$sushiOctaneListenerRegistered) {
return;
}

if (! class_exists('Laravel\Octane\Events\RequestReceived')) {
return;
}

$class = static::class;

app('events')->listen(
\Laravel\Octane\Events\RequestReceived::class,
static function () use ($class) {
$class::clearSushiConnection();
}
);

static::$sushiOctaneListenerRegistered = true;
}

public static function clearSushiConnection(): void
{
static::$sushiConnection = null;
}

protected static function configureSushiConnection()
{
$instance = new static;
Expand Down
56 changes: 56 additions & 0 deletions tests/SushiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public function setUp(): void

Foo::resetStatics();
Bar::resetStatics();
Bar::$hasBeenAccessedBefore = false;
File::cleanDirectory($this->cachePath);
}

Expand Down Expand Up @@ -193,6 +194,34 @@ function test_sushi_models_can_relate_to_models_in_regular_sqlite_databases()
$this->assertCount(2, $californiaMaki->ingredients);
}

function test_model_with_refresh_reruns_get_rows_after_connection_is_cleared()
{
OctaneRefreshModel::resetStatics();

$firstCount = OctaneRefreshModel::count(); // getRows runs once → 1 row

// Simulate what Octane's RequestReceived event does
OctaneRefreshModel::clearSushiConnection();

$secondCount = OctaneRefreshModel::count(); // getRows runs again → 2 rows

$this->assertGreaterThan($firstCount, $secondCount);
$this->assertEquals(2, OctaneRefreshModel::$callCount);
}

function test_model_without_refresh_does_not_register_octane_listener()
{
// Bar has shouldRefreshDataOnEachRequest() = false (default)
Bar::$hasBeenAccessedBefore = false;
Bar::resetStatics();

Bar::count(); // triggers boot → registerOctaneRefreshListener() returns early

$prop = (new \ReflectionClass(Bar::class))->getProperty('sushiOctaneListenerRegistered');
$prop->setAccessible(true);
$this->assertFalse($prop->getValue());
}

protected function usesSqliteConnection($app)
{
file_put_contents(__DIR__ . '/database/database.sqlite', '');
Expand All @@ -219,6 +248,7 @@ class Foo extends Model
public static function resetStatics()
{
static::setSushiConnection(null);
static::$sushiOctaneListenerRegistered = false;
static::clearBootedModels();
}

Expand Down Expand Up @@ -310,6 +340,7 @@ public function getRows()
public static function resetStatics()
{
static::setSushiConnection(null);
static::$sushiOctaneListenerRegistered = false;
static::clearBootedModels();
}

Expand Down Expand Up @@ -361,3 +392,28 @@ public function maki()
return $this->belongsTo(Maki::class);
}
}

class OctaneRefreshModel extends Model
{
use \Sushi\Sushi;

public static $callCount = 0;

public function getRows(): array
{
return array_fill(0, ++static::$callCount, ['name' => 'row']);
}

protected static function shouldRefreshDataOnEachRequest(): bool
{
return true;
}

public static function resetStatics(): void
{
static::$sushiConnection = null;
static::$sushiOctaneListenerRegistered = false;
static::$callCount = 0;
static::clearBootedModels();
}
}
Loading