Completed
Push — master ( a2ed6a...66582b )
by André
23:40 queued 05:20
created

Legacy::cleanupVarDir()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the Test Setup Factory base class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\API\Repository\Tests\SetupFactory;
10
11
use Doctrine\DBAL\Connection;
12
use Doctrine\DBAL\DBALException;
13
use Doctrine\DBAL\Driver\PDOException;
14
use Doctrine\DBAL\Schema\Schema;
15
use eZ\Publish\API\Repository\Tests\LegacySchemaImporter;
16
use eZ\Publish\Core\Base\ServiceContainer;
17
use Symfony\Component\DependencyInjection\ContainerBuilder;
18
use eZ\Publish\API\Repository\Tests\SetupFactory;
19
use eZ\Publish\API\Repository\Tests\IdManager;
20
use eZ\Publish\Core\Persistence\Legacy\Content\Type\MemoryCachingHandler as CachingContentTypeHandler;
21
use eZ\Publish\Core\Persistence\Legacy\Content\Language\CachingHandler as CachingLanguageHandler;
22
use Exception;
23
use eZ\Publish\Core\Repository\Values\User\UserReference;
24
use Symfony\Component\Filesystem\Filesystem;
25
use eZ\Publish\Core\Base\Container\Compiler;
26
27
/**
28
 * A Test Factory is used to setup the infrastructure for a tests, based on a
29
 * specific repository implementation to test.
30
 */
31
class Legacy extends SetupFactory
32
{
33
    /**
34
     * Data source name.
35
     *
36
     * @var string
37
     */
38
    protected static $dsn;
39
40
    /**
41
     * Root dir for IO operations.
42
     *
43
     * @var string
44
     */
45
    protected static $ioRootDir;
46
47
    /**
48
     * Database type (sqlite, mysql, ...).
49
     *
50
     * @var string
51
     */
52
    protected static $db;
53
54
    /**
55
     * Service container.
56
     *
57
     * @var \eZ\Publish\Core\Base\ServiceContainer
58
     */
59
    protected static $serviceContainer;
60
61
    /**
62
     * If the DB schema has already been initialized.
63
     *
64
     * @var bool
65
     */
66
    protected static $schemaInitialized = false;
67
68
    /**
69
     * Initial database data.
70
     *
71
     * @var array
72
     */
73
    protected static $initialData;
74
75
    protected $repositoryReference = 'ezpublish.api.repository';
76
77
    /**
78
     * Creates a new setup factory.
79
     */
80
    public function __construct()
81
    {
82
        self::$dsn = getenv('DATABASE');
83
        if (!self::$dsn) {
84
            // use sqlite in-memory by default (does not need special handling for paratest as it's per process)
85
            self::$dsn = 'sqlite://:memory:';
86
        } elseif (getenv('TEST_TOKEN') !== false) {
87
            // Using paratest, assuming dsn ends with db name here...
88
            self::$dsn .= '_' . getenv('TEST_TOKEN');
89
        }
90
91
        if ($repositoryReference = getenv('REPOSITORY_SERVICE_ID')) {
92
            $this->repositoryReference = $repositoryReference;
93
        }
94
95
        self::$db = preg_replace('(^([a-z]+).*)', '\\1', self::$dsn);
96
97
        if (!isset(self::$ioRootDir)) {
98
            self::$ioRootDir = $this->createTemporaryDirectory();
99
        }
100
    }
101
102
    /**
103
     * Creates a temporary directory and returns it.
104
     *
105
     * @return string
106
     * @throw \RuntimeException If the root directory can't be created
107
     */
108
    private function createTemporaryDirectory()
109
    {
110
        $tmpFile = tempnam(
111
            sys_get_temp_dir(),
112
            'ez_legacy_tests_' . time()
113
        );
114
        unlink($tmpFile);
115
116
        $fs = new Filesystem();
117
        $fs->mkdir($tmpFile);
118
119
        $varDir = $tmpFile . '/var';
120
        if ($fs->exists($varDir)) {
121
            $fs->remove($varDir);
122
        }
123
        $fs->mkdir($varDir);
124
125
        return $tmpFile;
126
    }
127
128
    /**
129
     * Returns a configured repository for testing.
130
     *
131
     * @param bool $initializeFromScratch if the back end should be initialized
132
     *                                    from scratch or re-used
133
     *
134
     * @return \eZ\Publish\API\Repository\Repository
135
     */
136
    public function getRepository($initializeFromScratch = true)
137
    {
138
        if ($initializeFromScratch || !self::$schemaInitialized) {
139
            $this->initializeSchema();
140
            $this->insertData();
141
        }
142
143
        $this->clearInternalCaches();
144
        /** @var \eZ\Publish\API\Repository\Repository $repository */
145
        $repository = $this->getServiceContainer()->get($this->repositoryReference);
146
147
        // Set admin user as current user by default
148
        $repository->getPermissionResolver()->setCurrentUserReference(
149
            new UserReference(14)
150
        );
151
152
        return $repository;
153
    }
154
155
    /**
156
     * Returns a config value for $configKey.
157
     *
158
     * @param string $configKey
159
     *
160
     * @throws Exception if $configKey could not be found.
161
     *
162
     * @return mixed
163
     */
164
    public function getConfigValue($configKey)
165
    {
166
        return $this->getServiceContainer()->getParameter($configKey);
167
    }
168
169
    /**
170
     * Returns a repository specific ID manager.
171
     *
172
     * @return \eZ\Publish\API\Repository\Tests\IdManager
173
     */
174
    public function getIdManager()
175
    {
176
        return new IdManager\Php();
177
    }
178
179
    /**
180
     * Insert the database data.
181
     */
182
    public function insertData()
183
    {
184
        $data = $this->getInitialData();
185
        $handler = $this->getDatabaseHandler();
186
        $connection = $handler->getConnection();
187
        $dbPlatform = $connection->getDatabasePlatform();
188
        $this->cleanupVarDir($this->getInitialVarDir());
189
190
        foreach (array_reverse(array_keys($data)) as $table) {
191
            try {
192
                // Cleanup before inserting (using TRUNCATE for speed, however not possible to rollback)
193
                $connection->executeUpdate($dbPlatform->getTruncateTableSql($handler->quoteIdentifier($table)));
194
            } catch (DBALException | PDOException $e) {
195
                // Fallback to DELETE if TRUNCATE failed (because of FKs for instance)
196
                $connection->createQueryBuilder()->delete($table)->execute();
197
            }
198
        }
199
200
        foreach ($data as $table => $rows) {
201
            // Check that at least one row exists
202
            if (!isset($rows[0])) {
203
                continue;
204
            }
205
206
            $q = $handler->createInsertQuery();
207
            $q->insertInto($handler->quoteIdentifier($table));
208
209
            // Contains the bound parameters
210
            $values = [];
211
212
            // Binding the parameters
213
            foreach ($rows[0] as $col => $val) {
214
                $q->set(
215
                    $handler->quoteIdentifier($col),
216
                    $q->bindParam($values[$col])
217
                );
218
            }
219
220
            $stmt = $q->prepare();
221
222
            foreach ($rows as $row) {
223
                try {
224
                    // This CANNOT be replaced by:
225
                    // $values = $row
226
                    // each $values[$col] is a PHP reference which should be
227
                    // kept for parameters binding to work
228
                    foreach ($row as $col => $val) {
229
                        $values[$col] = $val;
230
                    }
231
232
                    $stmt->execute();
233
                } catch (Exception $e) {
234
                    echo "$table ( ", implode(', ', $row), " )\n";
235
                    throw $e;
236
                }
237
            }
238
        }
239
240
        $this->applyStatements($this->getPostInsertStatements());
241
    }
242
243
    protected function getInitialVarDir()
244
    {
245
        return __DIR__ . '/../../../../../../var';
246
    }
247
248
    protected function cleanupVarDir($sourceDir)
249
    {
250
        $fs = new Filesystem();
251
        $varDir = self::$ioRootDir . '/var';
252
        if ($fs->exists($varDir)) {
253
            $fs->remove($varDir);
254
        }
255
        $fs->mkdir($varDir);
256
        $fs->mirror($sourceDir, $varDir);
257
    }
258
259
    /**
260
     * CLears internal in memory caches after inserting data circumventing the
261
     * API.
262
     */
263
    protected function clearInternalCaches()
264
    {
265
        /** @var $handler \eZ\Publish\Core\Persistence\Legacy\Handler */
266
        $handler = $this->getServiceContainer()->get('ezpublish.spi.persistence.legacy');
267
268
        $contentLanguageHandler = $handler->contentLanguageHandler();
269
        if ($contentLanguageHandler instanceof CachingLanguageHandler) {
270
            $contentLanguageHandler->clearCache();
271
        }
272
273
        $contentTypeHandler = $handler->contentTypeHandler();
274
        if ($contentTypeHandler instanceof CachingContentTypeHandler) {
275
            $contentTypeHandler->clearCache();
276
        }
277
278
        /** @var $cachePool \Psr\Cache\CacheItemPoolInterface */
279
        $cachePool = $this->getServiceContainer()->get('ezpublish.cache_pool');
280
281
        $cachePool->clear();
282
    }
283
284
    /**
285
     * Returns statements to be executed after data insert.
286
     *
287
     * @return string[]
288
     */
289
    protected function getPostInsertStatements()
290
    {
291
        if (self::$db === 'pgsql') {
292
            $setvalPath = __DIR__ . '/../../../../Core/Persistence/Legacy/Tests/_fixtures/setval.pgsql.sql';
293
294
            return array_filter(preg_split('(;\\s*$)m', file_get_contents($setvalPath)));
295
        }
296
297
        return [];
298
    }
299
300
    /**
301
     * Returns the initial database data.
302
     *
303
     * @return array
304
     */
305
    protected function getInitialData()
306
    {
307
        if (!isset(self::$initialData)) {
308
            self::$initialData = include __DIR__ . '/../../../../Core/Repository/Tests/Service/Integration/Legacy/_fixtures/test_data.php';
309
        }
310
311
        return self::$initialData;
312
    }
313
314
    /**
315
     * Initializes the database schema.
316
     *
317
     * @throws \Doctrine\DBAL\ConnectionException
318
     */
319
    protected function initializeSchema(): void
320
    {
321
        if (!self::$schemaInitialized) {
322
            $schemaImporter = new LegacySchemaImporter($this->getDatabaseConnection());
323
            $schemaImporter->importSchema(
324
                dirname(__DIR__, 5) .
325
                '/Bundle/EzPublishCoreBundle/Resources/config/storage/legacy/schema.yaml'
326
            );
327
328
            self::$schemaInitialized = true;
329
        }
330
    }
331
332
    /**
333
     * Applies the given SQL $statements to the database in use.
334
     *
335
     * @param array $statements
336
     */
337
    protected function applyStatements(array $statements)
338
    {
339
        foreach ($statements as $statement) {
340
            $this->getDatabaseHandler()->exec($statement);
341
        }
342
    }
343
344
    // ************* Setup copied and refactored from common.php ************
345
346
    /**
347
     * Returns the database handler from the service container.
348
     *
349
     * @return \eZ\Publish\Core\Persistence\Doctrine\ConnectionHandler
350
     */
351
    protected function getDatabaseHandler()
352
    {
353
        return $this->getServiceContainer()->get('ezpublish.api.storage_engine.legacy.dbhandler');
354
    }
355
356
    /**
357
     * Returns the raw database connection from the service container.
358
     *
359
     * @return \Doctrine\DBAL\Connection
360
     */
361
    private function getDatabaseConnection(): Connection
362
    {
363
        return $this->getServiceContainer()->get('ezpublish.persistence.connection');
364
    }
365
366
    /**
367
     * Returns the service container used for initialization of the repository.
368
     *
369
     * @return \eZ\Publish\Core\Base\ServiceContainer
370
     */
371
    public function getServiceContainer()
372
    {
373
        if (!isset(self::$serviceContainer)) {
374
            $config = include __DIR__ . '/../../../../../../config.php';
375
            $installDir = $config['install_dir'];
376
377
            /** @var \Symfony\Component\DependencyInjection\ContainerBuilder $containerBuilder */
378
            $containerBuilder = include $config['container_builder_path'];
379
380
            /* @var \Symfony\Component\DependencyInjection\Loader\YamlFileLoader $loader */
381
            $loader->load('search_engines/legacy.yml');
0 ignored issues
show
Bug introduced by
The variable $loader does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
382
            $loader->load('tests/integration_legacy.yml');
383
384
            $this->externalBuildContainer($containerBuilder);
385
386
            $containerBuilder->setParameter(
387
                'legacy_dsn',
388
                self::$dsn
389
            );
390
391
            $containerBuilder->setParameter(
392
                'io_root_dir',
393
                self::$ioRootDir . '/' . $containerBuilder->getParameter('storage_dir')
394
            );
395
396
            $containerBuilder->addCompilerPass(new Compiler\Search\FieldRegistryPass());
397
398
            // load overrides just before creating test Container
399
            $loader->load('tests/override.yml');
400
401
            self::$serviceContainer = new ServiceContainer(
402
                $containerBuilder,
403
                $installDir,
404
                $config['cache_dir'],
405
                true,
406
                true
407
            );
408
        }
409
410
        return self::$serviceContainer;
411
    }
412
413
    /**
414
     * This is intended to be used from external repository in order to
415
     * enable container customization.
416
     *
417
     * @param \Symfony\Component\DependencyInjection\ContainerBuilder $containerBuilder
418
     */
419
    protected function externalBuildContainer(ContainerBuilder $containerBuilder)
0 ignored issues
show
Unused Code introduced by
The parameter $containerBuilder is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
420
    {
421
        // Does nothing by default
422
    }
423
424
    /**
425
     * Get the Database name.
426
     *
427
     * @return string
428
     */
429
    public function getDB()
430
    {
431
        return self::$db;
432
    }
433
}
434