From 032069af6d69b386f37acf5061354c3dd7bde833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Thu, 19 Sep 2019 19:54:06 +0200 Subject: [PATCH] Redis transactions --- CHANGELOG-1.x.md | 119 ---------------------- CHANGELOG-2.x.md | 2 + README.md | 6 +- src/StorageDrivers/RedisStorageDriver.php | 48 ++++++--- 4 files changed, 40 insertions(+), 135 deletions(-) delete mode 100644 CHANGELOG-1.x.md create mode 100644 CHANGELOG-2.x.md diff --git a/CHANGELOG-1.x.md b/CHANGELOG-1.x.md deleted file mode 100644 index 10a04384..00000000 --- a/CHANGELOG-1.x.md +++ /dev/null @@ -1,119 +0,0 @@ -# Release Notes for 1.x - -## [v1.8.0 (2019-08-17)](https://github.com/stancl/tenancy/compare/v1.7.0...v1.8.0) - -### Added - -- **Multi-tenant Jobs:** Jobs are now automatically multi-tenant. The [documentation page](https://stancl-tenancy.netlify.com/docs/jobs-queues/) covers the small tweaks you will have to make to your config to get multi-tenant jobs to work. -- **Telescope Integration**: You can read more about this on the [documentation page](https://stancl-tenancy.netlify.com/docs/telescope/). -- **Horizon Integration**: You can read more about this on the [documentation page](https://stancl-tenancy.netlify.com/docs/horizon/). -- **Tenant Redirect** and **Custom ID schemes**: You can now easily redirect to tenant domains. You can also use a custom tenant ID scheme if you don't like UUIDs. You can read about these features [here](https://stancl-tenancy.netlify.com/docs/misc-tips/). - -### Fixed - -- #112 *PostgreSQL Database creation error.* - -### Code - -- Strict types declaration is now used in every file. - -## [v1.7.0 (2019-08-17)](https://github.com/stancl/tenancy/compare/v1.6.1...v1.7.0) - -### Added: - -- DB storage driver - you don't have to use Redis to store tenants anymore. Relational databases are now supported as well. [more info](https://stancl-tenancy.netlify.com/docs/storage-drivers/#database) -- `tenancy:install` will do everything except DB/Redis connection creation for you. It will make changes to Http/Kernel.php, create `routes/tenant.php`, publish config, and (optionally) publish the migration. [more info](https://stancl-tenancy.netlify.com/docs/installation/) -- `tenants:run` [more info](https://stancl-tenancy.netlify.com/docs/console-commands/#run) -- New documentation: https://stancl-tenancy.netlify.com -- Custom tenant DB names [more info](https://stancl-tenancy.netlify.com/docs/custom-database-names/) -- stancl/tenancy events [more info](https://stancl-tenancy.netlify.com/docs/event-system/) - -### Fixed: - -- #89 *Command "tenants:migrate" cannot be found when used in app code* -- #87 *Unable to migrate multiple tenants at once when using MySQL* -- #96 *Issue w/ redis->scan() in getAllTenants logic.* - -## [v1.6.1 (2019-08-04)](https://github.com/stancl/tenancy/compare/v1.6.0...v1.6.1) - -Multiple phpunit.xml configs are now generated to run the tests with different configurations, such as different Redis drivers. - -### Fixed - -- `tenancy()->all()` with predis [`0dc8c80`](https://github.com/stancl/tenancy/commit/0dc8c80a02efbee5676cc72e648e108037ca5268) - -### Dropped - -- Laravel 5.7 support [`65b3882`](https://github.com/stancl/tenancy/commit/65b38827d5a2fa183838a9dce9fb6a157fd7e859) - -## [v1.6.0 (2019-07-30)](https://github.com/stancl/tenancy/compare/v1.5.1...v1.6.0) - -### Added - -- `GlobalCache` facade [#78](https://github.com/stancl/tenancy/pull/78) - -## [v1.5.1 (2019-07-25)](https://github.com/stancl/tenancy/compare/v1.5.0...v1.5.1) - -### Fixed - -- Database is reconnected after migrating/rolling back/seeding is done [#71](https://github.com/stancl/tenancy/pull/71) -- Fixed tenant()->delete() (it used to delete the record from the `tenants` namespace but not the `domains` namespace) [#73](https://github.com/stancl/tenancy/pull/73) - -## [v1.5.0 (2019-07-13)](https://github.com/stancl/tenancy/compare/v1.4.0...v1.5.0) - -### Added - -- PostgreSQL DB manager [#52](https://github.com/stancl/tenancy/pull/52) -- `tenancy()->end()` [#68](https://github.com/stancl/tenancy/pull/68) - -### Fixed - -- Return type docblock for `TenantManager::all()` [#63](https://github.com/stancl/tenancy/issue/63) - -## [v1.4.0 (2019-07-03)](https://github.com/stancl/tenancy/compare/v1.3.1...v1.4.0) - -### Added - -- Predis support [#59](https://github.com/stancl/tenancy/pull/59) - -## [v1.3.1 (2019-05-06)](https://github.com/stancl/tenancy/compare/v1.3.0...v1.3.1) - -### Fixed -- Fix jobs [#38](https://github.com/stancl/tenancy/pull/38) -- Fix tests for 5.8 [#41](https://github.com/stancl/tenancy/issues/41) - - -## [v1.3.0 (2019-02-27)](https://github.com/stancl/tenancy/compare/v1.2.0...v1.3.0) - -### Added -- Add 5.8 support [#33](https://github.com/stancl/tenancy/pull/33) - - -## [v1.2.0 (2019-02-15)](https://github.com/stancl/tenancy/compare/v1.1.3...v1.2.0) - -### Added -- Add `Tenancy` facade [#29](https://github.com/stancl/tenancy/issues/29) [`987c54f`](https://github.com/stancl/tenancy/commit/987c54f04e6ff3bdef068d92da6a9ace847f6c37) - - -## [v1.1.3 (2019-02-13)](https://github.com/stancl/tenancy/compare/v1.1.2...v1.1.3) - -### Fixed -- Fix CacheManager (it merged tags incorrectly), write tests for CacheManager [#31](https://github.com/stancl/tenancy/issues/31) [`a2d68b1`](https://github.com/stancl/tenancy/commit/a2d68b12611350f70befa3eb97fb56c99d006b54) - - -## [v1.1.2 (2019-02-13)](https://github.com/stancl/tenancy/compare/v1.1.1...v1.1.2) - -### Fixed -- Fix small bug in CacheManager [`d4d4119`](https://github.com/stancl/tenancy/commit/d4d411975496272158d7823597427fad8966fff8) - - -## [v1.1.1 (2019-02-11)](https://github.com/stancl/tenancy/compare/v1.1.0...v1.1.1) - -### Fixed -- Fix "Associative arrays are stored as objects" [#28](https://github.com/stancl/tenancy/issues/28) - - -## [v1.1.0 (2019-02-10)](https://github.com/stancl/tenancy/compare/v1.0.0...v1.1.0) - -### Added -- Add array support to the storage [#27](https://github.com/stancl/tenancy/pull/27) diff --git a/CHANGELOG-2.x.md b/CHANGELOG-2.x.md new file mode 100644 index 00000000..24e990d2 --- /dev/null +++ b/CHANGELOG-2.x.md @@ -0,0 +1,2 @@ +# Release Notes for 2.x + diff --git a/README.md b/README.md index 94f158cc..0c6459d0 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # [stancl/tenancy](https://tenancy.samuelstancl.me) -[![Laravel 5.8](https://img.shields.io/badge/laravel-5.8-red.svg)](https://laravel.com) +[![Laravel 6.x](https://img.shields.io/badge/laravel-6.x-red.svg)](https://laravel.com) [![Latest Stable Version](https://poser.pugx.org/stancl/tenancy/version)](https://packagist.org/packages/stancl/tenancy) -[![Travis CI build](https://travis-ci.com/stancl/tenancy.svg?branch=1.x)](https://travis-ci.com/stancl/tenancy) -[![codecov](https://codecov.io/gh/stancl/tenancy/branch/1.x/graph/badge.svg)](https://codecov.io/gh/stancl/tenancy) +[![Travis CI build](https://travis-ci.com/stancl/tenancy.svg?branch=2.x)](https://travis-ci.com/stancl/tenancy) +[![codecov](https://codecov.io/gh/stancl/tenancy/branch/2.x/graph/badge.svg)](https://codecov.io/gh/stancl/tenancy) ### *A Laravel multi-database tenancy package that respects your code.* diff --git a/src/StorageDrivers/RedisStorageDriver.php b/src/StorageDrivers/RedisStorageDriver.php index 0248f21b..f4c30623 100644 --- a/src/StorageDrivers/RedisStorageDriver.php +++ b/src/StorageDrivers/RedisStorageDriver.php @@ -7,14 +7,13 @@ namespace Stancl\Tenancy\StorageDrivers; use Illuminate\Contracts\Redis\Factory as Redis; use Illuminate\Foundation\Application; use Stancl\Tenancy\Contracts\StorageDriver; +use Stancl\Tenancy\Exceptions\DomainsOccupiedByOtherTenantException; use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException; +use Stancl\Tenancy\Exceptions\TenantWithThisIdAlreadyExistsException; use Stancl\Tenancy\Tenant; -// todo2 transactions instead of pipelines? class RedisStorageDriver implements StorageDriver { - // todo2 json encoding? - /** @var Application */ protected $app; @@ -49,7 +48,17 @@ class RedisStorageDriver implements StorageDriver public function ensureTenantCanBeCreated(Tenant $tenant): void { - // todo2 + // Tenant ID + if ($this->redis->exists("tenants:{$tenant->id}")) { + throw new TenantWithThisIdAlreadyExistsException($tenant->id); + } + + // Domains + if ($this->redis->exists(...array_map(function ($domain) { + return "domains:$domain"; + }, $tenant->domains))) { + throw new DomainsOccupiedByOtherTenantException; + } } public function findByDomain(string $domain): Tenant @@ -74,7 +83,7 @@ class RedisStorageDriver implements StorageDriver public function createTenant(Tenant $tenant): void { - $this->redis->pipeline(function ($pipe) use ($tenant) { + $this->redis->transaction(function ($pipe) use ($tenant) { foreach ($tenant->domains as $domain) { $pipe->hmset("domains:$domain", ['tenant_id' => $tenant->id]); } @@ -90,20 +99,34 @@ class RedisStorageDriver implements StorageDriver public function updateTenant(Tenant $tenant): void { - $this->redis->pipeline(function ($pipe) use ($tenant) { - $pipe->hmset("tenants:{$tenant->id}", $tenant->data); // todo domains + $data = []; + foreach ($tenant->data as $key => $value) { + $data[$key] = json_decode($value, true); + } - foreach ($tenant->domains as $domain) { - $pipe->hmset("domains:$domain", 'tenant_id', $tenant->id); + $domains = $data['_tenancy_domains']; + unset($data['_tenancy_domains']); + + $this->redis->transaction(function ($pipe) use ($data, $domains) { + $id = $data['id']; + + $old_domains = json_decode($pipe->hget("tenants:$id", 'domains'), true); + $deleted_domains = array_diff($old_domains, $domains); + + foreach ($deleted_domains as $deleted_domain) { + $pipe->del("domains:$deleted_domain"); + } + + $pipe->hmset("tenants:$id", array_merge($data, ['_tenancy_domains' => json_encode($domains)])); + foreach ($domains as $domain) { + $pipe->hmset("domains:$domain", 'tenant_id', $id); } - - // todo2 deleted domains }); } public function deleteTenant(Tenant $tenant): void { - $this->redis->pipeline(function ($pipe) use ($tenant) { + $this->redis->transaction(function ($pipe) use ($tenant) { foreach ($tenant->domains as $domain) { $pipe->del("domains:$domain"); } @@ -120,7 +143,6 @@ class RedisStorageDriver implements StorageDriver */ public function all(array $ids = []): array { - // todo2 $this->redis->pipeline() $hashes = array_map(function ($hash) { return "tenants:{$hash}"; }, $ids);