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

23 commits

Author SHA1 Message Date
lukinovec
553f57a8ad
[MINOR BC] BroadcastingConfigBootstrapper rewrite, bugfixes (#1448)
> Minor breaking change: BroadcastingConfigBootstrapper::$broadcaster property removed, we now just use the default broadcaster for setting mappings based on a preset

### Broadcasting auth route problem (`Broadcast` facade always uses
central `BroadcastManager`)

> Note: Tested with Pusher and Reverb. Also, this is the primary problem
that this PR solves.

When using `BroadcastingConfigBootstrapper` for broadcasting on private
channels with multiple broadcasting apps (= each tenant has its own
Pusher/Reverb/... app and credentials), the auth requests sent to the
broadcasting server use the central broadcasting credentials. This is
because the `Broadcast` facade (used in
`BroadcastController::authenticate` like `Broadcast::auth($request)` to
authorize the channels **and then retrieve the auth key that's then sent
to the broadcasting server**) keeps using the `BroadcastManager`
instance resolved in the central context instead of the tenant
`BroadcastManager`. Calling `withBroadcasting()` in `bootstrap/app.php`
results in calling `Broadcast::routes()` in the central context, which
resolves and stores the central `BroadcastManager` (which is never
cleared, and is used in tenant context).

Clearing the facade's resolved instance
(`Illuminate\Contracts\Broadcasting\Factory`) in
`BroadcastingConfigBootstrapper::bootstrap()` forces the facade to
re-resolve `Factory` (= `BroadcastManager`) on the next use, so the next
`Broadcast::auth()` call uses the tenant `BroadcastManager` in the
tenant context (and clearing the resolved instance in `revert()` makes
the `Broadcast` facade use the central `BroadcastManager` again), and
the credentials from the current config will be used for authentication.

### Central `BroadcastManager` doesn't pass custom driver creators to
the tenant manager

When registering a custom driver creator
(`app(BroadcastManager::class)->extend('custom-driver', fn($app,
$config) => new CustomBroadcaster(...))`) in the central context (e.g.
in `BroadcastServiceProvider`/`AppServiceProvider`, or in our case, in
the tests), the creator won't be available in the tenant context.
Calling e.g. `app(BroadcastManager::class)->driver('custom-driver')` in
the tenant context (if the creator was registered only in the central
context) would throw `InvalidArgumentException` ("Driver [custom-driver]
is not supported.").

To fix that, register the original (central) `BroadcastManager`'s custom
creators in the new `BroadcastManager` in
`BroadcastingConfigBootstrapper::bootstrap()`.

> Note: This was always a problem, the tests just never caught it. The
original test where we used a custom driver creator actually registered
the same creator on each context switch (see the
`$registerTestingBroadcaster()` calls in the removed `BroadcastingTest`
file) -- if the creator was registered just once (which is what we do
now, after the fix), the test would fail. The test registered the driver
creator repeatedly which worked around custom creators not persisting
after switching between contexts.

### Central `Broadcaster` instance bound in tenant context

> Note: This section describes the problem with resolving/injecting the
`Broadcaster` contract directly (via DI or `app(Broadcaster::class)`).
The `extend()` call that fixes it also plays a second, bigger role --
see the "How `BroadcastingConfigBootstrapper::bootstrap()` works"
section below.

The problem was that resolving
`Illuminate\Contracts\Broadcasting\Broadcaster::class` in tenant context
returned the broadcaster instance from the central context (Laravel
binds the contract as a singleton, resolved from the default driver's
config as it was at resolution time -- before tenancy was initialized).
Fixed by making `Broadcaster::class` resolve to the current
`BroadcastManager`'s default broadcaster (= the tenant broadcaster in
tenant context) via `app->extend()`.

Since the manager caches its broadcasters (see "Broadcasters are
resolved once per tenancy initialization" below),
`app(Broadcaster::class)` and `Broadcast::driver()` return the same
instance in every context -- same as Laravel's default behavior.

### How `BroadcastingConfigBootstrapper::bootstrap()` works (the
`extend()` calls)

Both `extend()` calls run immediately during `bootstrap()`, not lazily
on some later resolution -- `extend()` executes the closure right away
when the extended singleton is already resolved, and both singletons are
resolved at the top of `bootstrap()` (where we save the central
instances for `revert()`).

Since the singletons are already resolved, the container also doesn't
store the closures as extenders -- they run once during `bootstrap()`
and that's it. Once `revert()` restores the original instances,
resolving either singleton again (even after a `forgetInstance()`) goes
purely through the original bindings. So when tenancy initializes:

1. `setConfig()` maps the tenant's properties to the broadcasting
config.
2. The `BroadcastManager` extend swaps the bound manager for a fresh one
with an empty driver cache and passes the central manager's custom
driver creators to it.
3. The `Broadcaster` contract extend calls `connection()` on the tenant
manager, which resolves the default broadcaster using the updated
(tenant) config and caches it as the manager's default driver. The
central broadcaster's auth properties -- the channel auth closures,
their options, and the authenticated user callback -- are then copied
onto this tenant broadcaster.
4. `Broadcast::clearResolvedInstance()` makes the facade re-resolve on
the next call, so it returns the tenant manager instead of the stale
central one.

When `/broadcasting/auth` gets hit later, `Broadcast::auth()` goes
through the tenant manager's `driver()`, which returns the broadcaster
cached in step 3 (built with the tenant credentials, using the copied
channel auth closures). This means the `Broadcaster` contract extend
isn't just for code that resolves or injects the contract directly --
it's also what makes channel auth work in tenant context. Without it,
`app(Broadcaster::class)` would keep returning the stale central
broadcaster, and the tenant broadcaster's `$channels` would stay empty,
so `Broadcast::auth()` would throw a 403 for every channel registered in
the central context. The same goes for user authentication --
`resolveAuthenticatedUser()` has no fallback, so without the copied
authenticated user callback (registered via
`Broadcast::resolveAuthenticatedUserUsing()`), `/broadcasting/user-auth`
would throw a 403 in tenant context.

`revert()` restores the original central manager and broadcaster
instances via `instance()` (they're saved at the top of `bootstrap()`
and nothing touches them while tenancy is initialized) and clears the
facade's resolved instance again.

### Broadcasters are resolved once per tenancy initialization,
`TenancyBroadcastManager` is removed

Originally, `TenancyBroadcastManager` re-resolved the broadcasters
listed in its `$tenantBroadcasters` static property on every retrieval.
Earlier, we considered that good since it meant direct config changes in
tenant context were picked up immediately. [As discussed in the
review](https://github.com/archtechx/tenancy/pull/1448#discussion_r3555941319),
there's probably no real use case for that, and the re-resolving
actually caused a subtle bug: channels registered via
`Broadcast::channel()` in tenant context got silently lost on the next
retrieval (e.g. during a `/broadcasting/auth` request), because each
re-resolution started over from a copy of the central channels -- making
the auth request fail with a 403 as if the channel was never registered.

Now, `TenancyBroadcastManager` is removed entirely.
`BroadcastingConfigBootstrapper` binds a fresh, base `BroadcastManager`
with no cached broadcasters, so the broadcasters get resolved using the
tenant's credentials on first use and stay cached (like in the parent
manager) for the duration of the tenant's context. The central
broadcaster's auth properties (the channel auth closures, their options,
and the authenticated user callback) are copied to the tenant
broadcaster directly in the `Broadcaster` contract's `extend()` closure
-- since Laravel only ever uses the *default* broadcaster's channel auth
closures for broadcasting auth (both `Broadcast::channel()` and
`Broadcast::auth()` go through the default broadcaster), the properties
only have to be copied to the default broadcaster.

The channel auth closures are always *only* on the default broadcaster,
even in plain Laravel. Every `Broadcast::channel()` call adds an entry
to the same `$channels` array on the default broadcaster -- a channel
with its own authorization rules is just one of those entries, so it
gets copied along with the rest. The only way to register a closure on a
non-default broadcaster is calling
`Broadcast::driver('foobar')->channel(...)` explicitly, and nothing in
Laravel's auth flow ever reads those (`/broadcasting/auth` always
authenticates using the default broadcaster).

This all means that:
- `TenancyBroadcastManager` is removed. Its `$tenantBroadcasters`
property's purpose was listing the broadcasters to re-resolve (and pass
the central channel auth closures to) -- custom drivers now work without
the need to configure anything, and the closure copying is a few lines
in the bootstrapper instead of a manager override.
- Channels registered via `Broadcast::channel()` in tenant context
persist for the duration of that context. They don't leak into other
tenants' contexts or into the central context.
- For broadcasters to use updated credentials, tenancy has to be
reinitialized. Direct broadcasting config changes made in tenant context
aren't picked up by the broadcasters, and tenant property changes are
only mapped to config in `bootstrap()`. An already-injected (= stale)
`Broadcaster` instance additionally needs to be obtained again after
reinitialization -- reinitializing swaps the bound instance, so a
previously injected instance keeps using the old credentials.

### NOTE: Already-connected clients stop receiving broadcasts after a
credential update

Updating a tenant's credentials doesn't disconnect already-connected
clients (tested with Reverb) -- they stay connected using the old key,
but they stop receiving broadcasts, since broadcasts sent after the
update are sent with the new credentials.

The frontend has to reconnect using the new key (e.g. by refreshing the
page). Meaning, clients can't be notified about the credential change
through websockets themselves -- a broadcast sent after the update
already uses the new credentials, so it won't reach clients connected
with the old key. So for things like notifying clients in response to
the credential changes, a different mechanism is needed.

### Credential map: overriding presets (BroadcastingConfigBootstrapper)

Previously, credential mappings from `$mapPresets` overrode mappings
defined in `$credentialsMap`. If someone used e.g. Pusher 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.

Fixed that by reversing the `array_merge()` order in
`BroadcastingConfigBootstrapper::__construct()`.

### **MINOR BC:** `BroadcastingConfigBootstrapper::$broadcaster`
property removed

`BroadcastingConfigBootstrapper::$broadcaster` determined which
`$mapPresets` preset to apply instead of just using
`broadcasting.default`. Its only effect was applying a preset for a
connection other than the default -- a connection nothing resolves, so
it did nothing useful. Set `$credentialsMap` directly if you need a
non-default mapping.

Removing it also let us drop the static mutation from the constructor,
which used to overwrite the user-configured `$credentialsMap`. The
preset merge now happens locally in `setConfig()`.

### Tests

Deleted the BroadcastingTest file, moved the tests to appropriate
bootstrapper test files.

Added tests
- for mapping tenant properties to broadcaster credentials (including
keeping the central config values when a tenant doesn't have a mapped
property, and reverting to the central credentials after a tenant with
credential overrides)
- for the rest of the changes mentioned above (including a regression
test for the tenant-context channel registration bug)
- for copying the channel options and the authenticated user callback
along with the channel auth closures
- for the bound `Broadcaster` and the manager's default broadcaster
being the same instance in central and tenant contexts
- for broadcasters that only implement the `Broadcaster` contract
(instead of extending the abstract class), and for configuring which map
preset is used via the `$broadcaster` property

Also improved the existing tests.

---------

Co-authored-by: Samuel Stancl <samuel@archte.ch>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-07-23 00:26:04 -07:00
lukinovec
76e5f96559
Add LogChannelBootstrapper (#1381)
This PR adds the LogChannelBootstrapper to provide tenant-specific
logging configuration. The bootstrapper automatically configures storage
path channels to use tenant-specific directories (NOTE: for this to work
correctly, the bootstrapper has to run AFTER
FilesystemTenancyBootstrapper, otherwise, the logs still won't be
separated, unless you use overrides) and supports custom channel
overrides for custom logging scenarios -- mapping tenant properties to
the channel config, or using custom closures an array with the logging
config, e.g. for making the slack channel (that's not handled by the
bootstrapper by default) tenant-specific.

The bootstrapper first modifies the channel config, then forgets the
channel from LogManager so that on the next logging attempt, the channel
is re-resolved with the modified config. Otherwise, the channel would
just use the initial config **if the channel was resolved before**. If
the channel wasn't resolved before, it'll always be resolved with the
updated (tenant) config, unless the configuration fails. In that case,
the config will be reverted (the central config will be restored) and
the error will be logged using the original channel.

When using a channel stack, the `stack` channel itself also has to be
forgotten, since the LogManager could retain e.g. the original `stack`
channel's webhook URL, while the underlying `slack` channel would use
the updated one, and while logging, the app would actually use the
initial webhook URL instead of the updated one (encountered this issue
while testing).

Note that **all** channels in `$storagePathChannels` and
`$channelOverrides` are affected.

Also, adding `'attachment' => 'false'` to the slack channel's config
makes the slack channel work with Discord webhooks (just a cool thing we
figured out whlle testing the bootstrapper).

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Samuel Štancl <samuel@archte.ch>
2026-07-08 01:42:05 -07:00
lukinovec
17706f8e2c
Correct DatabaseTenancyBootstrapperTest (#1466)
### Test for DatabaseTenancyBootstrapper throwing an exception when
`DB_URL` is set

The test meant to cover this set `central.url` before creating a tenant.
That made the `CreateDatabase` job fail with a QueryException (it tried
to use the URL host as a database name). The bootstrapper's check was
never reached, and the exception that the test intended to cover wasn't
ever thrown in the end. The test passed for the wrong reason.

Fixed by creating the tenant first, setting the url only after that, and
asserting that `tenancy()->initialize()` throws the bootstrapper's
specific exception.

### Reference env variable that Laravel uses now (`DB_URL`) instead of
`DATABASE_URL`

Changed all `DATABASE_URL` references in Tenancy to `DB_URL`. [In
Laravel
11](96508d43ec (diff-e4382b565f69d02de16eef89c8d5ca2b60a679ffca90ab8b7d85fbd78f30075bR35)),
the `DATABASE_URL` env variable got renamed to `DB_URL` in
`config/database.php`. The env var got renamed quite a while ago, so
referencing `DATABASE_URL` in Tenancy's comments and tests was a bit
confusing.

### Minor cleanup

The `harden prevents tenants from using the database of another tenant`
test now reads the central connection name from
`config('tenancy.database.central_connection')` instead of hardcoding
`'central'` for consistency with the other tests.
2026-06-30 18:59:46 -07:00
lukinovec
aa9d1d7fcf
Parameter validation and other DB manager improvements (#1459)
### Parameter validation

In `statement()` calls of `TenantDatabaseManager`s, use parameter
binding when possible. When that's not possible, validate the parameters
using `validateParameter()` or `validatePassword()`.

Passwords use a less strict allowlist than other parameters (e.g. DB
names), since passwords tend to use more special characters but we can
afford to be more restrictive in the generic `validateParameter()`.

In `SQLiteDatabaseManager`, names of file-based databases are validated
(in `createDatabase`, `deleteDatabase` and `databaseExists`) using a
similar allowlist to the (non-password) parameters in other DB managers,
with an additional character: `.` (this addition is necessary since
file-based SQLite databases end with `.sqlite`).

### DatabaseTenancyBootstrapper - harden() and the lost test file

While checking for more places that could use validation, I realized
that it's possible to update tenant's db_name to the central DB or the
DB of another tenant. Added the `DatabaseTenancyBootstrapper::$harden`
property -- setting it to true prevents tenants from connecting to the
wrong databases (`RuntimeException` is thrown after connecting to the
wrong database).

Also, the DatabaseTenancyBootstrapper test file was ignored while
running tests because it lacked the `Test` suffix. Added the suffix and
fixed the broken `DATABASE_URL` test in the file.

### SQLiteDatabaseManager - respect static $path property in
makeConnectionConfig()

SQLiteDatabaseManager had a bug in `makeConnectionConfig`: the method
didn't respect the static `$path` property, it used `database_path()`
instead. Added a regression test for that. Also recognizing in-memory
SQLite databases (using `isInMemory()`) is more strict now so that
simply having a db_name with `_tenancy_inmemory_` somewhere in the name
doesn't make a file-based database considered in-memory.

### MySQLDatabaseManager - charset and collation defaulting

Creating databases with `null` charsets and collations resulted in a
`QueryException`, since null isn't a valid charset/collation. To solve
that, in the `CREATE DATABASE` statement in MySQLDatabaseManager, only
add charset/collation to the statement if they are not null.

MySQL defaults to the server's charset and collation, so it's safe to
not pass any charset/collation in the `CREATE DATABASE` statement and
let MySQL choose. Also, if e.g. collation is non-null and charset is
null, MySQL will use a charset compatible with the used collation, and
this works both ways.

---------

Co-authored-by: Samuel Stancl <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-06-27 19:51:30 -07:00
lukinovec
dfb0e1ad66
TenancyUrlGenerator: override toRoute(), refactor (#1439)
This PR adds the `toRoute()` method override to `TenancyUrlGenerator`.
`toRoute()` now attempts to find a tenant equivalent of the passed route
(= a route with the same name as the passed one, but with the tenant
prefix) and generates URL for the tenant route. This behavior can be
bypassed using the bypass parameter, like with the `route()` method
override `TenancyUrlGenerator` had until now.

The primary reason for adding this is that Livewire v4 no longer uses
the `route()` helper (which automatically prefixes the passed route name
because of the override in `TenancyUrlGenerator`) in
`Livewire::getUpdateUri()`. Now, it uses `toRoute()`
(544aa3dfb8 (diff-e7609f8b0a60bde5a85067803d4e2f08f235c7cee9225a51ea67a85ff9a1d694R52)),
which didn't automatically swap the route for its 'tenant.'-prefixed
equivalent in tenant context (until now). So for the Livewire
integration to work with path identification, we need to override
`toRoute()` as described.

The `temporarySignedRoute()` override got removed because
`temporarySignedRoute()` calls `route()` under the hood, there's no need
to specifically override `temporarySignedRoute()`.

> Note: Browsing old convos, it seems like the `temporarySignedRoute()`
override was needed to make Livewire file uploads work with path
identification, but it's not needed anymore. TenancyUrlGenerator had
some changes since then, and now, I can't see the _exact_ reason why we
needed the override (`temporarySignedRoute()` uses `route()` under the
hood, so the only thing that should really matter is overriding
`route()`/`toRoute()`). It was likely a leftover from some older
implementation.

The `route()` override got simplified. Since `route()` uses `toRoute()`
under the hood, the `route()` override only has to have the prefixing
logic. The rest is delegated to `toRoute()`.

> Note: Even though we override `toRoute()` now which `route()` uses for
generating the URLs, we still need to override `route()` for its
`$this->routes->getByName($name)` call to receive the prefixed name. For
example, if `route()` wasn't overridden, and we only had one route:
`tenant.foo` (no central `foo` route), and we'd call `route('foo')`,
we'd get an exception saying that route "foo" wasn't found, even if
automatic route name prefixing was enabled and `toRoute()` was
overridden. With the `route()` override, `route('foo')` acts as if we
passed 'tenant.foo' instead of 'foo'.

Comments in TenancyUrlGenerator and UrlGeneratorBootstrapper got updated
to be more accurate. All _intentionally_ affected methods are listed in
TenancyUrlGenerator's docblock.

---------

Co-authored-by: Samuel Stancl <samuel@archte.ch>
2026-06-06 14:52:37 -07: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
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
Victor R
3c0e21b726
[4.x] Filesystem bootstrapper: scoped disk support (#1402)
Fixes #1401

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: lukinovec <lukinovec@gmail.com>
Co-authored-by: Samuel Stancl <samuel@archte.ch>
2025-12-16 23:17:11 +01:00
cab8ecebec
Create tenant storage directories in FilesystemTenancyBootstrapper (#1410)
This is because the CreateTenantStorage listener only runs when
a tenant is created, but in multi-server setups the directory may
need to be created each time a tenant is *used*, not just created.

Also changed the listeners to use TenantEvent instead of specific
events, to make it possible to use them with other events, such as
TenancyBootstrapped.

Also update permission bits in a few mkdir() calls to better scope
data to the current OS user.

Also fix a typo in CacheTenancyBootstrapper (exception message).
2025-11-04 21:16:39 +01:00
lukinovec
469595534e
[4.x] Make TenancyUrlGenerator inherit the original UrlGenerator's scheme (http or https) (#1390)
Before, when using UrlGeneratorBootstrapper, and your app had a
`https://` url, in tenant context, the url would have the `http://`
scheme.

Now, the bootstrapper makes sure that the TenancyUrlGenerator inherits
the original UrlGenerator's scheme. So if your app has e.g. url
"https://some-url.test", `route('home')` in tenant context will return
"http**s**://some-url.test/home" (originally, you'd get
"http://some-url.test/home" - the original scheme - https - wouldn't be
respected in the tenant context).

This PR addresses the issue reported on Discord
(https://discord.com/channels/976506366502006874/976506736120823909/1399012794514411621).

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Samuel Štancl <samuel@archte.ch>
2025-10-28 13:26:50 +01:00
33e4a8e4e2 Remove and recategorize todos 2025-08-31 16:57:52 +02:00
ecc3374293 [4.x] Support database cache store tenancy (#1290) (resolve #852)
* Initial implementation (lukinovec)

* Make sure DatabaseCacheBootstrapper runs after DatabaseTenancyBootstrapper, misc wip changes

* Fix withTenantDatabases()

* Add failing test (GlobalCacheTest)

* Configure globalCache's DB stores to use central connection instead of default connection every time it's reinstantiated

* Make GlobalCache facade not cached. Even though it wasn't causing issues
in our existing tests, it likely was flaky, and making it not $cached
makes it now consistent with global_cache() - always getting a new
CacheManager from the globalCache container binding

* Add database connection assertions in GlobalCacheTest

* Run all cached resolver/global cache tests with DatabaseCacheBootstrapper

* Reset adjustCacheManagerUsing in revert() and TestCase

* Reset static $stores property

* Finalize GlobalCache-related changes

* tests: remove pointless cache TTLs

* Refactor DatabaseCacheBootstrapper

* Refactor tests

Co-authored-by: lukinovec <lukinovec@gmail.com>
2025-08-08 00:54:01 +02:00
lukinovec
f308e2f84d
[4.x] Resolve testing todos (#1361)
* Test encrypted cookie identification

* Add Fortify bootstrapper custom query param passing test

* Correct Fortify route bootstrapper test (todo  refactor, convoluted)

* Clarify Fortify bootstrapper test

* Fix encrypted cookie identification test

* Move encrypted cookie assertion to "cookie identification works"

* Cover configured tenant model columns in cached resolver tests

* Refactor testing resolver with default vs custom tenant model name config

* Delete resolved todo

* Make code more concise

* Keep initial formatting (minimize diff noise)

* Make dataset/helper method parameter clearer

* Clarify fortify test

* Clarify assertions, improve comments

* Delete excessive comments, make existing comments consistent and clearer

* Make cached resolver test file clearer, update outdated comments

* Use the tenant model column term consistently

* FIx inconsistencies

* Provide more info in comment

* make comment more clear

* static property reset

---------

Co-authored-by: Samuel Štancl <samuel@archte.ch>
2025-08-03 23:21:03 +02:00
5f7fd38e5a
[4.x] URL generation, request data identification improvements (#1357)
* UrlGenerator: set defaults based on config; request data: move config to config file+resolver

* Claude code adjustments

* improve request data tests, simplify complex test in UrlGeneratorBootstrapperTest

* url generator test: test changing tenant parameter name

* request data identification: add tenant_model_column configuration

* defaultParameterNames -> passQueryParameter

* move comment

* minor refactor in PathIdentificationTest, expand CLAUDE.md to include early identification section

* Fix COLOR_FLAG

* improve test name

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* TenancyUrlGenerator: add a check for queryParameterName being null

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix code style (php-cs-fixer)

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-02 03:43:47 +02:00
8960a83047
[4.x] Laravel 12 support (#1321)
* Add Laravel 12 support, drop Laravel 11 support

* Fix RLS tree generation (specify schema name in generateTrees())

* ci fixes, use stable virtualcolumn version

---------

Co-authored-by: lukinovec <lukinovec@gmail.com>
2025-02-25 16:26:18 +01:00
657e165cc8
[4.x] Cleanup (#1317)
* cleanup, resolve todos, add immediate todos

* Improve path_identification_middleware docblock

* rename leave() method in tests

* wip fix hardcoded values making assumptions about the parameters used in routing

* defaultParameterNames

* fix CreatesDatabaseUsers return values

* $tenant -> tenant()

* resolve more todos

* make comment block a complete block

* Correct useTenantRoutesInFortify(), delete unused import

* test fixes

* remove todos

* remove JobPipeline todo

* simplify comment example

* remove todo

* fix VERSION_PREFIX in queue.yml

---------

Co-authored-by: lukinovec <lukinovec@gmail.com>
2025-02-20 20:49:09 +01:00
lukinovec
cecf07a8c9
[4.x] Add tenant parameter to defaults() in UrlGeneratorBootstrapper (#1311)
* Pass tenant parameter using defaults in UrlGeneratorBootstrapper, update tests accordingly (wip)

* Fix code style (php-cs-fixer)

* Update bootstrapper

* Improve TenancyUrlGenerator docblocks

* Improve bootstrapper/TenancyUrlGenerator tests (WIP)

* Improve route() name prefixing test

* Keep  `UrlGeneratorBootstrapper::$addTenantParameterToDefaults` disabled by default

* Add `$override` functionality  to TenancyUrlGenerator

* Test $override functionality, update new defaults in the bootstrapper tests

* Fix code style (php-cs-fixer)

* Update comments

* Update routeNameOverride()

* cleanup

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Samuel Štancl <samuel@archte.ch>
2025-02-14 13:57:29 +01:00
Samuel Štancl
5d3b3d3c21
[4.x] Improve RootUrl and UrlGenerator bootstrappers (#1294)
* Make RootUrlBootstrapper run ONLY in CLI by default (add $rootUrlOverrideInTests), work with resolved UrlGenerator

* Make resolving 'url' return a pre-created generator instance instead of creating it on every app('url') call

* Take care of doubling tenant keys in TenancyUrlGenerator, add regression test for using UrlGenerator and RootUrl bootstrappers together

* Fix code style (php-cs-fixer)

* refactor RootUrlBootstrapper

* add docblock

* clarify docblock

* simplify test: use concrete values instead of overly dynamic code

* Fix bootstrapper order in test, add url('/') assertion

* Use $this->app instead of app()

* Improve TenancyUrlGenerator and RootUrlBootstrapperTest clarity

* Revert attempt to maintain compatibility between the two bootstrappers

* Delete bootstrapper combining test

* Fix code style (php-cs-fixer)

---------

Co-authored-by: lukinovec <lukinovec@gmail.com>
Co-authored-by: PHP CS Fixer <phpcsfixer@example.com>
2025-01-16 10:30:06 +01:00
lukinovec
cb0d7e2902
Improve early identification tests (#66)
* Separate route-level domain identification test from path/request to improve readability WIP

* Get rid of confusing datasets in route-level identifcation tests

* Clean up updated tests

* Simplify early id tests

* Reduce dataset duplication

* Improve test readability, fix false positive,  polish details

* Separate early ID test from defaulting test (WIP)

* Finish improving and correcting the early identification/default route mode tests

* Make flag/default mode usage more clear by improving the docblock in DealsWithRouteContexts

* Fix PHPUnit deprecation warnings

* code review

* code review

---------

Co-authored-by: Samuel Štancl <samuel@archte.ch>
2024-12-20 03:42:23 +01:00
lukinovec
5f8a3d2ffe
Improve the tests where the removed universal_channel() was still used (#64) 2024-10-11 22:50:38 +02:00
5929ff9da2 rename mail bootstrapper test file 2024-09-27 21:03:39 +02:00
lukinovec
6e67ddf7a5
Resolve test to-dos (#45)
* Only retrieve domains if the relationship and the domains table exist (DomianTenantResolver)

* Resolve todo, add other todos

* Use constructor promotion in DeleteDomains

* Fix imports + domain deletion test

* Confirm that turning on resolver caching doesn't break the tests

* Fix Tenant model imports

* Fix code style (php-cs-fixer)

* remove runtime schema check

* temp: enable resolver cache

* make 'autoincrement ids are supported' pass

* disable resolver cache

---------

Co-authored-by: PHP CS Fixer <phpcsfixer@example.com>
Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com>
Co-authored-by: Samuel Štancl <samuel@archte.ch>
2024-04-18 00:22:03 +02:00
Samuel Štancl
eecf6f21c8
Cache prefixing logic rewrite, session scoping improvements, tests refactor (#43)
* Run cache tests on all supported drivers

* update ci healthcheck for memcached

* remove memcached healthcheck

* fix typos in test comments, expand internal.md [ci skip]

* add empty line [ci skip]

* switch to using $store->setPrefix()

* add dynamodb

* refactor try-finally to try-catch

* remove unnecessary clearResolvedInstances() call

* add dual Cache:: and cache() assertions

* add apc

* Flush APCu cache in test setup

* Revert "add dual Cache:: and cache() assertions"

This reverts commit a0bab162fbe2dd0d25e7056ceca4fb7ce54efc77.

* phpstan fix

* Add logic for scoping 'file' disks to FilesystemTenancyBootstrapper

* minor changes, add todos

* refactor how the session.connection is used in the DB session bootstrapper

* add session forgery prevention logic to the db session bootstrapper

* only use the fs bootstrapper for file disk in 'cache data is separated' dataset

* minor session scoping test changes

* Add session scoping logic to FilesystemTenancyBootstrapper, correctly update disk roots even with storage_path_tenancy disabled

* Fix code style (php-cs-fixer)

* update docblock

* make not-null check more explicit

* separate bootstrapper tests, fix swapped test names for two tests

* refactor cache bootstrapper tests

* resolve global cache todo

* expand tests: session separation tests, more filesystem separation assertions; change prefix_base-type config keys to templates/formats

* add apc session scoping test, various session separation bugfixes

* phpstan + minor logic fixes

* prefix_format -> prefix

* fix database session separation test

* revert composer.json changes, update laravel dependencies to expected next release

* only run session scoping logic in cache bootstrapper for redis, memcached, dynamodb, apc; update gitattributes

* tenancy.central_domains -> tenancy.identification.central_domains

* db session separation test: add datasets

---------

Co-authored-by: PHP CS Fixer <phpcsfixer@example.com>
2024-04-09 20:40:27 +02:00