1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-05-06 18:04:03 +00:00
Commit graph

1452 commits

Author SHA1 Message Date
lukinovec
3e32ec2118
Merge ddd8c68fbd into 23b18c93a0 2026-05-04 08:47:55 +02:00
Thomas
23b18c93a0
Skip DB deletion when create_database=false, add ignoreFailures (#1394)
Database deletion is now skipped by default if the tenant has the
`create_database` internal attribute set to false, meaning it was likely
created without a database. This skip can be opted out of by changing a
static property.

It also adds an opt-in static property for ignoring any other failures
during database deletion, to allow continuing execution of the delete
pipeline.

---------

Co-authored-by: Samuel Štancl <samuel@archte.ch>
2026-05-01 21:57:19 +02:00
41701aff5f
phpstan fix: Model covariants in Scope generics
Builds on changes in recent commit:
Commit ID: c32f52ce7c
Change ID: qsnosyvyulxzrnzorpxqwqqztmqorsmk
2026-05-01 16:09:52 +02:00
53f44762ca
docker: change mssql env yaml syntax 2026-05-01 15:55:06 +02:00
lukinovec
984911946a
Change tenant storage listeners into jobs (#1446)
The `CreateTenantStorage` and `DeleteTenantStorage` listeners were used
alongside JobPipelines. When the `TenantCreated` JobPipeline had
`shouldBeQueued(true)` and the `Listeners\CreateTenantStorage` was
uncommented, the listener would throw an exception
(`Stancl\Tenancy\Database\Exceptions\TenantDatabaseDoesNotExistException
Database tenantX.sqlite does not exist.`) because at the time of
executing the listener, the tenant DB wasn't created yet.

The same issue could likely also occur in the `DeleteTenantStorage`
listener as it uses `tenancy()->run()` to resolve the tenant's storage
path which wouldn't work if the tenant's database (or other resources)
was already deleted, making initialization impossible.

This PR changes `DeleteTenantStorage` into a job and puts it (commented)
into the job pipeline, so that it can be queued with the rest of the
jobs. It also removes `CreateTenantStorage` because it should be
redundant with the FilesystemTenancyBootstrapper creating the same paths
automatically when storage path is suffixed.

The old classes are kept but deprecated for backwards compatibility.

We've also added some edge case hardening to `DeleteTenantStorage` to
make sure it never deletes the central storage path directory, which
previously could in theory occur due to a misconfiguration if a user
enabled this job/listener but disabled storage path suffixing.

Co-authored-by: Samuel Štancl <samuel@archte.ch>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-04-22 16:45:54 +02:00
lukinovec
ab2a4d8438
Fix chaining withoutPending() with where() (#1457)
At the moment, `where()` cannot be used correctly while using
`withoutPending()`. For example, if we have a single non-pending tenant
in our DB (with ID 'foo'), queries like
`Tenant::withoutPending()->where('id', 'nonexistent')->first()`will
incorrectly return the non-pending tenant ('foo').

This is because `withoutPending()` does
`$builder->whereNull('data->pending_since')->orWhereNull('data')`. These
two aren't grouped, so `withoutPending()->where('id', 'nonexistent')`
basically translates to "WHERE data->pending_since IS NULL **OR (data IS
NULL AND id = 'nonexistent')**". So the query will include all tenants
whose `pending_since` is null (= all non-pending tenants).

Grouping `->whereNull('data->pending_since')->orWhereNull('data')` in a
closure passed to a separate `where()` fixes this issue.
2026-04-22 14:32:53 +02:00
lukinovec
ddd8c68fbd Improve comments 2026-04-21 13:25:05 +02:00
lukinovec
e592b3700e
Merge branch 'master' into broadcasting-fixes 2026-04-15 11:45:07 +02:00
c32f52ce7c
phpstan fix: Scope generics
phpstan started failing with '... implements generic interface
Illuminate\Database\Eloquent\Scope but does not specify its types:
TModel'. We solve this by adding an implements docblock to the scopes
implementing that interface. They're fairly generic - we just use the
Model type itself in the code - so we use Model for the type parameter.
2026-04-15 11:23:12 +02:00
lukinovec
d885659a16 Use toEqualCanonicalizing instead of toBe for custom creators
The order of the array items shouldn't matter.
2026-04-15 11:20:59 +02:00
lukinovec
7db486d1b9 Extract cleanup in broadcasting config test file 2026-04-15 11:12:59 +02:00
lukinovec
a7acd07e70 Reset BroadcastingConfigBootstrapper::$mapPresets in tests 2026-04-15 10:44:18 +02:00
lukinovec
f3652a8509 Make with() formatting consistent 2026-04-15 10:18:29 +02:00
lukinovec
a3c556870a Add regression test for credentialsMap merge order 2026-04-15 10:16:22 +02:00
lukinovec
7af2b8acb4 Improve BroadcastManager instance checks 2026-04-14 09:26:34 +02:00
lukinovec
20d494f4c6 Reset BroadcastingConfigBootstrapper::$broadcaster in tests 2026-04-14 09:09:46 +02:00
e31249dd09
Prevent mkdir() race conditions in FilesystemTenancyBootstrapper (#1453)
This prevents race conditions that may occur if there are two concurrent
processes trying to create the storage path for the tenant. The
storagePath() method runs during bootstrap() which can easily happen
in two places at once. The race condition specifically occurs in between
the is_dir() check and the mkdir() call, the latter producing an
exception if the dir already exist. We simply ignore any error coming
out of mkdir() and then check for success separately.

We could omit that success check since failure is unlikely and would
only occur due to a server misconfiguration that would manifest itself
in other ways as well, but this way the simple TOC/TOU race condition
is prevented while other errors are still reported.

We apply the same change to the mkdir() in scopeSessions() as the logic
is similar.

Resolves #1452
2026-04-13 23:57:59 +02:00
lukinovec
a247bb0b9e Correct config bootstrapper test comment 2026-04-13 15:48:03 +02:00
lukinovec
b3d5197702 Correct channel prefix test comments 2026-04-13 15:44:35 +02:00
lukinovec
4aeaa66b23 Remove unused $broadcaster parameter 2026-04-13 15:32:26 +02:00
lukinovec
9ab0a729d6
Fix typo
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-13 15:28:11 +02:00
dc344b7ae6
Merge branch 'master' into broadcasting-fixes 2026-04-12 13:29:28 +02:00
60dd5226c4
[4.x] Add Tenancy::reinitialize() method (#1449)
Some bootstrappers read attributes of the tenant during bootstrap() but
don't respond to changes made to the tenant afterwards.

Therefore, when making changes to the tenant that'd affect the behavior
of a bootstrapper, it's necessary to reinitialize tenancy (if it matters
that changes are reflected immediately). This adds a convenience helper
for that purpose.
2026-04-08 19:21:43 +02:00
lukinovec
f8528fc9ac Add 'reverb' to TenancyBroadcastManager::$tenantBroadcasters 2026-04-03 15:54:25 +02:00
lukinovec
ef476c5361 Polish comments 2026-04-03 13:40:44 +02:00
lukinovec
c831393589 Update comment 2026-04-03 13:10:19 +02:00
lukinovec
4937a74ed5 BroadcastingConfigBootstrapper and TenancyBroadcastManager: comments 2026-04-03 13:07:40 +02:00
lukinovec
29dd23db61 BroadcastingConfigBootstrapperTest: add 'reverb' driver to datasets
Adding 'reverb' to `TenancyBroadcastManager::$tenantBroadcasters` will make these tests pass.
2026-04-03 11:26:46 +02:00
lukinovec
6b99921839 BroadcastingConfigBootstrapper: correct $credentialsMap array_merge order
Previously, credential mappings from `$mapPresets` overrode mappings defined in `$credentialsMap`. If someone used pusher/reverb/ably and wanted to override some of that preset's mappings, e.g. use 'pusher_app_key' instead of 'pusher_key' by specifying 'pusher_app_key' in `$credentialsMap`, the preset's mapping ('pusher_key') would still be used.
2026-04-03 11:23:24 +02:00
lukinovec
fc45e09dc9 Update BroadcastingConfigBootstrapperTest
Tests now use datasets with all drivers that are in `TenancyBroadcastManager::$tenantBroadcasters` by default plus the custom driver. Also add assertions for updating the tenant properties/config in tenant context.
2026-04-03 11:17:06 +02:00
lukinovec
4b1cc9c84a Improve comments 2026-04-02 16:54:53 +02:00
github-actions[bot]
9e9bedc0f2 Fix code style (php-cs-fixer) 2026-04-02 14:15:16 +00:00
lukinovec
b2add06a98 Delete BroadcastingTest
Tests from BroadcastingTest moved to the appropriate bootstrapper test files. The new tenant credentials test has assertions equal to both the original property -> config mapping test and the config -> credentials test.
2026-04-02 16:14:53 +02:00
lukinovec
bbe2ff02df BroadcastingTest: update channel inheritance test
Test that the bound Broadcaster instance inherits the channels too. Also test that the channels aren't lost when switching context to another tenant.
2026-04-02 15:33:35 +02:00
lukinovec
b6c035c912 Improve comments 2026-04-02 15:28:33 +02:00
lukinovec
0fbe1bc846 TenancyBroadcastManager: delete Broadcaster singleton binding
Moved binding `Broadcaster` to the bootstrapper.
2026-04-02 15:24:52 +02:00
lukinovec
28b61198ed TenancyBroadcastManager: update docblocks 2026-04-01 15:50:04 +02:00
lukinovec
d939866798 Fix custom creator assertions 2026-03-31 17:12:50 +02:00
lukinovec
c4f4451fd5 Add assertions for the config of the bound manager's driver 2026-03-31 17:12:50 +02:00
github-actions[bot]
0b860ea38a Fix code style (php-cs-fixer) 2026-03-31 14:33:01 +00:00
lukinovec
65beecf265 BroadcastingConfigBootstrapper: clear the Broadcast facade's resolved Broadcasting\Factory instance
After initializing tenancy, calls like `Broadcast::auth()` use the central `BroadcastManager`. Clearing the facade's resolved `Broadcasting\Factory` instance fixes that problem.
2026-03-31 16:32:33 +02:00
lukinovec
b1e91f1029 BroadcastingConfigBootstrapper: make Broadcaster::class resolve to tenant's broadcaster on bootstrap() 2026-03-31 16:25:25 +02:00
lukinovec
c653c51928 BroadcastingConfigBootstrapper: make tenant manager inherit central manager's custom creators 2026-03-31 16:22:41 +02:00
lukinovec
fafd082261 Fix typo in test 2026-03-31 16:19:43 +02:00
lukinovec
2ecc94d8e8 BroadcastingConfigBootstrapper: test persistence of custom driver creators
Test that TenancyBroadcastManager inherits the custom driver creators from the central BroadcastManager.
2026-03-31 16:19:15 +02:00
lukinovec
7d749eb592 BroadcastingConfigBootstrapper: test mapping credentials
Test that BroadcastingConfigBootstrapper correctly maps tenant properties to broadcasting config/credentials, and that the credentials don't leak when switching contexts. Also add the `$config` property to `TestingBroadcaster` so that we can access the credentials used by the broadcaster.
2026-03-31 15:32:45 +02:00
Samuel Mwangi
fb654e7a6b
[4.x] Update Pest to v4 (#1430) 2026-03-30 09:44:53 +02:00
c4960b76cb
[4.x] Laravel 13 support (#1443)
- Update ci.yml and composer.json
- Wrap single database tenancy trait scopes in whenBooted()
- Update SessionSeparationTest to use laravel-cache- prefix in L13
  and laravel_cache_ in <=L12. Our own prefix remains tenant_%tenant%_
  (as configured in tenancy.cache.prefix). We could update this to be
  tenant-%tenant%- from now on for consistency with Laravel's prefixes
  (changed in https://github.com/laravel/framework/pull/56172) but I'm
  not sure yet. _ seems to read a bit better but perhaps consistency
  is more important. We may change this later and it can be adjusted
  in userland easily (since it's just a config option).
2026-03-18 19:17:28 +01:00
8f3ea6297f
phpstan: change InputOption syntax 2026-03-09 02:13:37 +01:00
lukinovec
16861d2599
[4.x] Make URL::temporarySignedRoute() respect the bypass parameter (#1438)
Using `URL::temporarySignedRoute()` in tenant context with
`UrlGeneratorBootstrapper` enabled doesn't work the same as `route()`.
The bypass parameter doesn't actually bypass the route name prefixing.

`route()` is called in the `parent::temporarySignedRoute()` call, and
because the bypass parameter is removed before calling
`parent::temporarySignedRoute()`, the underlying `route()` call doesn't
get the bypass parameter and it ends up attempting to generate URL for a
route with the name prefixed with 'tenant.'.

This PR adds the bypass parameter back after `prepareRouteInputs()`, so
that `parent::temporarySignedRoute()` receives it, and the underlying
`route()` call respects it. Also added basic tests for the
`URL::temporarySignedRoute()` behavior (the new bypass parameter test
works as a regression test).
2026-03-09 02:07:02 +01:00