This question already has answers here:
How to use multiple databases in Laravel
(7 answers)
Closed 5 years ago.
I am developing a application with multiple database access and I want to have PHPUnit tests with this. My current approache is to have in the config\databases.php multiple connections (mysql, mysql2, mysql3) so I can have in the env file a different access for all of them. Because of this, the models have the $connection variable defined. In my first feature test I want to access a page and just see the data that I am providing in my factory, so just to get things started. In my phpunit.xml file I have specified the DB_CONNECTION to be sqlite and for each of the MySql setting to have the value=":memory:".
LATER EDIT
<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE_1" value=":memory:"/>
<env name="DB_DATABASE_2" value=":memory:"/>
<env name="DB_DATABASE_3" value=":memory:"/>
</php>
So above you can find the relevant code from PHPUnit.
.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=db1
DB_USERNAME=xxx
DB_PASSWORD=xxx
DB_HOST_2=127.0.0.1
DB_PORT_2=3306
DB_DATABASE_2=db2
DB_USERNAME_2=xxx
DB_PASSWORD_2=xxx
DB_HOST_2=127.0.0.1
DB_PORT_2=3306
DB_DATABASE_3=db3
DB_USERNAME_3=xxx
DB_PASSWORD_3=xxx
The problem that I have is the fact that when I run the tests, i have this error -> PDOException: SQLSTATE[HY000] [1049] Unknown database ':memory:'.
So somehow Laravel is not parsing the memory value. Any suggestion will be mush appreciated.
Thank you
I was having the same issue, but I got things working with some help from Adam Wathan on Twitter.
Here's what I did:
phpunit.xml:
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="DB_CONNECTION_ACTIVITY_LOG" value="sqlite"/>
<env name="DB_DATABASE_ACTIVITY_LOG" value=":memory:"/>
config/database.php:
'sqlite' => [
'driver' => 'sqlite',
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
],
'mysql' => [
'driver' => env('DB_CONNECTION', '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', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
'mysql-activity-log' => [
'driver' => env('DB_CONNECTION_ACTIVITY_LOG', 'mysql'),
'host' => env('DB_HOST_ACTIVITY_LOG', '127.0.0.1'),
'port' => env('DB_PORT_ACTIVITY_LOG', '3306'),
'database' => env('DB_DATABASE_ACTIVITY_LOG', 'forge'),
'username' => env('DB_USERNAME_ACTIVITY_LOG', 'forge'),
'password' => env('DB_PASSWORD_ACTIVITY_LOG', ''),
'unix_socket' => env('DB_SOCKET_ACTIVITY_LOG', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
.env:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=my-app
DB_USERNAME=root
DB_PASSWORD=
DB_CONNECTION_ACTIVITY_LOG=mysql-activity-log
DB_HOST_ACTIVITY_LOG=127.0.0.1
DB_PORT_ACTIVITY_LOG=3306
DB_DATABASE_ACTIVITY_LOG=my-app
DB_USERNAME_ACTIVITY_LOG=root
DB_PASSWORD_ACTIVITY_LOG=
Also, for anyone not up to the point of the PDOException, make sure to set the connections in your migrations/models, too.
database/migrations/my_migration.php:
Schema::connection(env('DB_CONNECTION_ACTIVITY_LOG', 'mysql'))->create(...);
app/MyModel.php:
class MyModel extends Model
{
public function __construct($attributes = [])
{
parent::__construct($attributes);
$this->connection = config('app.env') === 'testing' ? 'sqlite' : 'mysql-activity-log';
}
...
}
Hard to decode where you actually put the :memory: value.
In the phpunit.xml <php> section, this should be sufficient (assuming you haven't modified the sqlite connection):
<php>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
</php>
To resolve a similar problem, I used a trait on the Model classes.
In my phpunit.xml I have this code
<env name="DB_CONNECTION" value="sqlite_testing"/>
<env name="DB_DATABASE" value=":memory:"/>```
In my config/database.php file I have connections set up for each of the databases, and a sqlite_testing connection set up for testing
'sqlite_testing' => [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
],
'mysql_connection_a' => [
'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', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
'mysql_connection_b' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE_B', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
'mysql_connection_c' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE_C', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
I then create a trait for each of my connections to set the connection and include them in the relevant models. e.g. if the User model needed to use mysql_connection_a I would use ConnectionATrait in the model
use App\Traits\ConnectionATrait;
class User extends Authenticatable
{
use Notifiable, ConnectionATrait;
The trait would then look like this
trait ConnectionATrait
{
/**
* The database table used by the model.
*
* #var string
*/
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
if (env('APP_ENV') != 'testing') {
$this->connection = 'mysql_connection_a';
}else{
$this->connection = 'sqlite_testing';
}
}
}
If you use migrations in your tests I also had to do a similar approach in the migration files and use a trait for each connection.
For the mysql_connection_a I create a trait that looks likes below that overrides the getConnection method:
trait ConnectionAConnectionTrait
{
/**
* Get the migration connection name.
*
* #return string
*/
public function getConnection()
{
if (env('APP_ENV') != 'testing') {
return 'mysql_connection_a';
}
return 'sqlite_testing';
}
}
Then in the migration it would look like this
use Database\migrations\traits\ConnectionAConnectionTrait;
class CreateUsersTable extends Migration {
use ConnectionAConnectionTrait;
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::connection($this->getConnection())
->create('users', function(Blueprint $table)
{
Related
The problem
I am using Laravel 8.83.23
I have schema dump from squashed migrations in database\schema\mysql-schema.dump
tests are running above test database, as in database.php
'testing' => [
'driver' => 'mysql',
'host' => env('DB_TEST_HOST', '127.0.0.1'),
'port' => env('DB_TEST_PORT', '3306'),
'database' => env('DB_TEST_DATABASE', 'forge'),
'username' => env('DB_TEST_USERNAME', 'forge'),
'password' => env('DB_TEST_PASSWORD', ''),
],
Before I squashed migrations, my test cases only used DatabaseMigrations trait, and the test database was recreated every time and all worked, example of test class:
class SystemControllerTest extends TestCase
{
use WithFaker;
use DatabaseMigrations;
/**
* #var User
*/
private $user;
public function setUp(): void
{
parent::setUp();
//create roles and data
$this->seed(RoleAndPermissionSeeder::class);
... etc
the migrations were found and executed, recreating the database
then, I squashed the migrations, so all migrations got deleted, and I got database\schema\mysql-schema.dump
php artisan migrate works as expected through command line, creating full database schemas from the dump (it finds it)
tests however no longer work, as there is an error
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'cinema_test.roles' doesn't exist (SQL: delete from `roles`)
when I check the sql test database after the test runs, it is empty (only table migrations gets created there, and it is empty)
this error persists even when I call artisan migrate in the test's setup:
public function setUp(): void
{
parent::setUp();
Artisan::call('migrate', array(
'--database' => 'testing',
'--force' => true));
//it crashes here
$this->seed(RoleAndPermissionSeeder::class);
RoleAndPermissionSeeder just operates with the sql tables, which do not exist, hence the error
I even tried DatabaseMigrations and DatabaseTransactions and RefreshDatabase traits, without any success
how do I populate the database data? There is no way for me to read the output of the Artisan::call('migrate') command, so I do not know what is happening there
return code of Artisan::call('migrate') is 0
is there maybe some setup I am missing?
I have finally figured this out.
The reason for the problem
The problem was in incorrect setup of the testing environment. I have not discovered the exact reason, but I figured out how to setup the testing environment so that the dump would be found and loaded.
How I hunt down the bug
This describes my steps on how I found a way to fix this.
In database.php I have copied testing database instead of normal one
in database.php I had the main database connection:
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'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', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => false,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
and the testing connection
'testing' => [
'driver' => 'mysql',
'host' => env('DB_TEST_HOST', '127.0.0.1'),
'port' => env('DB_TEST_PORT', '3306'),
'database' => env('DB_TEST_DATABASE', 'forge'),
'username' => env('DB_TEST_USERNAME', 'forge'),
'password' => env('DB_TEST_PASSWORD', ''),
],
I copied the testing connection data into a new mysql connection, just to see, whether on a command line I get same results
so, the file then looked like this
'mysql' => [
'url' => env('DATABASE_URL'),
'driver' => 'mysql',
'host' => env('DB_TEST_HOST', '127.0.0.1'),
'port' => env('DB_TEST_PORT', '3306'),
'database' => env('DB_TEST_DATABASE', 'forge'),
'username' => env('DB_TEST_USERNAME', 'forge'),
'password' => env('DB_TEST_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => false,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
/*'testing' => [
'driver' => 'mysql',
'host' => env('DB_TEST_HOST', '127.0.0.1'),
'port' => env('DB_TEST_PORT', '3306'),
'database' => env('DB_TEST_DATABASE', 'forge'),
'username' => env('DB_TEST_USERNAME', 'forge'),
'password' => env('DB_TEST_PASSWORD', ''),
],*/
on the console, I ran php artisan:migrate
the database dump was found and loaded
therefore, the dump was found in normal cases, but was not found, in testing cases
after some research, I changed the testing environment setup in phpunit.xml, I will explain it now
The file phpunit.xml
The phpunit.xml was as follows (not full file shown here):
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="TELESCOPE_ENABLED" value="false"/>
<env name="DB_CONNECTION" value="testing"/>
</php>
</phpunit>
so, we can see that the testing database connection is defined for unit tests
on web I found advice to set the database table only, instead of changing entire connection for tests, because it is easier
I tried such an approach, so the phpunit.xml became
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="TELESCOPE_ENABLED" value="false"/>
<env name="DB_DATABASE" value="cinema_test"/>
</php>
</phpunit>
I deleted the testing connection from database.php and related obsolete variables from .env file
this fixed the issue, and the dump file got loaded even in tests now
Conclusion
Although I have not figured out the real cause for the lavavel not loading the dump file, I have found a workaround which was to only change the database name for tests, instead of defining entirely new sql connection for testing pursposes. This solved the issue, and the database dump file gets loaded during tests now.
Seems like schema dumps can't be used for the in-memory database when testing
https://laravel.com/docs/9.x/migrations#squashing-migrations
May be able to do something like this
DB::unprepared(file_get_contents("path/file.sql"));
Would only try as a last resort, personally would want a test environment migration, also you should add a check for a test environment migration if you take this approach
I want to create a second database to act as a backup. I've been following another SO question solution and other websites which leads to the same thing. I've added a new DB connection in .env and configured it in database.php, cleared out the cache using php artisan config:cache. When I try to migrate using php artisan migrate --database=backup_literature_review_management, it says InvalidArgumentException, Database connection [backup_literature_review_management] not configured.
.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=literature_review_management
DB_USERNAME=root
DB_PASSWORD=
DB_CONNECTION_BACKUP=mysql
DB_HOST_BACKUP=127.0.0.1
DB_PORT_BACKUP=3306
DB_DATABASE_BACKUP=backup_literature_review_management
DB_USERNAME=root
DB_PASSWORD=
database.php
'connections' => [
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'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', ''),
'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'),
]) : [],
],
'mysql2' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST_BACKUP', '127.0.0.1'),
'port' => env('DB_PORT_BACKUP', '3306'),
'database' => env('DB_DATABASE_BACKUP', 'forge'),
'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'),
]) : [],
],
],
Cleared cache before migrating
php artisan config:cache
Trying to migrate to 2nd DB
php artisan migrate --database="backup_literature_review_management"
Error
InvalidArgumentException, Database connection [backup_literature_review_management] not configured.
You are calling the wrong connection. Use this:
php artisan migrate --database="mysql2"
You can also define the connection inside the migration file like:
Schema::connection('mysql2')->create('table', function (Blueprint $table) {...
With this last command, you can ommit --database="mysql2 in the migration command. I personally prefer to declare the connection inside the migration file.
Hope it helped!
I've setup Laravel 5.0 (that's a requirement) and set it up to use remote MySQL DB. All migrations and data interactions pass OK, but when trying to use Auth login POST, it fails with PDOException in Connector.php line 47:
SQLSTATE[HY000] [2002] No such file or directory, mentioning " PDO->__construct('mysql:host=localhost;dbname=homestead', 'homestead', 'secret', array('0', '2', '0', false, false))"
WHERE did it take that homestead DSN? Why does it skip the database.php config and .env config? If I try to add to .env some socket info (well it should not count here, right?) the socket path passes to the mentioned PDOException call stack, but hostname, login etc. NOT!
Confused. What am I doing wrong?
UPD
Here's the .env:
APP_ENV=local
APP_DEBUG=true
APP_KEY=SomeRandomString
DB_HOST=mysql.someremteohosting.net
DB_DATABASE=mydb
DB_USERNAME=myuser
DB_PASSWORD=mysuperpassword
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
And here's database.php:
<?php
return [
'fetch' => PDO::FETCH_CLASS,
'default' => 'mysql',
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'database' => storage_path().'/database.sqlite',
'prefix' => '',
],
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', 'localhost'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'strict' => false,
],
'pgsql' => [
'driver' => 'pgsql',
'host' => env('DB_HOST', 'localhost'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'schema' => 'public',
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'host' => env('DB_HOST', 'localhost'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'prefix' => '',
],
],
'migrations' => 'migrations',
],
];
Again, all migrations work. I can create, save and fetch objects in tinker without any problem. The only reference to homestead is in .env.example file.
Not sure what exactly was wrong here. The only place where I found 'homestead' in the app code was in .env.example. I deleted it an ran composer dump-autoload. After that it worked. Still surprised about laravel lurking into .example config without any reason.
I'm trying to write a migration for a new table.
public function up()
{
Schema::connection('fooBar')->create('tableOfThings', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
});
}
But when I run php artisan migration, I get the following error.
[InvalidArgumentException]
Database [fooBar] not configured.
This is what I have in config/database.php. You can see that I do infact have this DB configured.
'connections' => [
'foo_bar' => [
'driver' => 'mysql',
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', 3306),
'database' => 'fooBar',
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
// ...
]
Make sure the name of the configuration (not the database) is what you are passing to the connection.
DB::connection('foo_bar');
'connections' => [
THIS 'foo_bar' => [
'driver' => 'mysql',
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', 3306),
NOT THIS 'database' => 'fooBar',
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
// ...
]
You are trying to connect to the fooBar connection, but your connections array doesn't have that connection. It's called foo_bar instead.
Either change your connections configuration to fooBar or your schema connection call to foo_bar.
'connections' => [
'fooBar' => [ // Changed from foo_bar to fooBar.
// ...
],
// ...
]
How can I make sure that when I'm testing in Laravel with phpunit and for example I want to test my api route /test/1 it uses my test database?
I already made a test connection and changed my phpunit.xml file. I changed DB_CONNECTION to the test connection that I made.
<env name="DB_CONNECTION" value="mysql_testing"/>
But it when I make a post request I receive data from the development database?
In your Config/database.php file, what is the value of database in mysql_testing? It should be like this:
'mysql_testing' => [
'driver' => 'mysql',
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '3306'),
'database' => env('TEST_DB_DATABASE', 'db_testing'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'strict' => false,
'engine' => null,
],
And database db_testing must exists.
Inside one of your test files, add the following to output your current DB_CONNECTION:
dd(env('DB_CONNECTION'));
You should get mysql_testing in your test output if PHPUnit.xml is properly setup.