Completed
Push — master ( 9f2037...8625f3 )
by
unknown
23:57
created

Legacy::getRepository()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 1
dl 0
loc 15
rs 9.7666
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\Platforms\AbstractPlatform;
15
use Doctrine\DBAL\Schema\Schema;
16
use eZ\Publish\Core\Base\ServiceContainer;
17
use EzSystems\DoctrineSchema\API\Exception\InvalidConfigurationException;
18
use EzSystems\DoctrineSchema\Importer\SchemaImporter;
19
use Symfony\Component\DependencyInjection\ContainerBuilder;
20
use eZ\Publish\API\Repository\Tests\SetupFactory;
21
use eZ\Publish\API\Repository\Tests\IdManager;
22
use eZ\Publish\Core\Persistence\Legacy\Content\Type\MemoryCachingHandler as CachingContentTypeHandler;
23
use eZ\Publish\Core\Persistence\Legacy\Content\Language\CachingHandler as CachingLanguageHandler;
24
use Exception;
25
use eZ\Publish\Core\Repository\Values\User\UserReference;
26
use Symfony\Component\Filesystem\Filesystem;
27
use eZ\Publish\Core\Base\Container\Compiler;
28
29
/**
30
 * A Test Factory is used to setup the infrastructure for a tests, based on a
31
 * specific repository implementation to test.
32
 */
33
class Legacy extends SetupFactory
34
{
35
    /**
36
     * Data source name.
37
     *
38
     * @var string
39
     */
40
    protected static $dsn;
41
42
    /**
43
     * Root dir for IO operations.
44
     *
45
     * @var string
46
     */
47
    protected static $ioRootDir;
48
49
    /**
50
     * Database type (sqlite, mysql, ...).
51
     *
52
     * @var string
53
     */
54
    protected static $db;
55
56
    /**
57
     * Service container.
58
     *
59
     * @var \eZ\Publish\Core\Base\ServiceContainer
60
     */
61
    protected static $serviceContainer;
62
63
    /**
64
     * If the DB schema has already been initialized.
65
     *
66
     * @var bool
67
     */
68
    protected static $schemaInitialized = false;
69
70
    /**
71
     * Initial database data.
72
     *
73
     * @var array
74
     */
75
    protected static $initialData;
76
77
    protected $repositoryReference = 'ezpublish.api.repository';
78
79
    /**
80
     * Creates a new setup factory.
81
     */
82
    public function __construct()
83
    {
84
        self::$dsn = getenv('DATABASE');
85
        if (!self::$dsn) {
86
            // use sqlite in-memory by default (does not need special handling for paratest as it's per process)
87
            self::$dsn = 'sqlite://:memory:';
88
        } elseif (getenv('TEST_TOKEN') !== false) {
89
            // Using paratest, assuming dsn ends with db name here...
90
            self::$dsn .= '_' . getenv('TEST_TOKEN');
91
        }
92
93
        if ($repositoryReference = getenv('REPOSITORY_SERVICE_ID')) {
94
            $this->repositoryReference = $repositoryReference;
95
        }
96
97
        self::$db = preg_replace('(^([a-z]+).*)', '\\1', self::$dsn);
98
99
        if (!isset(self::$ioRootDir)) {
100
            self::$ioRootDir = $this->createTemporaryDirectory();
101
        }
102
    }
103
104
    /**
105
     * Creates a temporary directory and returns it.
106
     *
107
     * @return string
108
     * @throw \RuntimeException If the root directory can't be created
109
     */
110
    private function createTemporaryDirectory()
111
    {
112
        $tmpFile = tempnam(
113
            sys_get_temp_dir(),
114
            'ez_legacy_tests_' . time()
115
        );
116
        unlink($tmpFile);
117
118
        $fs = new Filesystem();
119
        $fs->mkdir($tmpFile);
120
121
        $varDir = $tmpFile . '/var';
122
        if ($fs->exists($varDir)) {
123
            $fs->remove($varDir);
124
        }
125
        $fs->mkdir($varDir);
126
127
        return $tmpFile;
128
    }
129
130
    /**
131
     * Returns a configured repository for testing.
132
     *
133
     * @param bool $initializeFromScratch if the back end should be initialized
134
     *                                    from scratch or re-used
135
     *
136
     * @return \eZ\Publish\API\Repository\Repository
137
     */
138
    public function getRepository($initializeFromScratch = true)
139
    {
140
        if ($initializeFromScratch || !self::$schemaInitialized) {
141
            $this->initializeSchema();
142
            $this->insertData();
143
        }
144
145
        $this->clearInternalCaches();
146
        $repository = $this->getServiceContainer()->get($this->repositoryReference);
147
148
        // Set admin user as current user by default
149
        $repository->setCurrentUser(new UserReference(14));
150
151
        return $repository;
152
    }
153
154
    /**
155
     * Returns a config value for $configKey.
156
     *
157
     * @param string $configKey
158
     *
159
     * @throws Exception if $configKey could not be found.
160
     *
161
     * @return mixed
162
     */
163
    public function getConfigValue($configKey)
164
    {
165
        return $this->getServiceContainer()->getParameter($configKey);
166
    }
167
168
    /**
169
     * Returns a repository specific ID manager.
170
     *
171
     * @return \eZ\Publish\API\Repository\Tests\IdManager
172
     */
173
    public function getIdManager()
174
    {
175
        return new IdManager\Php();
176
    }
177
178
    /**
179
     * Insert the database data.
180
     */
181
    public function insertData()
182
    {
183
        $data = $this->getInitialData();
184
        $handler = $this->getDatabaseHandler();
185
        $connection = $handler->getConnection();
186
        $dbPlatform = $connection->getDatabasePlatform();
187
        $this->cleanupVarDir($this->getInitialVarDir());
188
189
        foreach (array_reverse(array_keys($data)) as $table) {
190
            try {
191
                // Cleanup before inserting (using TRUNCATE for speed, however not possible to rollback)
192
                $connection->executeUpdate($dbPlatform->getTruncateTableSql($handler->quoteIdentifier($table)));
193
            } catch (DBALException | PDOException $e) {
194
                // Fallback to DELETE if TRUNCATE failed (because of FKs for instance)
195
                $connection->createQueryBuilder()->delete($table)->execute();
196
            }
197
        }
198
199
        foreach ($data as $table => $rows) {
200
            // Check that at least one row exists
201
            if (!isset($rows[0])) {
202
                continue;
203
            }
204
205
            $q = $handler->createInsertQuery();
206
            $q->insertInto($handler->quoteIdentifier($table));
207
208
            // Contains the bound parameters
209
            $values = array();
210
211
            // Binding the parameters
212
            foreach ($rows[0] as $col => $val) {
213
                $q->set(
214
                    $handler->quoteIdentifier($col),
215
                    $q->bindParam($values[$col])
216
                );
217
            }
218
219
            $stmt = $q->prepare();
220
221
            foreach ($rows as $row) {
222
                try {
223
                    // This CANNOT be replaced by:
224
                    // $values = $row
225
                    // each $values[$col] is a PHP reference which should be
226
                    // kept for parameters binding to work
227
                    foreach ($row as $col => $val) {
228
                        $values[$col] = $val;
229
                    }
230
231
                    $stmt->execute();
232
                } catch (Exception $e) {
233
                    echo "$table ( ", implode(', ', $row), " )\n";
234
                    throw $e;
235
                }
236
            }
237
        }
238
239
        $this->applyStatements($this->getPostInsertStatements());
240
    }
241
242
    protected function getInitialVarDir()
243
    {
244
        return __DIR__ . '/../../../../../../var';
245
    }
246
247
    protected function cleanupVarDir($sourceDir)
248
    {
249
        $fs = new Filesystem();
250
        $varDir = self::$ioRootDir . '/var';
251
        if ($fs->exists($varDir)) {
252
            $fs->remove($varDir);
253
        }
254
        $fs->mkdir($varDir);
255
        $fs->mirror($sourceDir, $varDir);
256
    }
257
258
    /**
259
     * CLears internal in memory caches after inserting data circumventing the
260
     * API.
261
     */
262
    protected function clearInternalCaches()
263
    {
264
        /** @var $handler \eZ\Publish\Core\Persistence\Legacy\Handler */
265
        $handler = $this->getServiceContainer()->get('ezpublish.spi.persistence.legacy');
266
267
        $contentLanguageHandler = $handler->contentLanguageHandler();
268
        if ($contentLanguageHandler instanceof CachingLanguageHandler) {
269
            $contentLanguageHandler->clearCache();
270
        }
271
272
        $contentTypeHandler = $handler->contentTypeHandler();
273
        if ($contentTypeHandler instanceof CachingContentTypeHandler) {
274
            $contentTypeHandler->clearCache();
275
        }
276
277
        /** @var $cachePool \Psr\Cache\CacheItemPoolInterface */
278
        $cachePool = $this->getServiceContainer()->get('ezpublish.cache_pool');
279
280
        $cachePool->clear();
281
    }
282
283
    /**
284
     * Returns statements to be executed after data insert.
285
     *
286
     * @return string[]
287
     */
288
    protected function getPostInsertStatements()
289
    {
290
        if (self::$db === 'pgsql') {
291
            $setvalPath = __DIR__ . '/../../../../Core/Persistence/Legacy/Tests/_fixtures/setval.pgsql.sql';
292
293
            return array_filter(preg_split('(;\\s*$)m', file_get_contents($setvalPath)));
294
        }
295
296
        return array();
297
    }
298
299
    /**
300
     * Returns the initial database data.
301
     *
302
     * @return array
303
     */
304
    protected function getInitialData()
305
    {
306
        if (!isset(self::$initialData)) {
307
            self::$initialData = include __DIR__ . '/../../../../Core/Repository/Tests/Service/Integration/Legacy/_fixtures/clean_ezdemo_47_dump.php';
308
        }
309
310
        return self::$initialData;
311
    }
312
313
    /**
314
     * Initializes the database schema.
315
     */
316
    protected function initializeSchema()
317
    {
318
        if (!self::$schemaInitialized) {
319
            $this->createSchema(
320
                dirname(__DIR__, 5) .
321
                '/Bundle/EzPublishCoreBundle/Resources/config/storage/legacy/schema.yaml'
322
            );
323
324
            self::$schemaInitialized = true;
325
        }
326
    }
327
328
    /**
329
     * Import database schema from Doctrine Schema Yaml configuration file.
330
     *
331
     * @param string $schemaFilePath Yaml schema configuration file path
332
     *
333
     * @throws \Doctrine\DBAL\ConnectionException
334
     */
335
    private function createSchema(string $schemaFilePath)
336
    {
337
        if (!file_exists($schemaFilePath)) {
338
            throw new \RuntimeException("The schema file path {$schemaFilePath} does not exist");
339
        }
340
341
        $connection = $this->getDatabaseConnection();
342
        $connection->beginTransaction();
343
        $importer = new SchemaImporter();
344
        try {
345
            $databasePlatform = $connection->getDatabasePlatform();
346
            $schema = $importer->importFromFile($schemaFilePath);
347
            $statements = array_merge(
348
                $this->getDropSqlStatementsForExistingSchema(
349
                    $schema,
350
                    $databasePlatform,
351
                    $connection
352
                ),
353
                // generate schema DDL queries
354
                $schema->toSql($databasePlatform)
355
            );
356
357
            foreach ($statements as $statement) {
358
                $connection->exec($statement);
359
            }
360
361
            $connection->commit();
362
        } catch (InvalidConfigurationException | DBALException $e) {
363
            $connection->rollBack();
364
            throw new \RuntimeException($e->getMessage(), 1, $e);
365
        }
366
    }
367
368
    /**
369
     * @param \Doctrine\DBAL\Schema\Schema $newSchema
370
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $databasePlatform
371
     * @param \Doctrine\DBAL\Connection $connection
372
     *
373
     * @return string[]
374
     */
375
    protected function getDropSqlStatementsForExistingSchema(
376
        Schema $newSchema,
377
        AbstractPlatform $databasePlatform,
378
        Connection $connection
379
    ): array {
380
        $existingSchema = $connection->getSchemaManager()->createSchema();
381
        $statements = [];
382
        // reverse table order for clean-up (due to FKs)
383
        $tables = array_reverse($newSchema->getTables());
384
        // cleanup pre-existing database
385
        foreach ($tables as $table) {
386
            if ($existingSchema->hasTable($table->getName())) {
387
                $statements[] = $databasePlatform->getDropTableSQL($table);
388
            }
389
        }
390
391
        return $statements;
392
    }
393
394
    /**
395
     * Applies the given SQL $statements to the database in use.
396
     *
397
     * @param array $statements
398
     */
399
    protected function applyStatements(array $statements)
400
    {
401
        foreach ($statements as $statement) {
402
            $this->getDatabaseHandler()->exec($statement);
403
        }
404
    }
405
406
    // ************* Setup copied and refactored from common.php ************
407
408
    /**
409
     * Returns the database schema as an array of SQL statements.
410
     *
411
     * @return string[]
412
     */
413
    protected function getSchemaStatements()
414
    {
415
        $schemaPath = __DIR__ . '/../../../../Core/Persistence/Legacy/Tests/_fixtures/schema.' . self::$db . '.sql';
416
417
        return array_filter(preg_split('(;\\s*$)m', file_get_contents($schemaPath)));
418
    }
419
420
    /**
421
     * Returns the database handler from the service container.
422
     *
423
     * @return \eZ\Publish\Core\Persistence\Doctrine\ConnectionHandler
424
     */
425
    protected function getDatabaseHandler()
426
    {
427
        return $this->getServiceContainer()->get('ezpublish.api.storage_engine.legacy.dbhandler');
428
    }
429
430
    /**
431
     * Returns the raw database connection from the service container.
432
     *
433
     * @return \Doctrine\DBAL\Connection
434
     */
435
    private function getDatabaseConnection(): Connection
436
    {
437
        return $this->getServiceContainer()->get('ezpublish.persistence.connection');
438
    }
439
440
    /**
441
     * Returns the service container used for initialization of the repository.
442
     *
443
     * @return \eZ\Publish\Core\Base\ServiceContainer
444
     */
445
    public function getServiceContainer()
446
    {
447
        if (!isset(self::$serviceContainer)) {
448
            $config = include __DIR__ . '/../../../../../../config.php';
449
            $installDir = $config['install_dir'];
450
451
            /** @var \Symfony\Component\DependencyInjection\ContainerBuilder $containerBuilder */
452
            $containerBuilder = include $config['container_builder_path'];
453
454
            /* @var \Symfony\Component\DependencyInjection\Loader\YamlFileLoader $loader */
455
            $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...
456
            $loader->load('tests/integration_legacy.yml');
457
458
            $this->externalBuildContainer($containerBuilder);
459
460
            $containerBuilder->setParameter(
461
                'legacy_dsn',
462
                self::$dsn
463
            );
464
465
            $containerBuilder->setParameter(
466
                'io_root_dir',
467
                self::$ioRootDir . '/' . $containerBuilder->getParameter('storage_dir')
468
            );
469
470
            $containerBuilder->addCompilerPass(new Compiler\Search\SearchEngineSignalSlotPass('legacy'));
471
            $containerBuilder->addCompilerPass(new Compiler\Search\FieldRegistryPass());
472
473
            // load overrides just before creating test Container
474
            $loader->load('tests/override.yml');
475
476
            self::$serviceContainer = new ServiceContainer(
477
                $containerBuilder,
478
                $installDir,
479
                $config['cache_dir'],
480
                true,
481
                true
482
            );
483
        }
484
485
        return self::$serviceContainer;
486
    }
487
488
    /**
489
     * This is intended to be used from external repository in order to
490
     * enable container customization.
491
     *
492
     * @param \Symfony\Component\DependencyInjection\ContainerBuilder $containerBuilder
493
     */
494
    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...
495
    {
496
        // Does nothing by default
497
    }
498
499
    /**
500
     * Get the Database name.
501
     *
502
     * @return string
503
     */
504
    public function getDB()
505
    {
506
        return self::$db;
507
    }
508
}
509