Completed
Push — refactor-loadFixtures ( e34ff9...883a4d )
by Alexis
07:38
created

WebTestCase::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 1
Metric Value
c 1
b 0
f 1
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\Test;
13
14
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
15
use Symfony\Bundle\FrameworkBundle\Console\Application;
16
use Symfony\Bundle\FrameworkBundle\Client;
17
use Symfony\Component\Console\Input\ArrayInput;
18
use Symfony\Component\Console\Output\OutputInterface;
19
use Symfony\Component\Console\Output\StreamOutput;
20
use Symfony\Component\DomCrawler\Crawler;
21
use Symfony\Component\BrowserKit\Cookie;
22
use Symfony\Component\HttpKernel\Kernel;
23
use Symfony\Component\HttpFoundation\Response;
24
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
25
use Symfony\Component\Security\Core\User\UserInterface;
26
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
27
use Symfony\Component\DependencyInjection\ContainerInterface;
28
use Symfony\Component\HttpFoundation\Session\Session;
29
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
30
use Symfony\Bridge\Doctrine\ManagerRegistry;
31
use Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader;
32
use Doctrine\Common\Persistence\ObjectManager;
33
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
34
use Doctrine\Common\DataFixtures\Executor\AbstractExecutor;
35
use Doctrine\Common\DataFixtures\ProxyReferenceRepository;
36
use Doctrine\DBAL\Connection;
37
use Doctrine\DBAL\Driver\PDOSqlite\Driver as SqliteDriver;
38
use Doctrine\DBAL\Platforms\MySqlPlatform;
39
use Doctrine\ORM\Tools\SchemaTool;
40
use Nelmio\Alice\Fixtures;
41
42
/**
43
 * @author Lea Haensenberger
44
 * @author Lukas Kahwe Smith <[email protected]>
45
 * @author Benjamin Eberlei <[email protected]>
46
 */
47
abstract class WebTestCase extends BaseWebTestCase
48
{
49
    protected $environment = 'test';
50
    protected $containers;
51
    protected $kernelDir;
52
    // 5 * 1024 * 1024 KB
53
    protected $maxMemory = 5242880;
54
55
    // RUN COMMAND
56
    protected $verbosityLevel;
57
    protected $decorated;
58
59
    /**
60
     * @var array
61
     */
62
    private $firewallLogins = array();
63
64
    /**
65
     * @var array
66
     */
67
    private static $cachedMetadatas = array();
68
69 1
    protected static function getKernelClass()
0 ignored issues
show
Coding Style introduced by
getKernelClass uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
70
    {
71 1
        $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : self::getPhpUnitXmlDir();
72
73 1
        list($appname) = explode('\\', get_called_class());
74
75 1
        $class = $appname.'Kernel';
76 1
        $file = $dir.'/'.strtolower($appname).'/'.$class.'.php';
77 1
        if (!file_exists($file)) {
78 1
            return parent::getKernelClass();
79
        }
80
        require_once $file;
81
82
        return $class;
83
    }
84
85
    /**
86
     * Creates a mock object of a service identified by its id.
87
     *
88
     * @param string $id
89
     *
90
     * @return \PHPUnit_Framework_MockObject_MockBuilder
91
     */
92 2
    protected function getServiceMockBuilder($id)
93
    {
94
        $service = $this->getContainer()->get($id);
95 2
        $class = get_class($service);
96
97
        return $this->getMockBuilder($class)->disableOriginalConstructor();
98
    }
99
100
    /**
101
     * Builds up the environment to run the given command.
102
     *
103
     * @param string $name
104
     * @param array  $params
105
     * @param bool   $reuseKernel
106
     *
107
     * @return string
108
     */
109 12
    protected function runCommand($name, array $params = array(), $reuseKernel = false)
110
    {
111 12
        array_unshift($params, $name);
112
113 12
        if (!$reuseKernel) {
114 12
            if (null !== static::$kernel) {
115 9
                static::$kernel->shutdown();
116 9
            }
117
118 12
            $kernel = static::$kernel = $this->createKernel(array('environment' => $this->environment));
119 12
            $kernel->boot();
120 12
        } else {
121 2
            $kernel = $this->getContainer()->get('kernel');
122
        }
123
124 12
        $application = new Application($kernel);
125 12
        $application->setAutoExit(false);
126
127
        // @codeCoverageIgnoreStart
128
        if ('20301' === Kernel::VERSION_ID) {
129
            $params = $this->configureVerbosityForSymfony20301($params);
130
        }
131
        // @codeCoverageIgnoreEnd
132
133 12
        $input = new ArrayInput($params);
134 12
        $input->setInteractive(false);
135
136 12
        $fp = fopen('php://temp/maxmemory:'.$this->maxMemory, 'r+');
137 12
        $output = new StreamOutput($fp, $this->getVerbosityLevel(), $this->getDecorated());
138
139 11
        $application->run($input, $output);
140
141 11
        rewind($fp);
142
143 11
        return stream_get_contents($fp);
144
    }
145
146
    /**
147
     * Retrieves the output verbosity level.
148
     *
149
     * @see Symfony\Component\Console\Output\OutputInterface for available levels
150
     *
151
     * @return int
152
     *
153
     * @throws \OutOfBoundsException If the set value isn't accepted
154
     */
155 12
    protected function getVerbosityLevel()
156
    {
157
        // If `null`, is not yet set
158 12
        if (null === $this->verbosityLevel) {
159
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
160 6
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
161 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
162
163 6
            $this->verbosityLevel = constant($verbosity);
164 6
        }
165
166
        // If string, it is set by the developer, so check that the value is an accepted one
167 12
        if (is_string($this->verbosityLevel)) {
168 6
            $level = strtoupper($this->verbosityLevel);
169 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
170
171 6
            if (!defined($verbosity)) {
172 1
                throw new \OutOfBoundsException(
173 1
                    sprintf('The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".', $level)
174 1
                    );
175
            }
176
177 5
            $this->verbosityLevel = constant($verbosity);
178 5
        }
179
180 11
        return $this->verbosityLevel;
181
    }
182
183
    /**
184
     * In Symfony 2.3.1 the verbosity level has to be set through {Symfony\Component\Console\Input\ArrayInput} and not
185
     * in {Symfony\Component\Console\Output\OutputInterface}.
186
     *
187
     * This method builds $params to be passed to {Symfony\Component\Console\Input\ArrayInput}.
188
     *
189
     * @codeCoverageIgnore
190
     *
191
     * @param array $params
192
     *
193
     * @return array
194
     */
195
    private function configureVerbosityForSymfony20301(array $params)
196
    {
197
        switch ($this->getVerbosityLevel()) {
198
            case OutputInterface::VERBOSITY_QUIET:
199
                $params['-q'] = '-q';
200
                break;
201
202
            case OutputInterface::VERBOSITY_VERBOSE:
203
                $params['-v'] = '';
204
                break;
205
206
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
207
                $params['-vv'] = '';
208
                break;
209
210
            case OutputInterface::VERBOSITY_DEBUG:
211
                $params['-vvv'] = '';
212
                break;
213
        }
214
215
        return $params;
216
    }
217
218 6
    public function setVerbosityLevel($level)
219
    {
220 6
        $this->verbosityLevel = $level;
221 6
    }
222
223
    /**
224
     * Retrieves the flag indicating if the output should be decorated or not.
225
     *
226
     * @return bool
227
     */
228 11
    protected function getDecorated()
229
    {
230 11
        if (null === $this->decorated) {
231
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
232 5
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
233 5
        }
234
235
        // Check the local decorated flag
236 11
        if (false === is_bool($this->decorated)) {
237
            throw new \OutOfBoundsException(
238
                sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated))
239
            );
240
        }
241
242 11
        return $this->decorated;
243
    }
244
245 6
    public function isDecorated($decorated)
246
    {
247 6
        $this->decorated = $decorated;
248 6
    }
249
250
    /**
251
     * Get an instance of the dependency injection container.
252
     * (this creates a kernel *without* parameters).
253
     *
254
     * @return ContainerInterface
255
     */
256 34
    protected function getContainer()
0 ignored issues
show
Coding Style introduced by
getContainer uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
257
    {
258 34
        if (!empty($this->kernelDir)) {
259
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
260
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
261
        }
262
263 34
        $cacheKey = $this->kernelDir.'|'.$this->environment;
264 34
        if (empty($this->containers[$cacheKey])) {
265
            $options = array(
266 34
                'environment' => $this->environment,
267 34
            );
268 34
            $kernel = $this->createKernel($options);
269 34
            $kernel->boot();
270
271 34
            $this->containers[$cacheKey] = $kernel->getContainer();
272 34
        }
273
274 34
        if (isset($tmpKernelDir)) {
275
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
276
        }
277
278 34
        return $this->containers[$cacheKey];
279
    }
280
281
    /**
282
     * @param string $type
283
     *
284
     * @return string
285
     */
286 26
    private function getExecutorClass($type)
287
    {
288 26
        return 'PHPCR' === $type && class_exists('Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor')
289 26
            ? 'Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor'
290 26
            : 'Doctrine\\Common\\DataFixtures\\Executor\\'.$type.'Executor';
291
    }
292
293
     /**
294
     * @param Connection $connection
295
     *
296
     * @return string $name
297
     */
298 21
    private function getNameParameter(Connection $connection)
299
    {
300 21
        $params = $connection->getParams();
301
302 21
        if (isset($params['master'])) {
303
            $params = $params['master'];
304
        }
305
306 21
        $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
307
308 21
        if (!$name) {
309
            throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
310
        }
311
312 21
        return $name;
313
    }
314
315
    /**
316
     * Purge database.
317
     *
318
     * @param ObjectManager $om
319
     * @param string        $omName The name of object manager to use
320
     */
321 21
    private function getCachedMetadatas(ObjectManager $om, $omName)
322
    {
323 21
        if (!isset(self::$cachedMetadatas[$omName])) {
324 6
            self::$cachedMetadatas[$omName] = $om->getMetadataFactory()->getAllMetadata();
325
            usort(self::$cachedMetadatas[$omName], function ($a, $b) { return strcmp($a->name, $b->name); });
326 6
        }
327
328 21
        return self::$cachedMetadatas[$omName];
329
    }
330
331
    /**
332
     * This function finds the time when the data blocks of a class definition
333
     * file were being written to, that is, the time when the content of the
334
     * file was changed.
335
     *
336
     * @param string $class The fully qualified class name of the fixture class to
337
     *                      check modification date on.
338
     *
339
     * @return \DateTime|null
340
     */
341 2
    protected function getFixtureLastModified($class)
342
    {
343 2
        $lastModifiedDateTime = null;
344
345 2
        $reflClass = new \ReflectionClass($class);
346 2
        $classFileName = $reflClass->getFileName();
347
348 2
        if (file_exists($classFileName)) {
349 2
            $lastModifiedDateTime = new \DateTime();
350 2
            $lastModifiedDateTime->setTimestamp(filemtime($classFileName));
351 2
        }
352
353 2
        return $lastModifiedDateTime;
354
    }
355
356
    /**
357
     * Determine if the Fixtures that define a database backup have been
358
     * modified since the backup was made.
359
     *
360
     * @param array  $classNames The fixture classnames to check
361
     * @param string $backup     The fixture backup SQLite database file path
362
     *
363
     * @return bool TRUE if the backup was made since the modifications to the
364
     *              fixtures; FALSE otherwise
365
     */
366 3
    protected function isBackupUpToDate(array $classNames, $backup)
367
    {
368 3
        $backupLastModifiedDateTime = new \DateTime();
369 3
        $backupLastModifiedDateTime->setTimestamp(filemtime($backup));
370
371 3
        foreach ($classNames as &$className) {
372 2
            $fixtureLastModifiedDateTime = $this->getFixtureLastModified($className);
373 2
            if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) {
374
                return false;
375
            }
376 3
        }
377
378 3
        return true;
379
    }
380
381
    /**
382
     * Copy SQLite backup file.
383
     *
384
     * @param ObjectManager            $om
385
     * @param string                   $executorClass
386
     * @param ProxyReferenceRepository $referenceRepository
387
     * @param string                   $backup              Path of the source file.
388
     * @param string                   $name                Path of the destination file.
389
     */
390 3
    private function copySqliteBackup($om, $executorClass,
391
        $referenceRepository, $backup, $name)
392
    {
393 3
        $om->flush();
394 3
        $om->clear();
395
396 3
        $this->preFixtureRestore($om, $referenceRepository);
397
398 3
        copy($backup, $name);
399
400 3
        $executor = new $executorClass($om);
401 3
        $executor->setReferenceRepository($referenceRepository);
402 3
        $executor->getReferenceRepository()->load($backup);
403
404 3
        $this->postFixtureRestore();
405
406 3
        return $executor;
407
    }
408
409
    /**
410
     * Purge database.
411
     *
412
     * @param ObjectManager            $om
413
     * @param string                   $type
414
     * @param int                      $purgeMode
415
     * @param string                   $executorClass
416
     * @param ProxyReferenceRepository $referenceRepository
417
     */
418 23
    private function purgeDatabase(ObjectManager $om, $type, $purgeMode,
419
        $executorClass,
420
        ProxyReferenceRepository $referenceRepository)
421
    {
422 23
        $container = $this->getContainer();
423
424 23
        $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
425 23
        if ('PHPCR' === $type) {
426 1
            $purger = new $purgerClass($om);
427 1
            $initManager = $container->has('doctrine_phpcr.initializer_manager')
428 1
                ? $container->get('doctrine_phpcr.initializer_manager')
429 1
                : null;
430
431 1
            $executor = new $executorClass($om, $purger, $initManager);
432 1
        } else {
433 22
            $purger = new $purgerClass();
434 22
            if (null !== $purgeMode) {
435 1
                $purger->setPurgeMode($purgeMode);
436 1
            }
437
438 22
            $executor = new $executorClass($om, $purger);
439
        }
440
441 23
        $executor->setReferenceRepository($referenceRepository);
442 23
        $executor->purge();
443
444 23
        return $executor;
445
    }
446
447
    /**
448
     * Purge database.
449
     *
450
     * @param ObjectManager            $om
451
     * @param string                   $type
0 ignored issues
show
Bug introduced by
There is no parameter named $type. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
452
     * @param array                    $metadatas
453
     * @param string                   $executorClass
454
     * @param ProxyReferenceRepository $referenceRepository
455
     */
456 18
    private function createSqliteSchema(ObjectManager $om, $name,
457
        $metadatas, $executorClass,
458
        ProxyReferenceRepository $referenceRepository)
459
    {
460
        // TODO: handle case when using persistent connections. Fail loudly?
461 18
        $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...
462 18
        $schemaTool->dropDatabase($name);
0 ignored issues
show
Unused Code introduced by
The call to SchemaTool::dropDatabase() has too many arguments starting with $name.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
463 18
        if (!empty($metadatas)) {
464 18
            $schemaTool->createSchema($metadatas);
465 18
        }
466 18
        $this->postFixtureSetup();
467
468 18
        $executor = new $executorClass($om);
469 18
        $executor->setReferenceRepository($referenceRepository);
470 18
    }
471
472
    /**
473
     * Set the database to the provided fixtures.
474
     *
475
     * Drops the current database and then loads fixtures using the specified
476
     * classes. The parameter is a list of fully qualified class names of
477
     * classes that implement Doctrine\Common\DataFixtures\FixtureInterface
478
     * so that they can be loaded by the DataFixtures Loader::addFixture
479
     *
480
     * When using SQLite this method will automatically make a copy of the
481
     * loaded schema and fixtures which will be restored automatically in
482
     * case the same fixture classes are to be loaded again. Caveat: changes
483
     * to references and/or identities may go undetected.
484
     *
485
     * Depends on the doctrine data-fixtures library being available in the
486
     * class path.
487
     *
488
     * @param array  $classNames   List of fully qualified class names of fixtures to load
489
     * @param string $omName       The name of object manager to use
490
     * @param string $registryName The service id of manager registry to use
491
     * @param int    $purgeMode    Sets the ORM purge mode
492
     *
493
     * @return null|AbstractExecutor
494
     */
495 26
    protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
496
    {
497 26
        $container = $this->getContainer();
498
        /** @var ManagerRegistry $registry */
499 26
        $registry = $container->get($registryName);
500 26
        $om = $registry->getManager($omName);
501 26
        $type = $registry->getName();
502
503 26
        $executorClass = $this->getExecutorClass($type);
504 26
        $referenceRepository = new ProxyReferenceRepository($om);
505
506 26
        $cacheDriver = $om->getMetadataFactory()->getCacheDriver();
507
508 26
        if ($cacheDriver) {
509 26
            $cacheDriver->deleteAll();
510 26
        }
511
512 26
        if ('ORM' === $type) {
513 25
            $connection = $om->getConnection();
514 25
            if ($connection->getDriver() instanceof SqliteDriver) {
515 21
                $name = $this->getNameParameter($connection);
516 21
                $metadatas = $this->getCachedMetadatas($om, $omName);
517
518 21
                if ($container->getParameter('liip_functional_test.cache_sqlite_db')) {
519 5
                    $backup = $container->getParameter('kernel.cache_dir').'/test_'.md5(serialize($metadatas).serialize($classNames)).'.db';
520 5
                    if (file_exists($backup) && file_exists($backup.'.ser') && $this->isBackupUpToDate($classNames, $backup)) {
521 3
                        $executor = $this->copySqliteBackup($om,
522 3
                            $executorClass, $referenceRepository,
523 3
                            $backup, $name);
524
525 3
                        return $executor;
526
                    }
527 2
                }
528
529 18
                $this->createSqliteSchema($om, $name, $metadatas,
530 18
                    $executorClass, $referenceRepository);
531 18
            }
532 22
        }
533
534 23
        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...
535 23
            $executor = $this->purgeDatabase($om, $type, $purgeMode,
536 23
                $executorClass, $referenceRepository);
537 23
        }
538
539 23
        $loader = $this->getFixtureLoader($container, $classNames);
540
541 23
        $executor->execute($loader->getFixtures(), true);
542
543 23
        if (isset($name) && isset($backup)) {
544 2
            $this->preReferenceSave($om, $executor, $backup);
545
546 2
            $executor->getReferenceRepository()->save($backup);
547 2
            copy($name, $backup);
548
549 2
            $this->postReferenceSave($om, $executor, $backup);
550 2
        }
551
552 23
        return $executor;
553
    }
554
555
    /**
556
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
557
     * @param bool   $append
558
     * @param null   $omName
559
     * @param string $registryName
560
     *
561
     * @return array
562
     *
563
     * @throws \BadMethodCallException
564
     */
565 3
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine')
566
    {
567 3
        if (!class_exists('Nelmio\Alice\Fixtures')) {
568
            throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
569
        }
570
571
        /** @var ManagerRegistry $registry */
572 3
        $registry = $this->getContainer()->get($registryName);
573 3
        $om = $registry->getManager($omName);
574
575 3
        if ($append === false) {
576
            //Clean database
577 3
            $connection = $om->getConnection();
578 3
            if ($registry->getName() === 'ORM' && $connection->getDatabasePlatform() instanceof MySqlPlatform) {
579 1
                $connection->query('SET FOREIGN_KEY_CHECKS=0');
580 1
            }
581
582 3
            $this->loadFixtures(array());
583
584 3
            if ($registry->getName() === 'ORM' && $connection->getDatabasePlatform() instanceof MySqlPlatform) {
585 1
                $connection->query('SET FOREIGN_KEY_CHECKS=1');
586 1
            }
587 3
        }
588
589 3
        $files = array();
590 3
        $kernel = $this->getContainer()->get('kernel');
591 3
        foreach ($paths as $path) {
592 3
            if ($path[0] !== '@' && file_exists($path) === true) {
593 1
                $files[] = $path;
594 1
                continue;
595
            }
596
597 2
            $files[] = $kernel->locateResource($path);
598 3
        }
599
600 3
        return Fixtures::load($files, $om);
601
    }
602
603
    /**
604
     * Callback function to be executed after Schema creation.
605
     * Use this to execute acl:init or other things necessary.
606
     */
607 18
    protected function postFixtureSetup()
608
    {
609 18
    }
610
611
    /**
612
     * Callback function to be executed after Schema restore.
613
     *
614
     * @return WebTestCase
615
     */
616 3
    protected function postFixtureRestore()
617
    {
618 3
    }
619
620
    /**
621
     * Callback function to be executed before Schema restore.
622
     *
623
     * @param ObjectManager            $manager             The object manager
624
     * @param ProxyReferenceRepository $referenceRepository The reference repository
625
     *
626
     * @return WebTestCase
627
     */
628 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...
629
    {
630 3
    }
631
632
    /**
633
     * Callback function to be executed after save of references.
634
     *
635
     * @param ObjectManager    $manager        The object manager
636
     * @param AbstractExecutor $executor       Executor of the data fixtures
637
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
638
     *
639
     * @return WebTestCase
640
     */
641 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...
642
    {
643 2
    }
644
645
    /**
646
     * Callback function to be executed before save of references.
647
     *
648
     * @param ObjectManager    $manager        The object manager
649
     * @param AbstractExecutor $executor       Executor of the data fixtures
650
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
651
     *
652
     * @return WebTestCase
653
     */
654 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...
655
    {
656 2
    }
657
658
    /**
659
     * Retrieve Doctrine DataFixtures loader.
660
     *
661
     * @param ContainerInterface $container
662
     * @param array              $classNames
663
     *
664
     * @return Loader
665
     */
666 23
    protected function getFixtureLoader(ContainerInterface $container, array $classNames)
667
    {
668 23
        $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
669 23
            ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
670 23
            : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
671
                ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
672 23
                : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
673
674 23
        $loader = new $loaderClass($container);
675
676 23
        foreach ($classNames as $className) {
677 6
            $this->loadFixtureClass($loader, $className);
678 23
        }
679
680 23
        return $loader;
681
    }
682
683
    /**
684
     * Load a data fixture class.
685
     *
686
     * @param Loader $loader
687
     * @param string $className
688
     */
689 6
    protected function loadFixtureClass($loader, $className)
690
    {
691 6
        $fixture = new $className();
692
693 6
        if ($loader->hasFixture($fixture)) {
694
            unset($fixture);
695
696
            return;
697
        }
698
699 6
        $loader->addFixture($fixture);
700
701 6
        if ($fixture instanceof DependentFixtureInterface) {
702
            foreach ($fixture->getDependencies() as $dependency) {
703
                $this->loadFixtureClass($loader, $dependency);
704
            }
705
        }
706 6
    }
707
708
    /**
709
     * Creates an instance of a lightweight Http client.
710
     *
711
     * If $authentication is set to 'true' it will use the content of
712
     * 'liip_functional_test.authentication' to log in.
713
     *
714
     * $params can be used to pass headers to the client, note that they have
715
     * to follow the naming format used in $_SERVER.
716
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
717
     *
718
     * @param bool|array $authentication
719
     * @param array|bool      $params
720
     *
721
     * @return Client
722
     */
723 46
    protected function makeClient($authentication = false, array $params = array())
724
    {
725 46
        if ($authentication) {
726 2
            if ($authentication === true) {
727
                $authentication = array(
728 1
                    'username' => $this->getContainer()
729 1
                        ->getParameter('liip_functional_test.authentication.username'),
730 1
                    'password' => $this->getContainer()
731 1
                        ->getParameter('liip_functional_test.authentication.password'),
732 1
                );
733 1
            }
734
735 2
            $params = array_merge($params, array(
736 2
                'PHP_AUTH_USER' => $authentication['username'],
737 2
                'PHP_AUTH_PW' => $authentication['password'],
738 2
            ));
739 2
        }
740
741 46
        $client = static::createClient(array('environment' => $this->environment), $params);
742
743 46
        if ($this->firewallLogins) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->firewallLogins of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
744
            // has to be set otherwise "hasPreviousSession" in Request returns false.
745 2
            $options = $client->getContainer()->getParameter('session.storage.options');
746
747 2
            if (!$options || !isset($options['name'])) {
748
                throw new \InvalidArgumentException('Missing session.storage.options#name');
749
            }
750
751 2
            $session = $client->getContainer()->get('session');
752
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
753 2
            if ($session instanceof Session) {
754 2
                $session->setId(uniqid());
755 2
            }
756
757 2
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
758
759
            /** @var $user UserInterface */
760 2
            foreach ($this->firewallLogins as $firewallName => $user) {
761 2
                $token = $this->createUserToken($user, $firewallName);
762
763
                // BC: security.token_storage is available on Symfony 2.6+
764
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
765 2
                if ($client->getContainer()->has('security.token_storage')) {
766 2
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
767 2
                } else {
768
                    // This block will never be reached with Symfony 2.6+
769
                    // @codeCoverageIgnoreStart
770
                    $tokenStorage = $client->getContainer()->get('security.context');
771
                    // @codeCoverageIgnoreEnd
772
                }
773
774 2
                $tokenStorage->setToken($token);
775 2
                $session->set('_security_'.$firewallName, serialize($token));
776 2
            }
777
778 2
            $session->save();
779 2
        }
780
781 46
        return $client;
782
    }
783
784
    /**
785
     * Create User Token.
786
     *
787
     * Factory method for creating a User Token object for the firewall based on
788
     * the user object provided. By default it will be a Username/Password
789
     * Token based on the user's credentials, but may be overridden for custom
790
     * tokens in your applications.
791
     *
792
     * @param UserInterface $user         The user object to base the token off of
793
     * @param string        $firewallName name of the firewall provider to use
794
     *
795
     * @return TokenInterface The token to be used in the security context
796
     */
797 2
    protected function createUserToken(UserInterface $user, $firewallName)
798
    {
799 2
        return new UsernamePasswordToken(
800 2
            $user,
801 2
            null,
802 2
            $firewallName,
803 2
            $user->getRoles()
804 2
        );
805
    }
806
807
    /**
808
     * Extracts the location from the given route.
809
     *
810
     * @param string $route    The name of the route
811
     * @param array  $params   Set of parameters
812
     * @param bool   $absolute
813
     *
814
     * @return string
815
     */
816 1
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
817
    {
818 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
819
    }
820
821
    /**
822
     * Checks the success state of a response.
823
     *
824
     * @param Response $response Response object
825
     * @param bool     $success  to define whether the response is expected to be successful
826
     * @param string   $type
827
     */
828 5
    public function isSuccessful(Response $response, $success = true, $type = 'text/html')
829
    {
830
        try {
831 5
            $crawler = new Crawler();
832 5
            $crawler->addContent($response->getContent(), $type);
833 5
            if (!count($crawler->filter('title'))) {
834 1
                $title = '['.$response->getStatusCode().'] - '.$response->getContent();
835 1
            } else {
836 4
                $title = $crawler->filter('title')->text();
837
            }
838 5
        } catch (\Exception $e) {
839
            $title = $e->getMessage();
840
        }
841
842 5
        if ($success) {
843 4
            $this->assertTrue($response->isSuccessful(), 'The Response was not successful: '.$title);
844 4
        } else {
845 1
            $this->assertFalse($response->isSuccessful(), 'The Response was successful: '.$title);
846
        }
847 5
    }
848
849
    /**
850
     * Executes a request on the given url and returns the response contents.
851
     *
852
     * This method also asserts the request was successful.
853
     *
854
     * @param string $path           path of the requested page
855
     * @param string $method         The HTTP method to use, defaults to GET
856
     * @param bool   $authentication Whether to use authentication, defaults to false
857
     * @param bool   $success        to define whether the response is expected to be successful
858
     *
859
     * @return string
860
     */
861 1
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
862
    {
863 1
        $client = $this->makeClient($authentication);
864 1
        $client->request($method, $path);
865
866 1
        $content = $client->getResponse()->getContent();
867 1
        if (is_bool($success)) {
868 1
            $this->isSuccessful($client->getResponse(), $success);
869 1
        }
870
871 1
        return $content;
872
    }
873
874
    /**
875
     * Executes a request on the given url and returns a Crawler object.
876
     *
877
     * This method also asserts the request was successful.
878
     *
879
     * @param string $path           path of the requested page
880
     * @param string $method         The HTTP method to use, defaults to GET
881
     * @param bool   $authentication Whether to use authentication, defaults to false
882
     * @param bool   $success        Whether the response is expected to be successful
883
     *
884
     * @return Crawler
885
     */
886 1
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
887
    {
888 1
        $client = $this->makeClient($authentication);
889 1
        $crawler = $client->request($method, $path);
890
891 1
        $this->isSuccessful($client->getResponse(), $success);
892
893 1
        return $crawler;
894
    }
895
896
    /**
897
     * @param UserInterface $user
898
     *
899
     * @return WebTestCase
900
     */
901 2
    public function loginAs(UserInterface $user, $firewallName)
902
    {
903 2
        $this->firewallLogins[$firewallName] = $user;
904
905 2
        return $this;
906
    }
907
908
    /**
909
     * Asserts that the HTTP response code of the last request performed by
910
     * $client matches the expected code. If not, raises an error with more
911
     * information.
912
     *
913
     * @param $expectedStatusCode
914
     * @param Client $client
915
     */
916 9
    public function assertStatusCode($expectedStatusCode, Client $client)
917
    {
918 9
        $helpfulErrorMessage = null;
919
920 9
        if ($expectedStatusCode !== $client->getResponse()->getStatusCode()) {
921
            // Get a more useful error message, if available
922
            if ($exception = $client->getContainer()->get('liip_functional_test.exception_listener')->getLastException()) {
923
                $helpfulErrorMessage = $exception->getMessage();
924
            } elseif (count($validationErrors = $client->getContainer()->get('liip_functional_test.validator')->getLastErrors())) {
925
                $helpfulErrorMessage = "Unexpected validation errors:\n";
926
927
                foreach ($validationErrors as $error) {
928
                    $helpfulErrorMessage .= sprintf("+ %s: %s\n", $error->getPropertyPath(), $error->getMessage());
929
                }
930
            } else {
931
                $helpfulErrorMessage = substr($client->getResponse(), 0, 200);
932
            }
933
        }
934
935 9
        self::assertEquals($expectedStatusCode, $client->getResponse()->getStatusCode(), $helpfulErrorMessage);
936 9
    }
937
938
    /**
939
     * Assert that the last validation errors within $container match the
940
     * expected keys.
941
     *
942
     * @param array              $expected  A flat array of field names
943
     * @param ContainerInterface $container
944
     */
945 2
    public function assertValidationErrors(array $expected, ContainerInterface $container)
946
    {
947 2
        self::assertThat(
948 2
            $container->get('liip_functional_test.validator')->getLastErrors(),
949 2
            new ValidationErrorsConstraint($expected),
950
            'Validation errors should match.'
951 2
        );
952 1
    }
953
}
954