Closed seanmtaylor closed 2 years ago
Are you using the tenant
connection as a template for tenants' database connections?
Yeah
'template_tenant_connection' => 'tenant',
With the following in config/database.php
'tenant' => [
'driver' => env('TENANT_DB_DRIVER', 'mysql'),
'url' => env('TENANT_DATABASE_URL'),
'host' => env('TENANT_DB_HOST', '127.0.0.1'),
'port' => env('TENANT_DB_PORT', '5432'),
'database' => env('TENANT_DB_DATABASE', 'forge'),
'username' => env('TENANT_DB_USERNAME', 'forge'),
'password' => env('TENANT_DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
'schema' => 'public',
'sslmode' => 'prefer',
],
And the corresponding env varaibles:
TENANT_DB_DRIVER=pgsql
TENANT_DB_HOST=postgres
TENANT_DB_PORT=5432
TENANT_DB_USERNAME=default
TENANT_DB_PASSWORD=secret
TENANT_DB_DATABASE=hub
From what I can tell, in the DatabaseManager::connectToTenant()
method, it now calls $this->purgeTenantConnection()
first, which is setting the database.connections.tenant
config to null
.
So when it then calls $this->createTenantConnection()
, there's no longer anything available in that config.
Right. The tenant
connection shouldn't be used for templates — it's a reserved name used by the package for tenant connections created dynamically on the fly. I'll add a note to the docs and config.
You should create a different connection and use as the template instead.
For context, the issue with using this one as a template is that you could have one tenant which has a tenancy_db_host
property. Initializing tenancy for that tenant would then change this in config('database.connections.tenant')
. If you then tried to initialize a tenant that doesn't have a specific DB host, it'd still try to use the last tenant's host rather than the one specified in the template as the template got overridden by a concrete tenant connection.
Thanks for the help so far @stancl.
So I don't have to actually specify a tenant
connection in config/database.php
. Instead I should rename that to something like tenant_template
and use that as the value of tenancy.template_tenant_connection
?
Yeah, naming it tenant_template
would be ideal. And there should be no tenant
connection in config/database.php
defined by you.
Thanks for the help so far @stancl.
So I don't have to actually specify a
tenant
connection inconfig/database.php
. Instead I should rename that to something liketenant_template
and use that as the value oftenancy.template_tenant_connection
?
Thanks for this @stancl I hit this same issue after upgrading today also. Have made the same change.
Thanks @stancl, that seemed to do the trick for most cases.
However, there still seems to be an issue.
Take this controller as an example.
This is a controller in the Central context, which accepts a {tenant}
as a route parameter.
It calls $tenant->run()
to fetch the users from the Tenant's app, which works fine.
But when doing a ->toArray()
it throws the Database connection [tenant] not configured.
error.
class CentralControllerExample
{
public function __invoke(Tenant $tenant)
{
// Get users from the Tenant
$users = $tenant->run(function () {
return User::query()->get();
});
dump($users); // This works fine
dump($users->toArray()); // Errors: Database connection [tenant] not configured.
}
}
Our test suite is also broken due to the [tenant]
connection not being configured.
The first test file runs fine, but then all following tests fail. We make use of the DatabaseTransactions
trait, but this has always worked fine previously.
Doesn't seem right that ->toArray()
would do anything when you use ->get()
. Are you sure you're using ->get()
and not ->cursor()
?
And if it's that ->toArray()
includes information from relations that are yet to be loaded, then it's correct that this doesn't work. If you attempt to make a query to the tenant DB (which seems to be what's happening here for some reason) while in the central context (outside of ->run()
here), you'll see an error that the connection is not defined.
Okay, so it seems to be Telescope which is causing the the ->toArray()
to be called as part of it's extractDataFromView()
method
https://github.com/laravel/telescope/blob/4.x/src/Watchers/RequestWatcher.php#L211:L224
Which is eventually getting to the ->getDateFormat()
method in Eloquent's HasAttributes
concern
That method then has a ->getConnection()
call, which is where things are failing :exploding_head:
Hmm I see, nice work finding that!
I'd say that there's no reason to do e.g. toArray()
yourself on models that are on the wrong connection. But perhaps due to cases like these, we could add a feature to make it possible to recover the connection on the model instance?
We could store the tenant ID in a special property that's not part of $attributes
, for example. But still, that'd have to temporarily initialize tenancy, so it'd be pretty heavy. Finding an alternative solution first would be better.
One way to deal with this would be to cache the date format on the model. I.e. whenever getDateFormat()
is called, also set $dateFormat
value. I think there's no case when a model would be on a different connection later, or with a different date format. So PRing memoization logic for this property into Laravel, to make it possible to interact with models even if after connection is dead, could be the right way to go about this.
The change would probably be just:
public function getDateFormat()
{
- return $this->dateFormat ?: $this->getConnection()->getQueryGrammar()->getDateFormat();
+ return $this->dateFormat ??= $this->getConnection()->getQueryGrammar()->getDateFormat();
}
Could you test that in the vendor/
files?
I have same issue. With Laravel Framework 8.79.0 and the version 4.6 it works, but if I try to update to 3.5.1 I get this error. I don't understand if there is something of new to configure. Seems that the connection of "tenant" become null, and so in DatabaseManager.php configuration: throw this exception:
if (is_null($config = Arr::get($connections, $name))) {
throw new InvalidArgumentException("Database connection [{$name}] not configured.");
}
Read through the conversation above. The issue can be one of two things.
tenant
connection in database/config.php
Hello, thankyou. No I don't have tenant connection.
In my stacktrace i found this:
[2022-01-18 07:47:46] local.ERROR: Database connection [tenant] not configured. {"userId":1,"exception":"[object] (InvalidArgumentException(code: 0): Database connection [tenant] not configured. at /var/www/html/vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php:161)
[stacktrace]
#0 /var/www/html/vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php(124): Illuminate\\Database\\DatabaseManager->configuration()
#1 /var/www/html/vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php(95): Illuminate\\Database\\DatabaseManager->makeConnection()
#2 /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(1653): Illuminate\\Database\\DatabaseManager->connection()
#3 /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(1619): Illuminate\\Database\\Eloquent\\Model::resolveConnection()
#4 /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php(1389): Illuminate\\Database\\Eloquent\\Model->getConnection()
#5 /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php(1301): Illuminate\\Database\\Eloquent\\Model->getDateFormat()
#6 /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php(221): Illuminate\\Database\\Eloquent\\Model->asDateTime()
#7 /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php(183): Illuminate\\Database\\Eloquent\\Model->addDateAttributesToArray()
#8 /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(1485): Illuminate\\Database\\Eloquent\\Model->attributesToArray()
#9 /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(1515): Illuminate\\Database\\Eloquent\\Model->toArray()
#10 [internal function]: Illuminate\\Database\\Eloquent\\Model->jsonSerialize()
#11 /var/www/html/vendor/laravel/framework/src/Illuminate/Http/JsonResponse.php(84): json_encode()
#12 /var/www/html/vendor/symfony/http-foundation/JsonResponse.php(54): Illuminate\\Http\\JsonResponse->setData()
#13 /var/www/html/vendor/laravel/framework/src/Illuminate/Http/JsonResponse.php(32): Symfony\\Component\\HttpFoundation\\JsonResponse->__construct()
#14 /var/www/html/vendor/inertiajs/inertia-laravel/src/Response.php(105): Illuminate\\Http\\JsonResponse->__construct()
#15 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php(776): Inertia\\Response->toResponse()
#16 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php(763): Illuminate\\Routing\\Router::toResponse()
#17 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php(695): Illuminate\\Routing\\Router->prepareResponse()
#18 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()
#19 /var/www/html/vendor/inertiajs/inertia-laravel/src/Middleware.php(82): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#20 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Inertia\\Middleware->handle()
#21 /var/www/html/app/Http/Middleware/IsSuperAdmin.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#22 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): App\\Http\\Middleware\\IsSuperAdmin->handle()
#23 /var/www/html/vendor/inertiajs/inertia-laravel/src/Middleware.php(82): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#24 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Inertia\\Middleware->handle()
#25 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#26 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle()
#27 /var/www/html/vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php(44): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#28 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Auth\\Middleware\\Authenticate->handle()
#29 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#30 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle()
#31 /var/www/html/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#32 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle()
#33 /var/www/html/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#34 /var/www/html/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest()
#35 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Session\\Middleware\\StartSession->handle()
#36 /var/www/html/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#37 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle()
#38 /var/www/html/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#39 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle()
#40 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#41 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php(697): Illuminate\\Pipeline\\Pipeline->then()
#42 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php(672): Illuminate\\Routing\\Router->runRouteWithinStack()
#43 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php(636): Illuminate\\Routing\\Router->runRoute()
#44 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Router.php(625): Illuminate\\Routing\\Router->dispatchToRoute()
#45 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(167): Illuminate\\Routing\\Router->dispatch()
#46 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()
#47 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#48 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()
#49 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()
#50 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#51 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()
#52 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()
#53 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#54 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()
#55 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(86): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#56 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()
#57 /var/www/html/vendor/fruitcake/laravel-cors/src/HandleCors.php(38): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#58 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Fruitcake\\Cors\\HandleCors->handle()
#59 /var/www/html/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(39): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#60 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\\Http\\Middleware\\TrustProxies->handle()
#61 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#62 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(142): Illuminate\\Pipeline\\Pipeline->then()
#63 /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(111): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()
#64 /var/www/html/public/index.php(52): Illuminate\\Foundation\\Http\\Kernel->handle()
#65 /var/www/html/server.php(21): require_once('...')
#66 {main}
"}
Could be something with Inertia because if I try to make a simple View made with blade it works.
And I notice that I try to make a simple response()->json(data) I get this error. So, the problem is with inertia because Inertia make a json response, and seems has problem with serilization of model.
In fact I can see this error: [internal function]: Illuminate\\Database\\Eloquent\\Model->jsonSerialize()
And so, Is definitely an error on my code because I have an foreach of Tenants, and inside I make a initialize tenant for get data on his database (I have multidatabase), and If i delete
foreach with tenancy()->initialize($tenant);
and return for example Tenants:all()
, it works.
EDIT Solved my problem, definitely is not a issue with library. Thankyou.
Updating from 3.4.5 to 3.5.1 faced the same issue. I've made the following changes:
//config/tenancy.php
'database' => [
/**
* Connection used as a "template" for the tenant database connection.
*/
- 'template_tenant_connection' => 'tenant',
+ 'template_tenant_connection' => 'tenant_template',
//config/database.php
//Tenant
- 'tenant' => [
+ 'tenant_template' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => null,
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
For the first sight, application works fine. However, any test that uses DB connection now doesn't give any error anymore, but just "hang" forever. @stancl any idea why?
Downgraded to v3.4.6 and still having an issue with tests if using tenant_template
instead of tenant
I'd need to see the tests to help
I'm having the same issue here as I've upgraded our packages today. Database connection [tenant] not configured.
We are using the multi database approach and on our super admin (central connection) when looking at the details screen of a tenant we hit the tenant db using tenancy->run()
to grab data like user counts etc that only exist on the tenants db. We do not have a tenant connection defined as this is done by this package.
We are also using inertia and inertia uses toArray when rendering prop data for our vue components.
I've read the above and wondering why would a connection be needed if we are calling toArray()? We have already switched the connection whilst fetching the data and toArray is just formatting it? Is this an issue with Laravel or are we doing something wrong?
@metadeck For toArray()
, see this: https://github.com/archtechx/tenancy/issues/774#issuecomment-1010048328
The method essentially uses the model's PDO connection to fetch the format used for timestamps.
The ideal solution is to avoid converting tenant models to an array when you're outside the tenant context.
One possible fix that I proposed above would be setting the date format while the connection is still alive.
And then if there were still issues, I could reimplement this part of the logic to keep the last tenant's connection alive. But to be honest I don't really like that solution. Ending tenancy should remove all of the connections that were created for the tenant. Keeping a connection alive after that to support edge cases like these feels messy even if it'd be practical in some cases.
So ideally this could be fixed just by changing your usage of the models.
Keeping a connection alive after that to support edge cases like these feels messy even if it'd be practical in some cases.
Agreed. We'll look at updating our usage so that this works with Inertia on our central super admin app.
https://github.com/archtechx/tenancy/issues/774#issuecomment-1010053040
I can confirm that this solves the issue.
I'll attempt a PR then 👍🏻
I'd need to see the tests to help
Unfortunately I can't share tests here.
However, after digging a bit deeper, I found out that after changes mentioned in https://github.com/archtechx/tenancy/issues/774#issuecomment-1016399705 on both v3.4.5 and v3.5.1 the tenant()->delete()
hangs, it's being executed forever.
Digging further I found that the problem is https://github.com/archtechx/tenancy/blob/f08e33afd80d14d99eb0595261cb100f981d4b52/src/Jobs/DeleteDatabase.php#L32
Dump of $this->tenant->database()->manager()
:
Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager {#3368
#connection: "tenant_template"
}
Further: https://github.com/archtechx/tenancy/blob/f08e33afd80d14d99eb0595261cb100f981d4b52/src/TenantDatabaseManagers/MySQLDatabaseManager.php#L43 hangs.
the statement is DROP DATABASE `laravel_tenant__acme`
dump of $this->database()
:
Illuminate\Database\MySqlConnection^ {#10647
#pdo: Closure()^ {#10583
class: "Illuminate\Database\Connectors\ConnectionFactory"
this: Illuminate\Database\Connectors\ConnectionFactory {#6398 …}
use: {
$config: array:15 [
"driver" => "mysql"
"host" => "127.0.0.1"
"port" => "3307"
"database" => null
"username" => "root"
"password" => "password"
"unix_socket" => ""
"charset" => "utf8mb4"
"collation" => "utf8mb4_unicode_ci"
"prefix" => ""
"prefix_indexes" => true
"strict" => true
"engine" => null
"options" => []
"name" => "tenant_template"
]
}
file: "./vendor/laravel/framework/src/Illuminate/Database/Connectors/ConnectionFactory.php"
line: "179 to 191"
}
...
I don't see anything wrong here.
$this->database()->statement('show databases;')
returns true
If I connect to the DB with MySQL client and execute DROP DATABASE `laravel_tenant__acme`
it works fine.
Tried dd($this->database()->statement("DROP DATABASE `{$tenant->database()->getName()}`"));
- it hangs.
@stancl can you help please?
The rest seems to work fine and I didn't face any issues with inertia/vue3 by the moment.
If you're encountering this in v3.4.6 or earlier, it's probably unrelated to this issue.
It is not related directly, however before updating I was able to use tenant
as a tenant template connection and there were no issues at all, so indirectly applies in this particular case.
I still can't find the problem, was trying to uninstall some other composer packages, change some configs and still experience the issue. Should I create a separate issue?
Bug description
I'm trying to upgrade from 3.4.5 to 3.5.0, but when accessing a Tenant I am getting an error
It seems to have been caused by the changes made in https://github.com/archtechx/tenancy/commit/73a4a3018cadca2ba0fb5f2130fca1718a2b3670
It's this line, https://github.com/archtechx/tenancy/commit/73a4a3018cadca2ba0fb5f2130fca1718a2b3670#diff-570b873722200ff149851a2e1c3c016ed87596744cc8b8e5be003760a825f974R81, where it is now unsetting the
database.connections.tenant
config.I am using the
PostgreSQLDatabaseManager
.Steps to reproduce
n/a
Expected behavior
Connect to the Tenant database successfully.
Laravel version
8.78.1
stancl/tenancy version
3.5.0