From 69db512d30e9d7f9596b98b886a07885ddeca565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Wed, 11 Sep 2019 14:32:13 +0200 Subject: [PATCH] Storage drivers WIP --- src/Contracts/FeatureProvider.php | 1 + src/Contracts/StorageDriver.php | 42 +++++++++++- src/StorageDrivers/RedisStorageDriver.php | 83 +++++++++++++---------- src/Tenant.php | 21 ++++-- src/TenantManagerv2.php | 23 ++++++- src/helpers.php | 7 +- 6 files changed, 128 insertions(+), 49 deletions(-) diff --git a/src/Contracts/FeatureProvider.php b/src/Contracts/FeatureProvider.php index bd3c59e0..05299f81 100644 --- a/src/Contracts/FeatureProvider.php +++ b/src/Contracts/FeatureProvider.php @@ -7,6 +7,7 @@ namespace Stancl\Tenancy\Contracts; use Stancl\Tenancy\TenantManager; /** Additional features, like Telescope tags and tenant redirects. */ +// todo interface name. Feature, FeatureProvider, ProvidesFeature(s) interface FeatureProvider { public function bootstrap(TenantManager $tenantManager): void; diff --git a/src/Contracts/StorageDriver.php b/src/Contracts/StorageDriver.php index 027ff8d9..a38f123a 100644 --- a/src/Contracts/StorageDriver.php +++ b/src/Contracts/StorageDriver.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Stancl\Tenancy\Contracts; use Stancl\Tenancy\Tenant; -use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException; // todo this class now manages types (json encoding) // make sure ids are not json encoded @@ -23,7 +22,7 @@ interface StorageDriver * * @param string $id * @return Tenant - * @throws TenantCouldNotBeIdentifiedException + * @throws \Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException */ public function findById(string $id): Tenant; @@ -41,5 +40,42 @@ interface StorageDriver * @param Tenant $tenant * @return true|TenantCannotBeCreatedException */ - public function canCreate(Tenant $tenant); + public function canCreateTenant(Tenant $tenant); + + /** + * Get a value from storage. + * + * @param string $key + * @param ?Tenant $tenant + * @return mixed + */ + public function get(string $key, Tenant $tenant = null); + + /** + * Get multiple values from storage. + * + * @param array $keys + * @param ?Tenant $tenant + * @return void + */ + public function getMany(array $keys, Tenant $tenant = null); + + /** + * Put a value into storage. + * + * @param string $key + * @param mixed $value + * @param ?Tenant $tenant + * @return void + */ + public function put(string $key, $value, Tenant $tenant = null): void; + + /** + * Put multiple values into storage. + * + * @param mixed[string] $kvPairs + * @param ?Tenant $tenant + * @return void + */ + public function putMany(array $kvPairs, Tenant $tenant = null): void; } diff --git a/src/StorageDrivers/RedisStorageDriver.php b/src/StorageDrivers/RedisStorageDriver.php index 29e9e06d..3f6baba8 100644 --- a/src/StorageDrivers/RedisStorageDriver.php +++ b/src/StorageDrivers/RedisStorageDriver.php @@ -4,43 +4,60 @@ declare(strict_types=1); namespace Stancl\Tenancy\StorageDrivers; -use Illuminate\Support\Facades\Redis; +use Illuminate\Foundation\Application; use Stancl\Tenancy\Interfaces\StorageDriver; +use Illuminate\Contracts\Redis\Factory as Redis; use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException; +use Stancl\Tenancy\Tenant; class RedisStorageDriver implements StorageDriver { - private $redis; + /** @var Application */ + protected $app; - public function __construct() + /** @var Redis */ + protected $redis; + + public function __construct(Application $app, Redis $redis) { - $this->redis = Redis::connection(config('tenancy.redis.connection', 'tenancy')); + $this->app = $app; + $this->redis = $redis->connection($app['config']['tenancy.redis.connection'] ?? 'tenancy'); } - public function identifyTenant(string $domain): array + /** + * Get the current tenant. + * + * @return Tenant + */ + protected function tenant() + { + return $this->app[Tenant::class]; + } + + public function findByDomain(string $domain): Tenant { $id = $this->getTenantIdByDomain($domain); if (! $id) { throw new TenantCouldNotBeIdentifiedException($domain); } - return $this->getTenantById($id); + return $this->find($id); } /** - * Get information about the tenant based on his uuid. + * Get information about the tenant based on his id. * - * @param string $uuid - * @param array $fields + * @param string $id + * @param string[] $fields * @return array */ - public function getTenantById(string $uuid, array $fields = []): array + public function find(string $id, array $fields = []): array { if (! $fields) { - return $this->redis->hgetall("tenants:$uuid"); + return $this->redis->hgetall("tenants:$id"); } - return \array_combine($fields, $this->redis->hmget("tenants:$uuid", $fields)); + return array_combine($fields, $this->redis->hmget("tenants:$id", $fields)); } public function getTenantIdByDomain(string $domain): ?string @@ -48,37 +65,31 @@ class RedisStorageDriver implements StorageDriver return $this->redis->hget("domains:$domain", 'tenant_id') ?: null; } - public function createTenant(string $domain, string $uuid): array + public function createTenant(Tenant $tenant): void { - $this->redis->hmset("domains:$domain", 'tenant_id', $uuid); - $this->redis->hmset("tenants:$uuid", 'uuid', \json_encode($uuid), 'domain', \json_encode($domain)); + $id = $tenant->id; - return $this->redis->hgetall("tenants:$uuid"); + foreach ($tenant->domains as $domain) { + $this->redis->hmset("domains:$domain", 'tenant_id', $id); + } + $this->redis->hmset("tenants:$id", 'id', json_encode($id), 'domain', json_encode($domain)); + + return $this->redis->hgetall("tenants:$id"); // todo } - /** - * {@inheritdoc} - * - * @param string $id - * @return bool - * @todo Make tenant & domain deletion atomic. - */ - public function deleteTenant(string $id): bool + /** @todo Make tenant & domain deletion atomic. */ + public function deleteTenant(Tenant $tenant): void { - try { - $domain = \json_decode($this->getTenantById($id)['domain']); - } catch (\Throwable $th) { - throw new \Exception("No tenant with UUID $id exists."); + foreach ($tenant->domains as $domain) { + $this->redis->del("domains:$domain"); } - $this->redis->del("domains:$domain"); - - return (bool) $this->redis->del("tenants:$id"); + $this->redis->del("tenants:{$tenant->id}"); } public function getAllTenants(array $uuids = []): array { - $hashes = \array_map(function ($hash) { + $hashes = array_map(function ($hash) { return "tenants:{$hash}"; }, $uuids); @@ -93,20 +104,20 @@ class RedisStorageDriver implements StorageDriver $all_keys = $this->redis->keys('tenants:*'); - $hashes = \array_map(function ($key) use ($redis_prefix) { + $hashes = array_map(function ($key) use ($redis_prefix) { // Left strip $redis_prefix from $key - return \substr($key, \strlen($redis_prefix)); + return substr($key, strlen($redis_prefix)); }, $all_keys); } - return \array_map(function ($tenant) { + return array_map(function ($tenant) { return $this->redis->hgetall($tenant); }, $hashes); } public function get(string $uuid, string $key) { - return $this->redis->hget("tenants:$uuid", $key); + return json_decode($this->redis->hget("tenants:$uuid", $key), true); } public function getMany(string $uuid, array $keys): array diff --git a/src/Tenant.php b/src/Tenant.php index 6f21583a..f32539e2 100644 --- a/src/Tenant.php +++ b/src/Tenant.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Stancl\Tenancy; use ArrayAccess; +use Stancl\Tenancy\Contracts\StorageDriver; // todo tenant storage @@ -27,23 +28,25 @@ class Tenant implements ArrayAccess * * @var string[] */ - public $domains; + public $domains = []; - /** - * @var TenantManager - */ - private $manager; + /** @var TenantManager */ + protected $manager; + + /** @var StorageDriver */ + protected $storage; /** * Does this tenant exist in the storage. * * @var bool */ - private $persisted = false; + protected $persisted = false; - public function __construct(TenantManager $tenantManager) + public function __construct(TenantManager $tenantManager, StorageDriver $storage) { $this->manager = $tenantManager; + $this->storage = $storage; } public static function new(): self @@ -65,6 +68,7 @@ class Tenant implements ArrayAccess return $this; } + // todo move this to TenantFactory? public function withDomains($domains): self { $domains = (array) $domains; @@ -128,6 +132,9 @@ class Tenant implements ArrayAccess public function put($key, $value = null): self { + // todo something like if ($this->storage->getIdKey() === $key) throw new Exception("Can't override ID")? or should it be overridable? + // and the responsibility of not overriding domains is up to the storage driver + if (is_array($key)) { $this->storage->putMany($key); foreach ($key as $k => $v) { // Add to cache diff --git a/src/TenantManagerv2.php b/src/TenantManagerv2.php index 1f286f52..bde3a00d 100644 --- a/src/TenantManagerv2.php +++ b/src/TenantManagerv2.php @@ -84,6 +84,7 @@ class TenantManagerv2 */ public function ensureTenantCanBeCreated(Tenant $tenant): void { + // todo move the "throw" responsibility to the canCreateTenant methods? if (($e = $this->storage->canCreateTenant($tenant)) instanceof TenantCannotBeCreatedException) { throw new $e; } @@ -138,6 +139,21 @@ class TenantManagerv2 return $this->storage->findByDomain($domain); } + /** + * Get all tenants. + * + * @param Tenant[]|string[] $only + * @return Tenant[] + */ + public function all($only = []): array + { + $only = array_map(function ($item) { + return $item instanceof Tenant ? $item->id : $item; + }, $only); + + return $this->storage->all($only); + } + public function initializeTenancy(Tenant $tenant): self { $this->bootstrapTenancy($tenant); @@ -175,15 +191,20 @@ class TenantManagerv2 /** * Get the current tenant. * + * @param string $key * @return Tenant * @throws NoTenantIdentifiedException */ - public function getTenant(): Tenant + public function getTenant(string $key = null): Tenant { if (! $this->tenant) { throw new NoTenantIdentifiedException; } + if (! is_null($key)) { + return $this->tenant[$key]; + } + return $this->tenant; } diff --git a/src/helpers.php b/src/helpers.php index 0e6b8ad7..b763c7de 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -2,13 +2,14 @@ declare(strict_types=1); +use Stancl\Tenancy\Tenant; use Stancl\Tenancy\TenantManager; if (! \function_exists('tenancy')) { function tenancy($key = null) { if ($key) { - return app(TenantManager::class)->tenant[$key] ?? null; + return app(TenantManager::class)->getTenant($key) ?? null; } return app(TenantManager::class); @@ -18,7 +19,9 @@ if (! \function_exists('tenancy')) { if (! \function_exists('tenant')) { function tenant($key = null) { - return tenancy($key); + if (! is_null($key)) { + return app(Tenant::class)->get($key); + } } }