Laravel uses incorrect database when Http request is sent from another Laravel app
Asked Answered
L

3

8

I seem to be running into a weird issue. I am triggering a post request using the Http facade from a laravel app to another laravel app. Both these apps are linked to separate databases. When I try to trigger the same endpoint using postman, it works fine but when the request is triggered from the other laravel app, the recipient laravel app tries to use the sender app's database settings which doesn't work. I am currently using Xampp on Windows to host both these apps and the packages are the latest versions. Has anyone experinced a similar issue or could you suggest a solution?

The code is as follows: The service which sends the POST request (Sender App (1)):

Http::post("http://localhost/second_app/public/api/test", array(
                    'id' => 1,
);

The code which received the request (Receiver App (2)):

public function test(Request $request)
{
    $club = Club::find($request->id);
}

I get an error in the log file which says that it is trying to find the clubs table in the first_app database while it should be using the second_app database. I tried logging the configurations and the request. The request is quite large to be posted here but I verified that is received correctly. The code for this log is:

Log::info("Received Request", ['database' => ['driver' => config('database.default'), 'name' => config('database.connections.'.config('database.default').'.database')]]);

If the request is sent from Postman to second_app or from the second_app to itself (using the Http facade)

[2021-08-17 03:13:56] local.INFO: Received Request {"database":{"driver":"mysql","name":"second_app"}} 

If the request is sent from first_app to second_app using the Http facade

[2021-08-17 03:14:01] local.INFO: Received Request {"database":{"driver":"mysql","name":"first_app"}} 
[2021-08-17 03:14:01] local.INFO: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'first_app.projects' doesn't exist (SQL: select * from `projects` where `code` = ABC_01 limit 1) {"exception":"[object] (Illuminate\\Database\\QueryException(code: 42S02): SQLSTATE[42S02]: Base table or view not found: 1146 Table 'first_app.projects' doesn't exist (SQL: select * from `projects` where `code` = ABC_01 limit 1) at \\vendor\\laravel\\framework\\src\\Illuminate\\Database\\Connection.php:692)

Config for the apps is left the same as default. The .env is filled with the following details

#first_app
DB_DATABASE="first_app"

#second_app
DB_DATABASE="second_app"

UPDATE I tried with separate vhosts as well. firstapp.test and secondapp.test was set up and the document root was pointed to the public directories. The issue remained the same and the incorrect configuration was used when the request was sent from first_app but it works correctly (as it did earlier) when the request is sent internally from the second_app or from Postman

Lallation answered 16/8, 2021 at 12:48 Comment(8)
Possibly a caching issue? Try clearing your config cache.Berthold
did you specify database connection in your Clug model?Sheers
I tried clearing cache. It works well if I send the same request through Postman or using the Http client within the second app. It fails when the request is sent from the other laravel app. @NikhilVaghela no, it would be using the default connection for that app, so I didn't want it to have additional parameters to change in each model when the environment changes.Lallation
Can you confirm that the request from Guzzle is actually hitting your second_app, and not actually hitting the one which initiated the request?Berthold
Yes, I can confirm that. There is no such endpoint in the first app and even the log entry about the error turns up in the second app and not the first one. The request data is received correctly at the second app.Lallation
You should be able to configure the connection in config/database.php and override those settings in your .env file. Can you post the relevant connection details from both files so we can see if there's anything amiss? Don't forget to remove any sensitive credentials. Also, please post the exception from the log just in case it has some clues in it. Other than that, I am not sure what else to suggest with the limited information we have.Berthold
One other debugging note. You should be able to run config('database.connections') to get a list of the configured connections. Try running that in a normal controller/action and then run it in the controller/action mentioned in your example and hit it through Guzzle, and see if they match.Berthold
@Berthold I logged the connection configuration for the request to the OP and it is using a configuration that doesn't even exist on the second_app. The first_app configuration is only present on the .env file of the first_app. I still couldn't figure out where the second_app gets the first_app database configuration from...Is there a common cache store that Xampp uses for all apps?Lallation
L
8

I tried hard-coding the database.php configuration values in the recipient app instead of using the env helper and that fixed the issue. It looks like the env helper was causing a conflict by using the sender app's environment variables in the recipient app.

Lallation answered 18/8, 2021 at 15:23 Comment(1)
Thanks a lot, I faced same issue, finally got your answer.Landloper
G
3

I got the same issue.

and i fix it by caching all configuration using php artisan optimize.

hardcoding the database configuration also works

Gullet answered 5/9, 2022 at 7:17 Comment(1)
Running php artisan optimize works like a charm.Vicereine
H
3

Cause

Laravel uses vlucas/phpdotenv to manage environment variables. phpdotenv stores/retrieves variables from $_ENV, $_SERVER and optionally php 's getenv() and putenv().

The use of getenv() and putenv(). is discouraged by vlucas/phpdotenv because it is "not thread safe". These two php methods attached environment variables to the process, and not to the thread/request. Apache on Windows with mod_php as the php handler uses runs multiple virtual hosts under a single process as discussed extensively here.

By default, Laravel enables the putenv adapter in phpdotenv library.

So what is happening is:

  1. The first Laravel site sets the environment variables using putenv() via the Env facade.
  2. The first Laravel site makes and API call to the second Laravel Site running as a virtual host on the same server, in the same process.
  3. The second site sees that the DB_DATABASE has already been configured by reading getenv('DB_DATABASE') and so reads the value. Because put/getenv() are process level.
  4. The SQL query processor tries to connect to the wrong database.

Solution

Tell Laravel to disabled the PutenvAdapter env adapter. Add the following line to `bootstrap/app.php. Laravel will then fallback to using $_SERVER and $_ENV which are thread safe.

\Illuminate\Support\Env::disablePutenv();

To limit the scope to Windows + Apache + mod_php, use the following. I have not tested whether Linux builds of Apache are subject to the putenv virtual host cross contamination.

if(php_sapi_name() === 'apache2handler' && ($_SERVER['WINDIR'] ?? false)) {
    \Illuminate\Support\Env::disablePutenv();
}

Risks

Disabling the PutenvAdapter does limit the places Laravel can retrieve environment variables. Make sure to test your application thoroughly if you set environment variables in unusual ways.

Haematoxylon answered 2/3, 2023 at 17:58 Comment(1)
Explained like a true diagnostician, smart, thank you.Lifeline

© 2022 - 2024 — McMap. All rights reserved.