1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-03-23 13:44:03 +00:00
tenancy/src/Database/TenantDatabaseManagers/SQLiteDatabaseManager.php
Samuel Štancl aba7a50619
Minor fixes
The change in SQLiteDatabaseManager wasn't properly saving the
updated internal value.

The check in CacheTenancyBootstrapper wasn't handling that local tests
have a 'testing' environment, not local. However fixing only the
condition would've still added the store to $names which would throw
an exception down the line. We make sure to only throw the exception
in prod, but also make sure to only add the store to $names if it is
supported.
2025-10-22 12:58:45 +02:00

151 lines
5.4 KiB
PHP

<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Database\TenantDatabaseManagers;
use Closure;
use Illuminate\Database\Eloquent\Model;
use PDO;
use Stancl\Tenancy\Database\Contracts\TenantDatabaseManager;
use Stancl\Tenancy\Database\Contracts\TenantWithDatabase;
use Throwable;
class SQLiteDatabaseManager implements TenantDatabaseManager
{
/**
* SQLite database directory path.
*
* Defaults to database_path().
*/
public static string|null $path = null;
/*
* If this isn't null, a connection to the tenant DB will be created
* and passed to the provided closure, for the purpose of keeping the
* connection alive for the desired lifetime. This means it's the
* closure's job to store the connection in a place that lives as
* long as the connection should live.
*
* The closure is called in makeConnectionConfig() -- a method normally
* called shortly before a connection is established.
*
* NOTE: The closure is called EVERY time makeConnectionConfig()
* is called, therefore it's up to the closure to discard
* the connection if a connection to the same database is already persisted.
*
* The closure also receives the DSN used to create the PDO connection,
* since the PDO connection driver makes it a bit hard to recover DB names
* from PDO instances. That should make it easier to match these with
* tenant instances passed to $closeInMemoryConnectionUsing closures,
* if you're setting that property as well.
*
* @var Closure(PDO, string)|null
*/
public static Closure|null $persistInMemoryConnectionUsing = null;
/*
* The opposite of $persistInMemoryConnectionUsing. This closure
* is called when the tenant is deleted, to clear the database
* in case a tenant with the same ID should be created within
* the lifetime of the $persistInMemoryConnectionUsing logic.
*
* NOTE: The parameter provided to the closure is the Tenant
* instance, not a PDO connection.
*
* @var Closure(Tenant)|null
*/
public static Closure|null $closeInMemoryConnectionUsing = null;
public function createDatabase(TenantWithDatabase $tenant): bool
{
/** @var TenantWithDatabase&Model $tenant */
$name = $tenant->database()->getName();
if ($this->isInMemory($name)) {
// If :memory: is used, we update the tenant with a *named* in-memory SQLite connection.
//
// This makes use of the package feasible with in-memory SQLite. Pure :memory: isn't
// sufficient since the tenant creation process involves constant creation and destruction
// of the tenant connection, always clearing the memory (like migrations). Additionally,
// tenancy()->central() calls would close the database since at the moment we close the
// tenant connection (to prevent accidental references to it in the central context) when
// tenancy is ended.
//
// Note that named in-memory databases DO NOT have process lifetime. You need an open
// PDO connection to keep the memory from being cleaned up. It's up to the user how they
// handle this, common solutions may involve storing the connection in the service container
// or creating a closure holding a reference to it and passing that to register_shutdown_function().
$name = '_tenancy_inmemory_' . $tenant->getTenantKey();
$tenant->setInternal('db_name', "file:$name?mode=memory&cache=shared");
$tenant->save();
return true;
}
return file_put_contents($this->getPath($name), '') !== false;
}
public function deleteDatabase(TenantWithDatabase $tenant): bool
{
$name = $tenant->database()->getName();
if ($this->isInMemory($name)) {
if (static::$closeInMemoryConnectionUsing) {
(static::$closeInMemoryConnectionUsing)($tenant);
}
return true;
}
$path = $this->getPath($name);
try {
unlink($path . '-journal');
unlink($path . '-wal');
unlink($path . '-shm');
} catch (Throwable) {}
try {
return unlink($path);
} catch (Throwable) {
return false;
}
}
public function databaseExists(string $name): bool
{
return $this->isInMemory($name) || file_exists($this->getPath($name));
}
public function makeConnectionConfig(array $baseConfig, string $databaseName): array
{
if ($this->isInMemory($databaseName)) {
$baseConfig['database'] = $databaseName;
if (static::$persistInMemoryConnectionUsing !== null) {
$dsn = "sqlite:$databaseName";
(static::$persistInMemoryConnectionUsing)(new PDO($dsn), $dsn);
}
} else {
$baseConfig['database'] = database_path($databaseName);
}
return $baseConfig;
}
public function getPath(string $name): string
{
if (static::$path) {
return rtrim(static::$path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $name;
}
return database_path($name);
}
public static function isInMemory(string $name): bool
{
return $name === ':memory:' || str_contains($name, '_tenancy_inmemory_');
}
}