Passed
Pull Request — master (#406)
by Florent
26:52
created

WebTestCase::tearDown()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 11
cts 11
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 2
nop 0
crap 4
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 Doctrine\Common\DataFixtures\DependentFixtureInterface;
15
use Doctrine\Common\DataFixtures\Executor\AbstractExecutor;
16
use Doctrine\Common\DataFixtures\ProxyReferenceRepository;
17
use Doctrine\Common\Persistence\ObjectManager;
18
use Doctrine\DBAL\Driver\PDOSqlite\Driver as SqliteDriver;
19
use Doctrine\DBAL\Platforms\MySqlPlatform;
20
use Doctrine\ORM\EntityManager;
21
use Doctrine\ORM\Tools\SchemaTool;
22
use Liip\FunctionalTestBundle\Utils\HttpAssertions;
23
use Nelmio\Alice\Fixtures;
24
use Symfony\Bridge\Doctrine\ManagerRegistry;
25
use Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader;
26
use Symfony\Bundle\FrameworkBundle\Client;
27
use Symfony\Bundle\FrameworkBundle\Console\Application;
28
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
29
use Symfony\Component\BrowserKit\Cookie;
30
use Symfony\Component\Console\Input\ArrayInput;
31
use Symfony\Component\Console\Output\StreamOutput;
32
use Symfony\Component\DependencyInjection\ContainerInterface;
33
use Symfony\Component\DependencyInjection\ResettableContainerInterface;
34
use Symfony\Component\DomCrawler\Crawler;
35
use Symfony\Component\HttpFoundation\Response;
36
use Symfony\Component\HttpFoundation\Session\Session;
37
use Symfony\Component\HttpKernel\Kernel;
38
use Symfony\Component\HttpKernel\KernelInterface;
39
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
40
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
41
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
42
use Symfony\Component\Security\Core\User\UserInterface;
43
44
/**
45
 * @author Lea Haensenberger
46
 * @author Lukas Kahwe Smith <[email protected]>
47
 * @author Benjamin Eberlei <[email protected]>
48
 */
49
abstract class WebTestCase extends BaseWebTestCase
50
{
51
    protected $environment = 'test';
52
53
    protected $containers;
54
55
    protected $kernelDir;
56
57
    // 5 * 1024 * 1024 KB
58
    protected $maxMemory = 5242880;
59
60
    // RUN COMMAND
61
    protected $verbosityLevel;
62
63
    protected $decorated;
64
65
    /**
66
     * @var array
67
     */
68
    private $firewallLogins = [];
69
70
    /**
71
     * @var array
72
     */
73
    private $excludedDoctrineTables = [];
74
75
    /**
76
     * @var array
77
     */
78
    private static $cachedMetadatas = [];
79
80
    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...
81
    {
82
        $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : static::getPhpUnitXmlDir();
0 ignored issues
show
Deprecated Code introduced by
The method Symfony\Bundle\Framework...ase::getPhpUnitXmlDir() has been deprecated with message: since 3.4 and will be removed in 4.0.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
83
84
        list($appname) = explode('\\', get_called_class());
85
86
        $class = $appname.'Kernel';
87
        $file = $dir.'/'.strtolower($appname).'/'.$class.'.php';
88
        if (!file_exists($file)) {
89
            return parent::getKernelClass();
90
        }
91
        require_once $file;
92
93
        return $class;
94
    }
95
96
    /**
97
     * Creates a mock object of a service identified by its id.
98
     *
99
     * @param string $id
100
     *
101
     * @return \PHPUnit_Framework_MockObject_MockBuilder
102
     */
103
    protected function getServiceMockBuilder($id)
104
    {
105
        $service = $this->getContainer()->get($id);
106
        $class = get_class($service);
107
108
        return $this->getMockBuilder($class)->disableOriginalConstructor();
109
    }
110
111
    /**
112
     * Builds up the environment to run the given command.
113
     *
114
     * @param string $name
115
     * @param array  $params
116
     * @param bool   $reuseKernel
117
     *
118
     * @return string
119
     */
120 12
    protected function runCommand($name, array $params = [], $reuseKernel = false)
121
    {
122 12
        array_unshift($params, $name);
123
124 12
        if (!$reuseKernel) {
125 12
            if (null !== static::$kernel) {
126 9
                static::$kernel->shutdown();
127 9
            }
128
129 12
            $kernel = static::$kernel = $this->createKernel(['environment' => $this->environment]);
130 12
            $kernel->boot();
131 12
        } else {
132 2
            $kernel = $this->getContainer()->get('kernel');
133
        }
134
135 12
        $application = $this->createApplication($kernel);
136
137 12
        $input = new ArrayInput($params);
138 12
        $input->setInteractive(false);
139
140 12
        $fp = fopen('php://temp/maxmemory:'.$this->maxMemory, 'r+');
141 12
        $verbosityLevel = $this->getVerbosityLevel();
142
143 11
        $this->setVerbosityLevelEnv($verbosityLevel);
144 11
        $output = new StreamOutput($fp, $verbosityLevel, $this->getDecorated());
145
146 11
        $application->run($input, $output);
147
148 11
        rewind($fp);
149
150 11
        return stream_get_contents($fp);
151
    }
152
153
    /**
154
     * @param KernelInterface $kernel
155
     *
156
     * @return Application
157
     */
158 12
    protected function createApplication(KernelInterface $kernel)
159
    {
160 12
        $application = new Application($kernel);
161 12
        $application->setAutoExit(false);
162
163 12
        return $application;
164
    }
165
166
    /**
167
     * Retrieves the output verbosity level.
168
     *
169
     * @see \Symfony\Component\Console\Output\OutputInterface for available levels
170
     *
171
     * @throws \OutOfBoundsException If the set value isn't accepted
172
     *
173
     * @return int
174
     */
175 12
    protected function getVerbosityLevel()
176
    {
177
        // If `null`, is not yet set
178 12
        if (null === $this->verbosityLevel) {
179
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
180 6
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
181 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
182
183 6
            $this->verbosityLevel = constant($verbosity);
184 6
        }
185
186
        // If string, it is set by the developer, so check that the value is an accepted one
187 12
        if (is_string($this->verbosityLevel)) {
188 6
            $level = strtoupper($this->verbosityLevel);
189 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
190
191 6
            if (!defined($verbosity)) {
192 1
                throw new \OutOfBoundsException(
193 1
                    sprintf('The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".', $level)
194 1
                );
195
            }
196
197 5
            $this->verbosityLevel = constant($verbosity);
198 5
        }
199
200 11
        return $this->verbosityLevel;
201
    }
202
203 6
    public function setVerbosityLevel($level)
204
    {
205 6
        $this->verbosityLevel = $level;
206 6
    }
207
208
    /**
209
     * Set verbosity for Symfony 3.4+.
210
     *
211
     * @see https://github.com/symfony/symfony/pull/24425
212
     *
213
     * @param $level
214
     */
215 11
    private function setVerbosityLevelEnv($level)
216
    {
217 11
        putenv('SHELL_VERBOSITY='.$level);
218 11
    }
219
220
    /**
221
     * Retrieves the flag indicating if the output should be decorated or not.
222
     *
223
     * @return bool
224
     */
225 11
    protected function getDecorated()
226
    {
227 11
        if (null === $this->decorated) {
228
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
229 5
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
230 5
        }
231
232
        // Check the local decorated flag
233 11
        if (false === is_bool($this->decorated)) {
234
            throw new \OutOfBoundsException(
235
                sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated))
236
            );
237
        }
238
239 11
        return $this->decorated;
240
    }
241
242 6
    public function isDecorated($decorated)
243
    {
244 6
        $this->decorated = $decorated;
245 6
    }
246
247
    /**
248
     * Get an instance of the dependency injection container.
249
     * (this creates a kernel *without* parameters).
250
     *
251
     * @return ContainerInterface
252
     */
253 50
    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...
254
    {
255 50
        if (!empty($this->kernelDir)) {
256
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
257
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
258
        }
259
260 50
        $cacheKey = $this->kernelDir.'|'.$this->environment;
261 50
        if (empty($this->containers[$cacheKey])) {
262
            $options = [
263 49
                'environment' => $this->environment,
264 49
            ];
265 49
            $kernel = $this->createKernel($options);
266 49
            $kernel->boot();
267
268 49
            $this->containers[$cacheKey] = $kernel->getContainer();
269 49
        }
270
271 50
        if (isset($tmpKernelDir)) {
272
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
273
        }
274
275 50
        return $this->containers[$cacheKey];
276
    }
277
278
    /**
279
     * This function finds the time when the data blocks of a class definition
280
     * file were being written to, that is, the time when the content of the
281
     * file was changed.
282
     *
283
     * @param string $class The fully qualified class name of the fixture class to
284
     *                      check modification date on
285
     *
286
     * @return \DateTime|null
287
     */
288 3
    protected function getFixtureLastModified($class)
289
    {
290 3
        $lastModifiedDateTime = null;
291
292 3
        $reflClass = new \ReflectionClass($class);
293 3
        $classFileName = $reflClass->getFileName();
294
295 3
        if (file_exists($classFileName)) {
296 3
            $lastModifiedDateTime = new \DateTime();
297 3
            $lastModifiedDateTime->setTimestamp(filemtime($classFileName));
298 3
        }
299
300 3
        return $lastModifiedDateTime;
301
    }
302
303
    /**
304
     * Determine if the Fixtures that define a database backup have been
305
     * modified since the backup was made.
306
     *
307
     * @param array  $classNames The fixture classnames to check
308
     * @param string $backup     The fixture backup SQLite database file path
309
     *
310
     * @return bool TRUE if the backup was made since the modifications to the
311
     *              fixtures; FALSE otherwise
312
     */
313 7
    protected function isBackupUpToDate(array $classNames, $backup)
314
    {
315 7
        $backupLastModifiedDateTime = new \DateTime();
316 7
        $backupLastModifiedDateTime->setTimestamp(filemtime($backup));
317
318
        /** @var \Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader $loader */
319 7
        $loader = $this->getFixtureLoader($this->getContainer(), $classNames);
320
321
        // Use loader in order to fetch all the dependencies fixtures.
322 7
        foreach ($loader->getFixtures() as $className) {
323 3
            $fixtureLastModifiedDateTime = $this->getFixtureLastModified($className);
324 3
            if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) {
325 1
                return false;
326
            }
327 7
        }
328
329 7
        return true;
330
    }
331
332
    /**
333
     * Set the database to the provided fixtures.
334
     *
335
     * Drops the current database and then loads fixtures using the specified
336
     * classes. The parameter is a list of fully qualified class names of
337
     * classes that implement Doctrine\Common\DataFixtures\FixtureInterface
338
     * so that they can be loaded by the DataFixtures Loader::addFixture
339
     *
340
     * When using SQLite this method will automatically make a copy of the
341
     * loaded schema and fixtures which will be restored automatically in
342
     * case the same fixture classes are to be loaded again. Caveat: changes
343
     * to references and/or identities may go undetected.
344
     *
345
     * Depends on the doctrine data-fixtures library being available in the
346
     * class path.
347
     *
348
     * @param array  $classNames   List of fully qualified class names of fixtures to load
349
     * @param string $omName       The name of object manager to use
350
     * @param string $registryName The service id of manager registry to use
351
     * @param int    $purgeMode    Sets the ORM purge mode
352
     *
353
     * @return null|AbstractExecutor
354
     */
355 36
    protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
356
    {
357 36
        $container = $this->getContainer();
358
        /** @var ManagerRegistry $registry */
359 36
        $registry = $container->get($registryName);
360
        /** @var ObjectManager $om */
361 36
        $om = $registry->getManager($omName);
362 36
        $type = $registry->getName();
363
364 36
        $executorClass = 'PHPCR' === $type && class_exists('Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor')
365 36
            ? 'Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor'
366 36
            : 'Doctrine\\Common\\DataFixtures\\Executor\\'.$type.'Executor';
367 36
        $referenceRepository = new ProxyReferenceRepository($om);
368 36
        $cacheDriver = $om->getMetadataFactory()->getCacheDriver();
369
370 36
        if ($cacheDriver) {
371 36
            $cacheDriver->deleteAll();
372 36
        }
373
374 36
        if ('ORM' === $type) {
375 35
            $connection = $om->getConnection();
376 35
            if ($connection->getDriver() instanceof SqliteDriver) {
377 30
                $params = $connection->getParams();
378 30
                if (isset($params['master'])) {
379
                    $params = $params['master'];
380
                }
381
382 30
                $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
383 30
                if (!$name) {
384
                    throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
385
                }
386
387 30
                if (!isset(self::$cachedMetadatas[$omName])) {
388 10
                    self::$cachedMetadatas[$omName] = $om->getMetadataFactory()->getAllMetadata();
389 10
                    usort(self::$cachedMetadatas[$omName], function ($a, $b) {
390
                        return strcmp($a->name, $b->name);
391 10
                    });
392 10
                }
393 30
                $metadatas = self::$cachedMetadatas[$omName];
394
395 30
                if ($container->getParameter('liip_functional_test.cache_sqlite_db')) {
396 9
                    $backup = $container->getParameter('kernel.cache_dir').'/test_'.md5(serialize($metadatas).serialize($classNames)).'.db';
397 9
                    if (file_exists($backup) && file_exists($backup.'.ser') && $this->isBackupUpToDate($classNames, $backup)) {
398 7
                        $connection = $this->getContainer()->get('doctrine.orm.entity_manager')->getConnection();
399 7
                        if (null !== $connection) {
400 7
                            $connection->close();
401 7
                        }
402
403 7
                        $om->flush();
404 7
                        $om->clear();
405
406 7
                        $this->preFixtureBackupRestore($om, $referenceRepository, $backup);
407
408 7
                        copy($backup, $name);
409
410 7
                        $executor = new $executorClass($om);
411 7
                        $executor->setReferenceRepository($referenceRepository);
412 7
                        $executor->getReferenceRepository()->load($backup);
413
414 7
                        $this->postFixtureBackupRestore($backup);
415
416 7
                        return $executor;
417
                    }
418 3
                }
419
420
                // TODO: handle case when using persistent connections. Fail loudly?
421 24
                $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...
422 24
                $schemaTool->dropDatabase();
423 24
                if (!empty($metadatas)) {
424 24
                    $schemaTool->createSchema($metadatas);
425 24
                }
426 24
                $this->postFixtureSetup();
427
428 24
                $executor = new $executorClass($om);
429 24
                $executor->setReferenceRepository($referenceRepository);
430 24
            }
431 29
        }
432
433 30
        if (empty($executor)) {
434 6
            $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
435 6
            if ('PHPCR' === $type) {
436 1
                $purger = new $purgerClass($om);
437 1
                $initManager = $container->has('doctrine_phpcr.initializer_manager')
438 1
                    ? $container->get('doctrine_phpcr.initializer_manager')
439 1
                    : null;
440
441 1
                $executor = new $executorClass($om, $purger, $initManager);
442 1
            } else {
443 5
                if ('ORM' === $type) {
444 5
                    $purger = new $purgerClass(null, $this->excludedDoctrineTables);
445 5
                } else {
446
                    $purger = new $purgerClass();
447
                }
448
449 5
                if (null !== $purgeMode) {
450 2
                    $purger->setPurgeMode($purgeMode);
451 2
                }
452
453 5
                $executor = new $executorClass($om, $purger);
454
            }
455
456 6
            $executor->setReferenceRepository($referenceRepository);
457 6
            $executor->purge();
458 6
        }
459
460 30
        $loader = $this->getFixtureLoader($container, $classNames);
461
462 30
        $executor->execute($loader->getFixtures(), true);
463
464 30
        if (isset($name, $backup)) {
465 3
            $this->preReferenceSave($om, $executor, $backup);
466
467 3
            $executor->getReferenceRepository()->save($backup);
468 3
            copy($name, $backup);
469
470 3
            $this->postReferenceSave($om, $executor, $backup);
471 3
        }
472
473 30
        return $executor;
474
    }
475
476
    /**
477
     * Clean database.
478
     *
479
     * @param ManagerRegistry $registry
480
     * @param EntityManager   $om
481
     * @param null            $omName
482
     * @param string          $registryName
483
     * @param int             $purgeMode
484
     */
485 9
    private function cleanDatabase(ManagerRegistry $registry, EntityManager $om, $omName = null, $registryName = 'doctrine', $purgeMode = null)
486
    {
487 9
        $connection = $om->getConnection();
488
489 9
        $mysql = ('ORM' === $registry->getName()
490 9
            && $connection->getDatabasePlatform() instanceof MySqlPlatform);
491
492 9
        if ($mysql) {
493 1
            $connection->query('SET FOREIGN_KEY_CHECKS=0');
494 1
        }
495
496 9
        $this->loadFixtures([], $omName, $registryName, $purgeMode);
497
498 9
        if ($mysql) {
499 1
            $connection->query('SET FOREIGN_KEY_CHECKS=1');
500 1
        }
501 9
    }
502
503
    /**
504
     * Locate fixture files.
505
     *
506
     * @param array $paths
507
     *
508
     * @throws \InvalidArgumentException if a wrong path is given outside a bundle
509
     *
510
     * @return array $files
511
     */
512 10
    private function locateResources($paths)
513
    {
514 10
        $files = [];
515
516 10
        $kernel = $this->getContainer()->get('kernel');
517
518 10
        foreach ($paths as $path) {
519 10
            if ('@' !== $path[0]) {
520 3
                if (!file_exists($path)) {
521 1
                    throw new \InvalidArgumentException(sprintf('Unable to find file "%s".', $path));
522
                }
523 2
                $files[] = $path;
524
525 2
                continue;
526
            }
527
528 7
            $files[] = $kernel->locateResource($path);
529 8
        }
530
531 8
        return $files;
532
    }
533
534
    /**
535
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
536
     * @param bool   $append
537
     * @param null   $omName
538
     * @param string $registryName
539
     * @param int    $purgeMode
540
     *
541
     * @throws \BadMethodCallException
542
     *
543
     * @return array
544
     */
545 10
    public function loadFixtureFiles(array $paths = [], $append = false, $omName = null, $registryName = 'doctrine', $purgeMode = null)
546
    {
547 10
        if (!class_exists('Nelmio\Alice\Fixtures')) {
548
            // This class is available during tests, no exception will be thrown.
549
            // @codeCoverageIgnoreStart
550
            throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
551
            // @codeCoverageIgnoreEnd
552
        }
553
554
        /** @var ContainerInterface $container */
555 10
        $container = $this->getContainer();
556
557
        /** @var ManagerRegistry $registry */
558 10
        $registry = $container->get($registryName);
559
560
        /** @var EntityManager $om */
561 10
        $om = $registry->getManager($omName);
562
563 10
        if (false === $append) {
564 9
            $this->cleanDatabase($registry, $om, $omName, $registryName, $purgeMode);
565 9
        }
566
567 10
        $files = $this->locateResources($paths);
568
569
        // Check if the Hautelook AliceBundle is registered and if yes, use it instead of Nelmio Alice
570 8
        $hautelookLoaderServiceName = 'hautelook_alice.fixtures.loader';
571 8
        if ($container->has($hautelookLoaderServiceName)) {
572 3
            $loaderService = $container->get($hautelookLoaderServiceName);
573 3
            $persisterClass = class_exists('Nelmio\Alice\ORM\Doctrine') ?
574 3
                'Nelmio\Alice\ORM\Doctrine' :
575 3
                'Nelmio\Alice\Persister\Doctrine';
576
577 3
            return $loaderService->load(new $persisterClass($om), $files);
578
        }
579
580 5
        return Fixtures::load($files, $om);
581
    }
582
583
    /**
584
     * Callback function to be executed after Schema creation.
585
     * Use this to execute acl:init or other things necessary.
586
     */
587 24
    protected function postFixtureSetup()
588
    {
589 24
    }
590
591
    /**
592
     * Callback function to be executed after Schema restore.
593
     *
594
     * @return WebTestCase
595
     *
596
     * @deprecated since version 1.8, to be removed in 2.0. Use postFixtureBackupRestore method instead.
597
     */
598 7
    protected function postFixtureRestore()
599
    {
600 7
    }
601
602
    /**
603
     * Callback function to be executed before Schema restore.
604
     *
605
     * @param ObjectManager            $manager             The object manager
606
     * @param ProxyReferenceRepository $referenceRepository The reference repository
607
     *
608
     * @return WebTestCase
609
     *
610
     * @deprecated since version 1.8, to be removed in 2.0. Use preFixtureBackupRestore method instead.
611
     */
612 7
    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...
613
    {
614 7
    }
615
616
    /**
617
     * Callback function to be executed after Schema restore.
618
     *
619
     * @param string $backupFilePath Path of file used to backup the references of the data fixtures
620
     *
621
     * @return WebTestCase
622
     */
623 7
    protected function postFixtureBackupRestore($backupFilePath)
0 ignored issues
show
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...
624
    {
625 7
        $this->postFixtureRestore();
0 ignored issues
show
Deprecated Code introduced by
The method Liip\FunctionalTestBundl...e::postFixtureRestore() has been deprecated with message: since version 1.8, to be removed in 2.0. Use postFixtureBackupRestore method instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
626
627 7
        return $this;
628
    }
629
630
    /**
631
     * Callback function to be executed before Schema restore.
632
     *
633
     * @param ObjectManager            $manager             The object manager
634
     * @param ProxyReferenceRepository $referenceRepository The reference repository
635
     * @param string                   $backupFilePath      Path of file used to backup the references of the data fixtures
636
     *
637
     * @return WebTestCase
638
     */
639 7
    protected function preFixtureBackupRestore(
640
        ObjectManager $manager,
641
        ProxyReferenceRepository $referenceRepository,
642
        $backupFilePath
0 ignored issues
show
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...
643
    ) {
644 7
        $this->preFixtureRestore($manager, $referenceRepository);
0 ignored issues
show
Deprecated Code introduced by
The method Liip\FunctionalTestBundl...se::preFixtureRestore() has been deprecated with message: since version 1.8, to be removed in 2.0. Use preFixtureBackupRestore method instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
645
646 7
        return $this;
647
    }
648
649
    /**
650
     * Callback function to be executed after save of references.
651
     *
652
     * @param ObjectManager    $manager        The object manager
653
     * @param AbstractExecutor $executor       Executor of the data fixtures
654
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
655
     *
656
     * @return WebTestCase
657
     */
658 3
    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...
659
    {
660 3
    }
661
662
    /**
663
     * Callback function to be executed before save of references.
664
     *
665
     * @param ObjectManager    $manager        The object manager
666
     * @param AbstractExecutor $executor       Executor of the data fixtures
667
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
668
     *
669
     * @return WebTestCase
670
     */
671 3
    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...
672
    {
673 3
    }
674
675
    /**
676
     * Retrieve Doctrine DataFixtures loader.
677
     *
678
     * @param ContainerInterface $container
679
     * @param array              $classNames
680
     *
681
     * @return Loader
682
     */
683 36
    protected function getFixtureLoader(ContainerInterface $container, array $classNames)
684
    {
685 36
        $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
686 36
            ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
687 36
            : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
688
                // This class is not available during tests.
689
                // @codeCoverageIgnoreStart
690
                ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
691
                // @codeCoverageIgnoreEnd
692 36
                : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
693
694 36
        $loader = new $loaderClass($container);
695
696 36
        foreach ($classNames as $className) {
697 11
            $this->loadFixtureClass($loader, $className);
698 36
        }
699
700 36
        return $loader;
701
    }
702
703
    /**
704
     * Load a data fixture class.
705
     *
706
     * @param Loader $loader
707
     * @param string $className
708
     */
709 11
    protected function loadFixtureClass($loader, $className)
710
    {
711 11
        $fixture = null;
0 ignored issues
show
Unused Code introduced by
$fixture is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
712
713 11
        if ($this->getContainer()->has($className)) {
714
            $fixture = $this->getContainer()->get($className);
715
        } else {
716 11
            $fixture = new $className();
717
        }
718
719 11
        if ($loader->hasFixture($fixture)) {
720 2
            unset($fixture);
721
722 2
            return;
723
        }
724
725 11
        $loader->addFixture($fixture);
726
727 11
        if ($fixture instanceof DependentFixtureInterface) {
728 2
            foreach ($fixture->getDependencies() as $dependency) {
729 2
                $this->loadFixtureClass($loader, $dependency);
730 2
            }
731 2
        }
732 11
    }
733
734
    /**
735
     * Creates an instance of a lightweight Http client.
736
     *
737
     * If $authentication is set to 'true' it will use the content of
738
     * 'liip_functional_test.authentication' to log in.
739
     *
740
     * $params can be used to pass headers to the client, note that they have
741
     * to follow the naming format used in $_SERVER.
742
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
743
     *
744
     * @param bool|array $authentication
745
     * @param array      $params
746
     *
747
     * @return Client
748
     */
749 55
    protected function makeClient($authentication = false, array $params = [])
750
    {
751 55
        if ($authentication) {
752 2
            if (true === $authentication) {
753
                $authentication = [
754 1
                    'username' => $this->getContainer()
755 1
                        ->getParameter('liip_functional_test.authentication.username'),
756 1
                    'password' => $this->getContainer()
757 1
                        ->getParameter('liip_functional_test.authentication.password'),
758 1
                ];
759 1
            }
760
761 2
            $params = array_merge($params, [
762 2
                'PHP_AUTH_USER' => $authentication['username'],
763 2
                'PHP_AUTH_PW' => $authentication['password'],
764 2
            ]);
765 2
        }
766
767 55
        $client = static::createClient(['environment' => $this->environment], $params);
768
769 55
        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...
770
            // has to be set otherwise "hasPreviousSession" in Request returns false.
771 2
            $options = $client->getContainer()->getParameter('session.storage.options');
772
773 2
            if (!$options || !isset($options['name'])) {
774
                throw new \InvalidArgumentException('Missing session.storage.options#name');
775
            }
776
777 2
            $session = $client->getContainer()->get('session');
778
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
779 2
            if ($session instanceof Session) {
780 2
                $session->setId(uniqid());
781 2
            }
782
783 2
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
784
785
            /** @var $user UserInterface */
786 2
            foreach ($this->firewallLogins as $firewallName => $user) {
787 2
                $token = $this->createUserToken($user, $firewallName);
788
789
                // BC: security.token_storage is available on Symfony 2.6+
790
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
791 2
                if ($client->getContainer()->has('security.token_storage')) {
792 2
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
793 2
                } else {
794
                    // This block will never be reached with Symfony 2.6+
795
                    // @codeCoverageIgnoreStart
796
                    $tokenStorage = $client->getContainer()->get('security.context');
797
                    // @codeCoverageIgnoreEnd
798
                }
799
800 2
                $tokenStorage->setToken($token);
801 2
                $session->set('_security_'.$firewallName, serialize($token));
802 2
            }
803
804 2
            $session->save();
805 2
        }
806
807 55
        return $client;
808
    }
809
810
    /**
811
     * Create User Token.
812
     *
813
     * Factory method for creating a User Token object for the firewall based on
814
     * the user object provided. By default it will be a Username/Password
815
     * Token based on the user's credentials, but may be overridden for custom
816
     * tokens in your applications.
817
     *
818
     * @param UserInterface $user         The user object to base the token off of
819
     * @param string        $firewallName name of the firewall provider to use
820
     *
821
     * @return TokenInterface The token to be used in the security context
822
     */
823 2
    protected function createUserToken(UserInterface $user, $firewallName)
824
    {
825 2
        return new UsernamePasswordToken(
826 2
            $user,
827 2
            null,
828 2
            $firewallName,
829 2
            $user->getRoles()
830 2
        );
831
    }
832
833
    /**
834
     * Extracts the location from the given route.
835
     *
836
     * @param string $route    The name of the route
837
     * @param array  $params   Set of parameters
838
     * @param int    $absolute
839
     *
840
     * @return string
841
     */
842 1
    protected function getUrl($route, $params = [], $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
843
    {
844 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
845
    }
846
847
    /**
848
     * Checks the success state of a response.
849
     *
850
     * @param Response $response Response object
851
     * @param bool     $success  to define whether the response is expected to be successful
852
     * @param string   $type
853
     */
854 6
    public function isSuccessful(Response $response, $success = true, $type = 'text/html')
855
    {
856 6
        HttpAssertions::isSuccessful($response, $success, $type);
857 5
    }
858
859
    /**
860
     * Executes a request on the given url and returns the response contents.
861
     *
862
     * This method also asserts the request was successful.
863
     *
864
     * @param string $path           path of the requested page
865
     * @param string $method         The HTTP method to use, defaults to GET
866
     * @param bool   $authentication Whether to use authentication, defaults to false
867
     * @param bool   $success        to define whether the response is expected to be successful
868
     *
869
     * @return string
870
     */
871 1
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
872
    {
873 1
        $client = $this->makeClient($authentication);
874 1
        $client->request($method, $path);
875
876 1
        $content = $client->getResponse()->getContent();
877 1
        if (is_bool($success)) {
878 1
            $this->isSuccessful($client->getResponse(), $success);
0 ignored issues
show
Bug introduced by
It seems like $client->getResponse() can be null; however, isSuccessful() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
879 1
        }
880
881 1
        return $content;
882
    }
883
884
    /**
885
     * Executes a request on the given url and returns a Crawler object.
886
     *
887
     * This method also asserts the request was successful.
888
     *
889
     * @param string $path           path of the requested page
890
     * @param string $method         The HTTP method to use, defaults to GET
891
     * @param bool   $authentication Whether to use authentication, defaults to false
892
     * @param bool   $success        Whether the response is expected to be successful
893
     *
894
     * @return Crawler
895
     */
896 1
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
897
    {
898 1
        $client = $this->makeClient($authentication);
899 1
        $crawler = $client->request($method, $path);
900
901 1
        $this->isSuccessful($client->getResponse(), $success);
0 ignored issues
show
Bug introduced by
It seems like $client->getResponse() can be null; however, isSuccessful() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
902
903 1
        return $crawler;
904
    }
905
906
    /**
907
     * @param UserInterface $user
908
     * @param string        $firewallName
909
     *
910
     * @return WebTestCase
911
     */
912 2
    public function loginAs(UserInterface $user, $firewallName)
913
    {
914 2
        $this->firewallLogins[$firewallName] = $user;
915
916 2
        return $this;
917
    }
918
919
    /**
920
     * Asserts that the HTTP response code of the last request performed by
921
     * $client matches the expected code. If not, raises an error with more
922
     * information.
923
     *
924
     * @param $expectedStatusCode
925
     * @param Client $client
926
     */
927 12
    public function assertStatusCode($expectedStatusCode, Client $client)
928
    {
929 12
        HttpAssertions::assertStatusCode($expectedStatusCode, $client);
930 9
    }
931
932
    /**
933
     * Assert that the last validation errors within $container match the
934
     * expected keys.
935
     *
936
     * @param array              $expected  A flat array of field names
937
     * @param ContainerInterface $container
938
     */
939 3
    public function assertValidationErrors(array $expected, ContainerInterface $container)
940
    {
941 3
        HttpAssertions::assertValidationErrors($expected, $container);
942 1
    }
943
944
    /**
945
     * @param array $excludedDoctrineTables
946
     */
947 1
    public function setExcludedDoctrineTables($excludedDoctrineTables)
948
    {
949 1
        $this->excludedDoctrineTables = $excludedDoctrineTables;
950 1
    }
951
952 79
    protected function tearDown()
953
    {
954 79
        if (null !== $this->containers) {
955 48
            foreach ($this->containers as $container) {
956 48
                if ($container instanceof ResettableContainerInterface) {
957 48
                    $container->reset();
958 48
                }
959 48
            }
960 48
        }
961
962 79
        $this->containers = null;
963
964 79
        parent::tearDown();
965 79
    }
966
}
967