Passed
Push — extract-fixtures-and-assertion... ( 8de50a )
by Alexis
17:41 queued 07:43
created

FixturesLoader::getNameParameter()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 5.9256

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 16
ccs 6
cts 9
cp 0.6667
rs 8.8571
cc 5
eloc 8
nc 16
nop 1
crap 5.9256
1
<?php
2
3
/*
4
 * This file is part of the Liip/FunctionalTestBundle
5
 *
6
 * (c) Lukas Kahwe Smith <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Liip\FunctionalTestBundle\Utils;
13
14
use Symfony\Component\DependencyInjection\ContainerInterface;
15
use Symfony\Bridge\Doctrine\ManagerRegistry;
16
use Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader;
17
use Doctrine\Common\Persistence\ObjectManager;
18
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
19
use Doctrine\Common\DataFixtures\Executor\AbstractExecutor;
20
use Doctrine\Common\DataFixtures\ProxyReferenceRepository;
21
use Doctrine\DBAL\Connection;
22
use Doctrine\DBAL\Driver\PDOSqlite\Driver as SqliteDriver;
23
use Doctrine\DBAL\Platforms\MySqlPlatform;
24
use Doctrine\ORM\EntityManager;
25
use Doctrine\ORM\Tools\SchemaTool;
26
use Nelmio\Alice\Fixtures;
27
28
class FixturesLoader
29
{
30
    /** @var \Symfony\Component\DependencyInjection\ContainerInterface $container */
31
    private $container;
32
33
    /**
34
     * @var array
35
     */
36
    private static $cachedMetadatas = array();
37
38 36
    public function __construct(ContainerInterface $container)
39
    {
40 36
        $this->container = $container;
41 36
    }
42
43
    /**
44
     * @param string $type
45
     *
46
     * @return string
47
     */
48 36
    private function getExecutorClass($type)
49
    {
50 36
        return 'PHPCR' === $type && class_exists('Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor')
51 36
            ? 'Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor'
52 36
            : 'Doctrine\\Common\\DataFixtures\\Executor\\'.$type.'Executor';
53
    }
54
55
    /**
56
     * Get file path of the SQLite database.
57
     *
58
     * @param Connection $connection
59
     *
60
     * @return string $name
61
     */
62 31
    private function getNameParameter(Connection $connection)
63
    {
64 31
        $params = $connection->getParams();
65
66 31
        if (isset($params['master'])) {
67
            $params = $params['master'];
68
        }
69
70 31
        $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
71
72 31
        if (!$name) {
73
            throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
74
        }
75
76 31
        return $name;
77
    }
78
79
    /**
80
     * Purge SQLite database.
81
     *
82
     * @param ObjectManager $om
83
     * @param string        $omName The name of object manager to use
84
     */
85 31
    private function getCachedMetadatas(ObjectManager $om, $omName)
86
    {
87 31
        if (!isset(self::$cachedMetadatas[$omName])) {
88 6
            self::$cachedMetadatas[$omName] = $om->getMetadataFactory()->getAllMetadata();
89
            usort(self::$cachedMetadatas[$omName], function ($a, $b) { return strcmp($a->name, $b->name); });
90 6
        }
91
92 31
        return self::$cachedMetadatas[$omName];
93
    }
94
95
    /**
96
     * This function finds the time when the data blocks of a class definition
97
     * file were being written to, that is, the time when the content of the
98
     * file was changed.
99
     *
100
     * @param string $class The fully qualified class name of the fixture class to
101
     *                      check modification date on.
102
     *
103
     * @return \DateTime|null
104
     */
105 2
    protected function getFixtureLastModified($class)
106
    {
107 2
        $lastModifiedDateTime = null;
108
109 2
        $reflClass = new \ReflectionClass($class);
110 2
        $classFileName = $reflClass->getFileName();
111
112 2
        if (file_exists($classFileName)) {
113 2
            $lastModifiedDateTime = new \DateTime();
114 2
            $lastModifiedDateTime->setTimestamp(filemtime($classFileName));
115 2
        }
116
117 2
        return $lastModifiedDateTime;
118
    }
119
120
    /**
121
     * Determine if the Fixtures that define a database backup have been
122
     * modified since the backup was made.
123
     *
124
     * @param array  $classNames The fixture classnames to check
125
     * @param string $backup     The fixture backup SQLite database file path
126
     *
127
     * @return bool TRUE if the backup was made since the modifications to the
128
     *              fixtures; FALSE otherwise
129
     */
130 3
    protected function isBackupUpToDate(array $classNames, $backup)
131
    {
132 3
        $backupLastModifiedDateTime = new \DateTime();
133 3
        $backupLastModifiedDateTime->setTimestamp(filemtime($backup));
134
135 3
        foreach ($classNames as &$className) {
136 2
            $fixtureLastModifiedDateTime = $this->getFixtureLastModified($className);
137 2
            if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) {
138
                return false;
139
            }
140 3
        }
141
142 3
        return true;
143
    }
144
145
    /**
146
     * Copy SQLite backup file.
147
     *
148
     * @param ObjectManager            $om
149
     * @param string                   $executorClass
150
     * @param ProxyReferenceRepository $referenceRepository
151
     * @param string                   $backup              Path of the source file.
152
     * @param string                   $name                Path of the destination file.
153
     */
154 3
    private function copySqliteBackup($om, $executorClass,
155
                                      $referenceRepository, $backup, $name)
156
    {
157 3
        $om->flush();
158 3
        $om->clear();
159
160 3
        $this->preFixtureRestore($om, $referenceRepository);
161
162 3
        copy($backup, $name);
163
164 3
        $executor = new $executorClass($om);
165 3
        $executor->setReferenceRepository($referenceRepository);
166 3
        $executor->getReferenceRepository()->load($backup);
167
168 3
        $this->postFixtureRestore();
169
170 3
        return $executor;
171
    }
172
173
    /**
174
     * Purge database.
175
     *
176
     * @param ObjectManager            $om
177
     * @param string                   $type
178
     * @param int                      $purgeMode
179
     * @param string                   $executorClass
180
     * @param ProxyReferenceRepository $referenceRepository
181
     */
182 33
    private function purgeDatabase(ObjectManager $om, $type, $purgeMode,
183
                                   $executorClass,
184
                                   ProxyReferenceRepository $referenceRepository)
185
    {
186 33
        $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
187 33
        if ('PHPCR' === $type) {
188 1
            $purger = new $purgerClass($om);
189 1
            $initManager = $this->container->has('doctrine_phpcr.initializer_manager')
190 1
                ? $this->container->get('doctrine_phpcr.initializer_manager')
191 1
                : null;
192
193 1
            $executor = new $executorClass($om, $purger, $initManager);
194 1
        } else {
195 32
            $purger = new $purgerClass();
196 32
            if (null !== $purgeMode) {
197 1
                $purger->setPurgeMode($purgeMode);
198 1
            }
199
200 32
            $executor = new $executorClass($om, $purger);
201
        }
202
203 33
        $executor->setReferenceRepository($referenceRepository);
204 33
        $executor->purge();
205
206 33
        return $executor;
207
    }
208
209
    /**
210
     * Purge database.
211
     *
212
     * @param ObjectManager            $om
213
     * @param array                    $metadatas
214
     * @param string                   $executorClass
215
     * @param ProxyReferenceRepository $referenceRepository
216
     */
217 28
    private function createSqliteSchema(ObjectManager $om,
218
                                        $metadatas, $executorClass,
219
                                        ProxyReferenceRepository $referenceRepository)
220
    {
221
        // TODO: handle case when using persistent connections. Fail loudly?
222 28
        $schemaTool = new SchemaTool($om);
0 ignored issues
show
Compatibility introduced by
$om of type object<Doctrine\Common\Persistence\ObjectManager> is not a sub-type of object<Doctrine\ORM\EntityManagerInterface>. It seems like you assume a child interface of the interface Doctrine\Common\Persistence\ObjectManager to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
223 28
        $schemaTool->dropDatabase();
224 28
        if (!empty($metadatas)) {
225 28
            $schemaTool->createSchema($metadatas);
226 28
        }
227 28
        $this->postFixtureSetup();
228
229 28
        $executor = new $executorClass($om);
230 28
        $executor->setReferenceRepository($referenceRepository);
231 28
    }
232
233
    /**
234
     * Set the database to the provided fixtures.
235
     *
236
     * Drops the current database and then loads fixtures using the specified
237
     * classes. The parameter is a list of fully qualified class names of
238
     * classes that implement Doctrine\Common\DataFixtures\FixtureInterface
239
     * so that they can be loaded by the DataFixtures Loader::addFixture
240
     *
241
     * When using SQLite this method will automatically make a copy of the
242
     * loaded schema and fixtures which will be restored automatically in
243
     * case the same fixture classes are to be loaded again. Caveat: changes
244
     * to references and/or identities may go undetected.
245
     *
246
     * Depends on the doctrine data-fixtures library being available in the
247
     * class path.
248
     *
249
     * @param array  $classNames   List of fully qualified class names of fixtures to load
250
     * @param string $omName       The name of object manager to use
251
     * @param string $registryName The service id of manager registry to use
252
     * @param int    $purgeMode    Sets the ORM purge mode
253
     *
254
     * @return null|AbstractExecutor
255
     */
256 36
    public function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
257
    {
258
        /** @var ManagerRegistry $registry */
259 36
        $registry = $this->container->get($registryName);
260 36
        $om = $registry->getManager($omName);
261 36
        $type = $registry->getName();
262
263 36
        $executorClass = $this->getExecutorClass($type);
264 36
        $referenceRepository = new ProxyReferenceRepository($om);
265
266 36
        $cacheDriver = $om->getMetadataFactory()->getCacheDriver();
267
268 36
        if ($cacheDriver) {
269 36
            $cacheDriver->deleteAll();
270 36
        }
271
272 36
        if ('ORM' === $type) {
273 35
            $connection = $om->getConnection();
274 35
            if ($connection->getDriver() instanceof SqliteDriver) {
275 31
                $name = $this->getNameParameter($connection);
276 31
                $metadatas = $this->getCachedMetadatas($om, $omName);
277
278 31
                if ($this->container->getParameter('liip_functional_test.cache_sqlite_db')) {
279 5
                    $backup = $this->container->getParameter('kernel.cache_dir').'/test_'.md5(serialize($metadatas).serialize($classNames)).'.db';
280 5
                    if (file_exists($backup) && file_exists($backup.'.ser') && $this->isBackupUpToDate($classNames, $backup)) {
281 3
                        $executor = $this->copySqliteBackup($om,
282 3
                            $executorClass, $referenceRepository,
283 3
                            $backup, $name);
284
285 3
                        return $executor;
286
                    }
287 2
                }
288
289 28
                $this->createSqliteSchema($om, $metadatas,
290 28
                    $executorClass, $referenceRepository);
291 28
            }
292 32
        }
293
294 33
        if (empty($executor)) {
0 ignored issues
show
Bug introduced by
The variable $executor seems only to be defined at a later point. As such the call to empty() seems to always evaluate to true.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
295 33
            $executor = $this->purgeDatabase($om, $type, $purgeMode,
296 33
                $executorClass, $referenceRepository);
297 33
        }
298
299 33
        $loader = $this->getFixtureLoader($classNames);
300
301 33
        $executor->execute($loader->getFixtures(), true);
302
303 33
        if (isset($name) && isset($backup)) {
304 2
            $this->preReferenceSave($om, $executor, $backup);
305
306 2
            $executor->getReferenceRepository()->save($backup);
307 2
            copy($name, $backup);
308
309 2
            $this->postReferenceSave($om, $executor, $backup);
310 2
        }
311
312 33
        return $executor;
313
    }
314
315
    /**
316
     * Clean database.
317
     *
318
     * @param ManagerRegistry $registry
319
     * @param EntityManager   $om
320
     */
321 6
    private function cleanDatabase(ManagerRegistry $registry, EntityManager $om)
322
    {
323 6
        $connection = $om->getConnection();
324
325 6
        $mysql = ($registry->getName() === 'ORM'
326 6
            && $connection->getDatabasePlatform() instanceof MySqlPlatform);
327
328 6
        if ($mysql) {
329 1
            $connection->query('SET FOREIGN_KEY_CHECKS=0');
330 1
        }
331
332 6
        $this->loadFixtures(array());
333
334 6
        if ($mysql) {
335 1
            $connection->query('SET FOREIGN_KEY_CHECKS=1');
336 1
        }
337 6
    }
338
339
    /**
340
     * Locate fixture files.
341
     *
342
     * @param array $paths
343
     *
344
     * @return array $files
345
     */
346 6
    private function locateResources($paths)
347
    {
348 6
        $files = array();
349
350 6
        $kernel = $this->container->get('kernel');
351
352 6
        foreach ($paths as $path) {
353 6
            if ($path[0] !== '@' && file_exists($path) === true) {
354 1
                $files[] = $path;
355 1
                continue;
356
            }
357
358 5
            $files[] = $kernel->locateResource($path);
359 6
        }
360
361 6
        return $files;
362
    }
363
364
    /**
365
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
366
     * @param bool   $append
367
     * @param null   $omName
368
     * @param string $registryName
369
     *
370
     * @return array
371
     *
372
     * @throws \BadMethodCallException
373
     */
374 6
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine')
375
    {
376 6
        if (!class_exists('Nelmio\Alice\Fixtures')) {
377
            throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
378
        }
379
380
        /** @var ManagerRegistry $registry */
381 6
        $registry = $this->container->get($registryName);
382
        /** @var EntityManager $om */
383 6
        $om = $registry->getManager($omName);
384
385 6
        if ($append === false) {
386 6
            $this->cleanDatabase($registry, $om);
387 6
        }
388
389 6
        $files = $this->locateResources($paths);
390
391
        // Check if the Hautelook AliceBundle is registered and if yes, use it instead of Nelmio Alice
392 6
        $hautelookLoaderServiceName = 'hautelook_alice.fixtures.loader';
393 6
        if ($this->container->has($hautelookLoaderServiceName)) {
394 6
            $loaderService = $this->container->get($hautelookLoaderServiceName);
395 6
            $persisterClass = class_exists('Nelmio\Alice\ORM\Doctrine') ?
396 6
                'Nelmio\Alice\ORM\Doctrine' :
397 6
                'Nelmio\Alice\Persister\Doctrine';
398
399 6
            return $loaderService->load(new $persisterClass($om), $files);
400
        }
401
402
        return Fixtures::load($files, $om);
403
    }
404
405
    /**
406
     * Retrieve Doctrine DataFixtures loader.
407
     *
408
     * @param array $classNames
409
     *
410
     * @return Loader
411
     */
412 33
    protected function getFixtureLoader(array $classNames)
413
    {
414 33
        $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
415 33
            ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
416 33
            : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
417
                ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
418 33
                : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
419
420 33
        $loader = new $loaderClass($this->container);
421
422 33
        foreach ($classNames as $className) {
423 8
            $this->loadFixtureClass($loader, $className);
424 33
        }
425
426 33
        return $loader;
427
    }
428
429
    /**
430
     * Load a data fixture class.
431
     *
432
     * @param Loader $loader
433
     * @param string $className
434
     */
435 8
    protected function loadFixtureClass($loader, $className)
436
    {
437 8
        $fixture = new $className();
438
439 8
        if ($loader->hasFixture($fixture)) {
440 1
            unset($fixture);
441
442 1
            return;
443
        }
444
445 8
        $loader->addFixture($fixture);
446
447 8
        if ($fixture instanceof DependentFixtureInterface) {
448 1
            foreach ($fixture->getDependencies() as $dependency) {
449 1
                $this->loadFixtureClass($loader, $dependency);
450 1
            }
451 1
        }
452 8
    }
453
454
    /**
455
     * Callback function to be executed after Schema creation.
456
     * Use this to execute acl:init or other things necessary.
457
     */
458 28
    protected function postFixtureSetup()
459
    {
460 28
    }
461
462
    /**
463
     * Callback function to be executed after Schema restore.
464
     *
465
     * @return WebTestCase
466
     */
467 3
    protected function postFixtureRestore()
468
    {
469 3
    }
470
471
    /**
472
     * Callback function to be executed before Schema restore.
473
     *
474
     * @param ObjectManager            $manager             The object manager
475
     * @param ProxyReferenceRepository $referenceRepository The reference repository
476
     *
477
     * @return WebTestCase
478
     */
479 3
    protected function preFixtureRestore(ObjectManager $manager, ProxyReferenceRepository $referenceRepository)
0 ignored issues
show
Unused Code introduced by
The parameter $manager 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...
Unused Code introduced by
The parameter $referenceRepository 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...
480
    {
481 3
    }
482
483
    /**
484
     * Callback function to be executed after save of references.
485
     *
486
     * @param ObjectManager    $manager        The object manager
487
     * @param AbstractExecutor $executor       Executor of the data fixtures
488
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
489
     *
490
     * @return WebTestCase
491
     */
492 2
    protected function postReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
0 ignored issues
show
Unused Code introduced by
The parameter $manager 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...
Unused Code introduced by
The parameter $executor 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...
Unused Code introduced by
The parameter $backupFilePath 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...
493
    {
494 2
    }
495
496
    /**
497
     * Callback function to be executed before save of references.
498
     *
499
     * @param ObjectManager    $manager        The object manager
500
     * @param AbstractExecutor $executor       Executor of the data fixtures
501
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
502
     *
503
     * @return WebTestCase
504
     */
505 2
    protected function preReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
0 ignored issues
show
Unused Code introduced by
The parameter $manager 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...
Unused Code introduced by
The parameter $executor 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...
Unused Code introduced by
The parameter $backupFilePath 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...
506
    {
507 2
    }
508
}
509