Skip to content

Commit acdc38e

Browse files
committed
feat: implement caching for PostgreSQL
1 parent 773649c commit acdc38e

File tree

11 files changed

+321
-0
lines changed

11 files changed

+321
-0
lines changed

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ RUN apt-get update \
88
unzip \
99
wget \
1010
default-mysql-client \
11+
postgresql-client \
1112
&& docker-php-ext-install \
1213
pdo_mysql \
1314
pdo_pgsql \

doc/configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ liip_test_fixtures:
1717
- `cache_db`: an array with a storage as key and a service as value, examples :
1818
- `sqlite: 'Liip\TestFixturesBundle\Services\DatabaseBackup\SqliteDatabaseBackup'`
1919
- `mysql: 'Liip\TestFixturesBundle\Services\DatabaseBackup\MysqlDatabaseBackup'`
20+
- `pgsql: 'Liip\TestFixturesBundle\Services\DatabaseBackup\PgsqlDatabaseBackup'`
2021
- `mongodb: 'Liip\TestFixturesBundle\Services\DatabaseBackup\MongodbDatabaseBackup'`
2122

2223
« [Installation](./installation.md) • [Database](./database.md) »

doc/database.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ For example:
134134
liip_test_fixtures:
135135
cache_db:
136136
mysql: 'Liip\TestFixturesBundle\Services\DatabaseBackup\MysqlDatabaseBackup'
137+
pgsql: 'Liip\TestFixturesBundle\Services\DatabaseBackup\PgsqlDatabaseBackup'
137138
mongodb: 'Liip\TestFixturesBundle\Services\DatabaseBackup\MongodbDatabaseBackup'
138139
phpcr: ...
139140
db2: ...
@@ -142,6 +143,8 @@ liip_test_fixtures:
142143

143144
**Attention: `Liip\TestFixturesBundle\Services\DatabaseBackup\MysqlDatabaseBackup` requires `mysql-client` installed on server.**
144145

146+
**Attention: `Liip\TestFixturesBundle\Services\DatabaseBackup\PgsqlDatabaseBackup` requires `postgresql-client` installed on server.**
147+
145148
**Attention: `Liip\TestFixturesBundle\Services\DatabaseBackup\MongodbDatabaseBackup` requires `mongodb-clients` installed on server.**
146149

147150
### Load fixtures ([↑](#methods))

src/Resources/config/database_tools.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
<argument type="service" id="Liip\TestFixturesBundle\Services\FixturesLoaderFactory" />
2525
</service>
2626

27+
<service id="Liip\TestFixturesBundle\Services\DatabaseBackup\PgsqlDatabaseBackup" public="true">
28+
<argument type="service" id="service_container" />
29+
<argument type="service" id="Liip\TestFixturesBundle\Services\FixturesLoaderFactory" />
30+
</service>
31+
2732
<service id="Liip\TestFixturesBundle\Services\DatabaseBackup\MongodbDatabaseBackup" public="true">
2833
<argument type="service" id="service_container" />
2934
<argument type="service" id="Liip\TestFixturesBundle\Services\MongoDBFixturesLoaderFactory" />
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Liip/TestFixturesBundle
7+
*
8+
* (c) Lukas Kahwe Smith <[email protected]>
9+
*
10+
* This source file is subject to the MIT license that is bundled
11+
* with this source code in the file LICENSE.
12+
*/
13+
14+
namespace Liip\TestFixturesBundle\Services\DatabaseBackup;
15+
16+
use Doctrine\Common\DataFixtures\Executor\AbstractExecutor;
17+
use Doctrine\Common\DataFixtures\ProxyReferenceRepository;
18+
use Doctrine\ORM\EntityManager;
19+
20+
/**
21+
* @author Nikita Slutsky <[email protected]>
22+
*/
23+
final class PgsqlDatabaseBackup extends AbstractDatabaseBackup
24+
{
25+
public function getBackupFilePath(): string
26+
{
27+
$cacheDir = $this->container->getParameter('kernel.cache_dir');
28+
$hash = md5(serialize($this->metadatas).serialize($this->classNames));
29+
30+
return $cacheDir.'/test_postgresql_'.$hash.'.tar';
31+
}
32+
33+
public function isBackupActual(): bool
34+
{
35+
$backupFileName = $this->getBackupFilePath();
36+
$backupReferenceFileName = $backupFileName.'.ser';
37+
38+
return file_exists($backupFileName)
39+
&& file_exists($backupReferenceFileName)
40+
&& $this->isBackupUpToDate($backupFileName);
41+
}
42+
43+
public function backup(AbstractExecutor $executor): void
44+
{
45+
/** @var ProxyReferenceRepository $referenceRepository */
46+
$referenceRepository = $executor->getReferenceRepository();
47+
48+
/** @var EntityManager $em */
49+
$em = $referenceRepository->getManager();
50+
51+
$connection = $em->getConnection();
52+
$params = $connection->getParams();
53+
54+
$referenceRepository->save($this->getBackupFilePath());
55+
exec($this->createCommand('pg_dump --format=t', $params).' > '.$this->getBackupFilePath());
56+
}
57+
58+
public function restore(AbstractExecutor $executor, array $excludedTables = []): void
59+
{
60+
/** @var ProxyReferenceRepository $referenceRepository */
61+
$referenceRepository = $executor->getReferenceRepository();
62+
63+
/** @var EntityManager $em */
64+
$em = $referenceRepository->getManager();
65+
66+
$connection = $em->getConnection();
67+
$params = $connection->getParams();
68+
69+
$this->executeQuery($connection, 'SET session_replication_role = \'replica\';');
70+
exec($this->createCommand('pg_restore --format=t --clean', $params).' '.$this->getBackupFilePath());
71+
$this->executeQuery($connection, 'SET session_replication_role = \'origin\';');
72+
$referenceRepository->load($this->getBackupFilePath());
73+
}
74+
75+
protected function getReferenceBackup(): string
76+
{
77+
return file_get_contents($this->getBackupFilePath());
78+
}
79+
80+
private function createCommand(string $command, array $params): string
81+
{
82+
// doctrine-bundle >= 2.2
83+
if (isset($params['primary'])) {
84+
$params = $params['primary'];
85+
}
86+
// doctrine-bundle < 2.2
87+
elseif (isset($params['master'])) {
88+
$params = $params['master'];
89+
}
90+
91+
$command .= isset($params['dbname']) && $params['dbname'] ? ' --dbname='.$params['dbname'] : '';
92+
$command .= isset($params['host']) && $params['host'] ? ' --host='.$params['host'] : '';
93+
$command .= isset($params['port']) && $params['port'] ? ' --port='.$params['port'] : '';
94+
$command .= isset($params['user']) && $params['user'] ? ' --username='.$params['user'] : '';
95+
96+
if (isset($params['password']) && $params['password']) {
97+
$command = 'PGPASSWORD='.$params['password'].' '.$command.' --no-password';
98+
}
99+
100+
return $command;
101+
}
102+
103+
private function executeQuery($connection, string $query): void
104+
{
105+
if (method_exists($connection, 'executeQuery')) {
106+
$connection->executeQuery($query);
107+
} else {
108+
$connection->query($query);
109+
}
110+
}
111+
}

tests/AppConfig/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ liip_test_fixtures:
1010
cache_db:
1111
sqlite: 'Liip\TestFixturesBundle\Services\DatabaseBackup\SqliteDatabaseBackup'
1212
mysql: 'Liip\TestFixturesBundle\Services\DatabaseBackup\MysqlDatabaseBackup'
13+
pgsql: 'Liip\TestFixturesBundle\Services\DatabaseBackup\PgsqlDatabaseBackup'
1314
mongodb: 'Liip\TestFixturesBundle\Services\DatabaseBackup\MongodbDatabaseBackup'
1415

1516
services:
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Liip/TestFixturesBundle
7+
*
8+
* (c) Lukas Kahwe Smith <[email protected]>
9+
*
10+
* This source file is subject to the MIT license that is bundled
11+
* with this source code in the file LICENSE.
12+
*/
13+
14+
namespace Liip\Acme\Tests\AppConfigPgsqlCacheDb;
15+
16+
use Liip\Acme\Tests\AppConfigPgsql\AppConfigPgsqlKernel;
17+
use Symfony\Component\Config\Loader\LoaderInterface;
18+
use Symfony\Component\DependencyInjection\ContainerBuilder;
19+
20+
class AppConfigPgsqlKernelCacheDb extends AppConfigPgsqlKernel
21+
{
22+
/**
23+
* {@inheritdoc}
24+
*/
25+
public function getCacheDir(): string
26+
{
27+
return __DIR__.'/var/cache/';
28+
}
29+
30+
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
31+
{
32+
// Load the default file.
33+
parent::configureContainer($container, $loader);
34+
35+
// Load the file with specific configuration
36+
$loader->load(__DIR__.'/config.yml');
37+
}
38+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# inherits configuration from ../AppConfigPgsql/config.yml
2+
3+
liip_test_fixtures:
4+
cache_db:
5+
pgsql: 'Liip\TestFixturesBundle\Services\DatabaseBackup\PgsqlDatabaseBackup'

tests/Test/ConfigMysqlTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ protected function setUp(): void
8080
* Data fixtures.
8181
*
8282
* @group mysql
83+
* @group pgsql
8384
*/
8485
public function testLoadEmptyFixtures(): void
8586
{
@@ -93,6 +94,7 @@ public function testLoadEmptyFixtures(): void
9394

9495
/**
9596
* @group mysql
97+
* @group pgsql
9698
*/
9799
public function testLoadFixtures(): void
98100
{
@@ -133,6 +135,7 @@ public function testLoadFixtures(): void
133135

134136
/**
135137
* @group mysql
138+
* @group pgsql
136139
*/
137140
public function testAppendFixtures(): void
138141
{
@@ -244,6 +247,7 @@ public function testLoadFixturesAndExcludeFromPurge(): void
244247
* Doctrine\Common\DataFixtures\Purger\ORMPurger.
245248
*
246249
* @group mysql
250+
* @group pgsql
247251
*/
248252
public function testLoadFixturesAndPurge(): void
249253
{
@@ -308,6 +312,7 @@ public function testLoadFixturesAndPurge(): void
308312
* Use nelmio/alice.
309313
*
310314
* @group mysql
315+
* @group pgsql
311316
*/
312317
public function testLoadFixturesFiles(): void
313318
{
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Liip/TestFixturesBundle
7+
*
8+
* (c) Lukas Kahwe Smith <[email protected]>
9+
*
10+
* This source file is subject to the MIT license that is bundled
11+
* with this source code in the file LICENSE.
12+
*/
13+
14+
namespace Liip\Acme\Tests\Test;
15+
16+
use Doctrine\Common\Annotations\Annotation\IgnoreAnnotation;
17+
use Liip\Acme\Tests\App\Entity\User;
18+
use Liip\Acme\Tests\AppConfigPgsqlCacheDb\AppConfigPgsqlKernelCacheDb;
19+
20+
/**
21+
* Test PostgreSQL database with database caching enabled.
22+
*
23+
* The following tests require a connection to a PostgreSQL database,
24+
* they are disabled by default (see phpunit.xml.dist).
25+
*
26+
* In order to run them, you have to set the PostgreSQL connection
27+
* parameters in the Tests/AppConfigPgsql/config.yml file.
28+
*
29+
* Use Tests/AppConfigPgsql/AppConfigPgsqlKernelCacheDb.php instead of
30+
* Tests/App/AppKernel.php.
31+
* So it must be loaded in a separate process.
32+
*
33+
* @runTestsInSeparateProcesses
34+
*
35+
* @preserveGlobalState disabled
36+
*
37+
* @IgnoreAnnotation("group")
38+
*
39+
* @internal
40+
*/
41+
class ConfigPgsqlCacheDbTest extends ConfigPgsqlTest
42+
{
43+
public function testLoadFixturesAndExcludeFromPurge(): void
44+
{
45+
$this->markTestSkipped('This test is not an actual optimization for PostgreSQL.');
46+
}
47+
48+
/**
49+
* @group pgsql
50+
*/
51+
public function testLoadFixturesAndCheckBackup(): void
52+
{
53+
$this->assertTrue($this->databaseTool->isDatabaseCacheEnabled());
54+
55+
$this->databaseTool->loadFixtures([
56+
'Liip\Acme\Tests\App\DataFixtures\ORM\LoadUserData',
57+
]);
58+
59+
// Load data from database
60+
$users = $this->userRepository->findAll();
61+
62+
// Check that all User have been saved to database
63+
$this->assertCount(
64+
2,
65+
$users
66+
);
67+
68+
/** @var User $user1 */
69+
$user1 = $this->userRepository
70+
->findOneBy([
71+
'id' => 1,
72+
])
73+
;
74+
75+
$this->assertSame(
76+
77+
$user1->getEmail()
78+
);
79+
80+
// Store salt for later use
81+
$salt = $user1->getSalt();
82+
83+
// Clean database
84+
$this->databaseTool->loadFixtures();
85+
86+
$users = $this->userRepository->findAll();
87+
88+
// Check that all User have been removed from database
89+
$this->assertCount(
90+
0,
91+
$users
92+
);
93+
94+
// Load fixtures again
95+
$this->databaseTool->loadFixtures([
96+
'Liip\Acme\Tests\App\DataFixtures\ORM\LoadUserData',
97+
]);
98+
99+
$users = $this->userRepository->findAll();
100+
101+
// Check that all User have been loaded again in database
102+
$this->assertCount(
103+
2,
104+
$users
105+
);
106+
107+
$user1 = $this->userRepository
108+
->findOneBy([
109+
'id' => 1,
110+
])
111+
;
112+
113+
// Salt is a random string, if it's the same as before it means that the backup has been saved and loaded
114+
// successfully
115+
$this->assertSame(
116+
$salt,
117+
$user1->getSalt()
118+
);
119+
}
120+
121+
/**
122+
* @group pgsql
123+
*/
124+
public function testLoadFixturesCheckReferences(): void
125+
{
126+
$referenceRepository = $this->databaseTool->loadFixtures([
127+
'Liip\Acme\Tests\App\DataFixtures\ORM\LoadUserData',
128+
])->getReferenceRepository();
129+
130+
$this->assertCount(1, $referenceRepository->getReferences());
131+
132+
$referenceRepository = $this->databaseTool->loadFixtures([
133+
'Liip\Acme\Tests\App\DataFixtures\ORM\LoadUserData',
134+
'Liip\Acme\Tests\App\DataFixtures\ORM\LoadSecondUserData',
135+
])->getReferenceRepository();
136+
137+
$this->assertCount(2, $referenceRepository->getReferences());
138+
}
139+
140+
protected static function getKernelClass(): string
141+
{
142+
return AppConfigPgsqlKernelCacheDb::class;
143+
}
144+
}

0 commit comments

Comments
 (0)