0

I'm reposting this question because the previous one was closed as being answered by a related post here but it wasn't.

That post is saying how to access the servers using raw commands. My question has to do with how LARAVEL handles it - because I use Eloquent and Laravel relationships to handle all the pivots, etc.


So I have a multi-DB setup which I thought was working (well it does, but the caveat is the title).

I'm currently using two databases, one for the actual app we're developing, and another DB specifically for user accounts.

Here's the snippet on the ENV side:

DB_CONNECTION=mysql
DB_HOST=APP_SERVER_IP
DB_PORT=APP_SERVER_PORT
DB_DATABASE=APP_SERVER_DB
DB_USERNAME=APP_SERVER_USER
DB_PASSWORD=APP_SERVER_PASS

DB_CONNECTION_SH=mysql
DB_HOST_SH=ACCOUNTS_SERVER_IP
DB_PORT_SH=ACCOUNTS_SERVER_PORT
DB_DATABASE_SH=ACCOUNTS_SERVER_DB
DB_USERNAME_SH=ACCOUNTS_SERVER_USER
DB_PASSWORD_SH=ACCOUNTS_SERVER_PASS

Then on the database.php I have:

'mysql'  => [
    'driver'         => 'mysql',
    'host'           => env('DB_HOST', '127.0.0.1'),
    'port'           => env('DB_PORT', '3306'),
    'database'       => env('DB_DATABASE', 'forge'),
    'username'       => env('DB_USERNAME', 'forge'),
    'password'       => env('DB_PASSWORD', ''),
    ...
],
'sh'     => [
    'driver'   => env('DB_CONNECTION_SH'),
    'host'     => env('DB_HOST_SH'),
    'port'     => env('DB_PORT_SH'),
    'database' => env('DB_DATABASE_SH'),
    'username' => env('DB_USERNAME_SH'),
    'password' => env('DB_PASSWORD_SH'),
    ...
],
...

Then I use this method of setting the connection dynamically on the model.

public function __construct(array $attributes = array()) { 
    parent::__construct($attributes);
    $this->setConnection($this->switchableDB());
}

switchableDB is just a method on a trait; and it just returns a string of either 'mysql' or 'sh'. This is set ONCE in the env (and taken by the config) btw - so it's not as complicated as you may think.

public function switchableDB() {
    // simply return the connection based on what's set on the config/env
    return config('sso.enabled') ? 'sh' : 'mysql';
}

Which WORKS as far as selecting the DB goes.

What I WANT to do is

  1. Have the app server instance on one AWS instance
  2. Have its database on a remote DB instance
  3. Have the accounts server on ANOTHER remote DB (so that other apps can connect to it like this app)

But I observed that multi-DB access only seems to work if the databases are on the same server/IP.

You CAN have the app server on a different IP from the DBs no problem, but when I tried moving the two databases to two different IPs, suddenly things broke with a SQLSTATE[42000]: Syntax error or access violation: 1049 Unknown database 'DB_NAME') error.

But the database certainly exists, just not in the same IP. This suggests the DBs at least have to be located in one server. Reinforced when I cloned the "missing db" to the same server and set the host for both DBs as the same.

Which leads me to believe that while it CAN "look for" set the connection (via switchableDB), for some reason the connection's HOST (and possibly PORT) settings aren't recognized.

I must be doing something wrong because why even have the option to define the host on multiple DBs if it's just going to assume they're on the same server?

Hopefully someone can shed light on what I can do to basically use TWO different DBs on TWO different server IPs.


Update: clarification as Requested by user Dont Panic

So the "accounts" (sh) is a kind of "global" database. And there's a setting in the .env that can tell the webapp whether it should be using it or not.

The reason for this env "setting/switch" is if you wanted to (re)deploy the app in a self-contained environment where it just uses its local DBs for everything (i.e. an airgapped intranet) or whether it uses the "global/cloud" db.

So there are some tables common (users is one of them) on BOTH databases, but it never uses them at the same time - it's always one or the other (determined by what was set on the env)

So for a use case, I use it as a toggle whether or not to enable single-sign-on functionality. If SSO is enabled, it will use the users table of accounts (sh) db. But if it's disabled, it just uses the users table of the "local" db (mysql)

The latter will allow the user to just have a self-contained deployment where they don't even need the internet - they could just setup a local webserver on their machine and logins will work.

The former on the other hand, will assume you're online and should use the accounts db for user login.

Now I know this may not be the best implementation of SSO functionality, but that really isn't my question/concern. My concern is simply given the fact that switching connections is already working (as it DOES try to access the DBs as intended judging from the errors)... it's just that for some reason it assumes - regardless of what you set as the host of the nominated connection - that the they're all on the same IP (or localhost). Which begs the question: why even have a "host" setting for the other connection if it's not going to respect it?

nargalzius
  • 31
  • 6
  • "...Then I use this method of setting the connection dynamically on the model..." Why not just use the [`$connection`](https://laravel.com/docs/8.x/eloquent#database-connections) property on the model? – IGP Jan 23 '21 at 07:32
  • Because the `switchableDB()` value is dynamic depending on a setting. And multiple models change connection properties depending on the setting. I don't think `protected $connection = $this->switchableDB();` is allowed But that's beside the point; the `__construct ` method of setting the connection works fine (as it's able to select the DBs). The problem is (and the topic title) the HOST property is ingored - which breaks everything if the DBs are in different servers. – nargalzius Jan 23 '21 at 09:56
  • It is quite hard to understand what you are asking. The code shows you want a *single* model (presumably `accounts` aka users) spread across your 2 servers. That would mean the same DB table exists on both servers, some records on server A (eg user Jane), and some on user B (eg user Joe). But that does not seem to be what you actually describe? *Have the accounts server on ANOTHER remote DB* - this sounds like there is only one `accounts` table? Can you edit your question and clarify? – Don't Panic Jan 23 '21 at 10:06
  • @Don'tPanic edited the original post and appended the clarification you requested at the bottom. Let me know if that sheds light to your questions :) – nargalzius Jan 24 '21 at 12:47
  • *it assumes ... that they're all on the same IP* - my guess is bcs having a single table split across 2 DBs is not a supported use-case, eg how would relationships work? I understand that's not what you are trying to do though. Does your `setConnection()` update the model's `$connection` property? – Don't Panic Jan 24 '21 at 13:07
  • @Don'tPanic, it's _not_ trying to "split a single table to two dbs". The "table" (and parent db) used is depending on what the `.env` says. So while it can be toggled, it's really just set only once during prod build, it's doesn't change realtime or anything. Another way of putting it so we don't get caught up with the other details is to consider this: telling it to use `sh` instead of `mysql` was never an issue. The issues is _given_ it's using `sh` - why is it not accessing `sh` using the host that was defined for `sh`? – nargalzius Jan 24 '21 at 14:13
  • As I said, *I understand that's not what you are trying to do*. From Laravel's point of view though, setting a different DB server every time you access a model - which might happen many times per request - means it *could* be split across those connections. Which (probably?) makes no sense. Which is why I said it is likely not a supported use-case - whatever framework code handles that connection switching simply does not do the server IP part, bcs why would you? Maybe it is even an intentional safety-catch. – Don't Panic Jan 24 '21 at 14:51
  • Your use-case is a totally valid one, and I am sure it is both doable and supported, but making the switch on the fly during model instantiation somehow does not seem like the right approach. Maybe you could try extending your model, so SSO accounts get the different `$connection`? Or if this is a 1-time deployment sort of config thing, have something in your build/deploy process which flips that switch? – Don't Panic Jan 24 '21 at 14:51
  • @Don'tPanic _"Maybe you could try extending your model, so SSO accounts get the different $connection? Or if this is a 1-time deployment sort of config thing"_ I didn't extend it per se, I only included the construct to the models which needed the toggle functionality. But yes, it _is_ a 1-time deployment sort for config thing. I'm assuming if I do the `extends` approach I would need two different classes; one expliclitly using `mysql` connection and another expliclitly using`sh` connection. But then how will I set which extend to use based on the config? Is that even possible? – nargalzius Jan 24 '21 at 15:12
  • @Don'tPanic I've also added the switchableDB method in the original post just to show how it's really just a one time setting on the env/config. – nargalzius Jan 24 '21 at 15:18

1 Answers1

0

I understand what you're looking for. Unfortunately, you cannot set a dynamic $connection property on a model without having to write the functionality yourself (like you did on your __construct method. However, you can do it when you query the model.

App\Models\User::on('sh')->get();    // Query User model using sh connection
App\Models\User::on('mysql')->get(); // Query User model using mysql connection

This is equivalent to

(new App\Models\User)->setConnection('sh')->get();
(new App\Models\User)->setConnection('mysql')->get();
IGP
  • 14,160
  • 4
  • 26
  • 43
  • "you cannot set a dynamic $connection property on a model without having to write the functionality yourself (like you did on your __construct method)" I'm a bit confused by your statement. i DID write the functionality on the __construct... and it actually works as far as accessing different databases. Meaning i can have the model switch between 'sh' and 'mysql' no problem through the model. So to rephrase: if the 2 dbs were in the same server, the code works as is. My question is why the drastic effect when the HOST setting is changed on either one of them – nargalzius Jan 24 '21 at 12:24
  • What I meant is that functionality is not provided out of the box (you had to write the functionality on your constructor). Having connections with different hosts shouldn't be an issue as long as you actually can connect to the database. – IGP Jan 24 '21 at 12:50
  • -cont (sorry ran out of characters) Or are you saying that if I set the connection on the controller side (when querying the model like you said) that it would suddenly allow different host IPs? Because what you wrote seems to just be another way of doing it - as the construct method works the same way (setting the connection) in theory – nargalzius Jan 24 '21 at 12:51
  • @IGN yes I agree - that's what my team also assumed. But we discovered that doesn't seem to be the case. It knows to use `sh` if we tell it to use `sh` - but ONLY if `sh` db is on the same host as the `mysql` DB. – nargalzius Jan 24 '21 at 13:11
  • Have you checked if you can actually access the database from the server you're hosting your project on? I've worked with different databases (sqlite, postgres, oracle) that were not locally hosted on a same project. the issues I had at the time were always network related. If your configuration for the `sh` and `mysql` connections in the config file are okay, then the likely culprit is the connection/link itself rather than laravel's ORM. – IGP Jan 24 '21 at 16:14
  • _Have you checked if you can actually access the database from the server you're hosting your project on?_ Assuming I duplicated both connections (`sh` & `mysql`) on each server - yes, I can switch to either server (IP/host) via ENV and it works. I can even access the connections remotely even if my webserver is on my local machine (so webserver on a basic MAMP localhost, but `sh` and `mysql` is on a remote [but single IP] server) So the connections technically work - it's when I try to use different IPs for each is where it begins to act up. – nargalzius Jan 25 '21 at 00:50