Completed
Push — master ( e00f97...9ea99b )
by Alexis
32:38 queued 24:59
created

WebTestCase::createApplication()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 1
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\StreamOutput;
19
use Symfony\Component\DomCrawler\Crawler;
20
use Symfony\Component\BrowserKit\Cookie;
21
use Symfony\Component\HttpKernel\Kernel;
22
use Symfony\Component\HttpFoundation\Response;
23
use Symfony\Component\HttpKernel\KernelInterface;
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\Driver\PDOSqlite\Driver as SqliteDriver;
37
use Doctrine\DBAL\Platforms\MySqlPlatform;
38
use Doctrine\ORM\EntityManager;
39
use Doctrine\ORM\Tools\SchemaTool;
40
use Nelmio\Alice\Fixtures;
41
use Liip\FunctionalTestBundle\Utils\HttpAssertions;
42
43
/**
44
 * @author Lea Haensenberger
45
 * @author Lukas Kahwe Smith <[email protected]>
46
 * @author Benjamin Eberlei <[email protected]>
47
 */
48
abstract class WebTestCase extends BaseWebTestCase
49
{
50
    protected $environment = 'test';
51
52
    protected $containers;
53
54
    protected $kernelDir;
55
56
    // 5 * 1024 * 1024 KB
57
    protected $maxMemory = 5242880;
58
59
    // RUN COMMAND
60
    protected $verbosityLevel;
61
62
    protected $decorated;
63
64
    /**
65
     * @var array
66
     */
67
    private $firewallLogins = array();
68
69
    /**
70
     * @var array
71
     */
72
    private $excludedDoctrineTables = array();
73
74
    /**
75
     * @var array
76
     */
77
    private static $cachedMetadatas = array();
78
79
    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...
80
    {
81
        $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...
82
83
        list($appname) = explode('\\', get_called_class());
84
85
        $class = $appname.'Kernel';
86
        $file = $dir.'/'.strtolower($appname).'/'.$class.'.php';
87
        if (!file_exists($file)) {
88
            return parent::getKernelClass();
89
        }
90
        require_once $file;
91
92
        return $class;
93
    }
94
95
    /**
96
     * Creates a mock object of a service identified by its id.
97
     *
98
     * @param string $id
99
     *
100
     * @return \PHPUnit_Framework_MockObject_MockBuilder
101
     */
102
    protected function getServiceMockBuilder($id)
103
    {
104
        $service = $this->getContainer()->get($id);
105
        $class = get_class($service);
106
107
        return $this->getMockBuilder($class)->disableOriginalConstructor();
108
    }
109
110
    /**
111
     * Builds up the environment to run the given command.
112
     *
113
     * @param string $name
114
     * @param array  $params
115
     * @param bool   $reuseKernel
116
     *
117
     * @return string
118
     */
119 12
    protected function runCommand($name, array $params = array(), $reuseKernel = false)
120
    {
121 12
        array_unshift($params, $name);
122
123 12
        if (!$reuseKernel) {
124 12
            if (null !== static::$kernel) {
125 9
                static::$kernel->shutdown();
126 9
            }
127
128 12
            $kernel = static::$kernel = $this->createKernel(array('environment' => $this->environment));
129 12
            $kernel->boot();
130 12
        } else {
131 2
            $kernel = $this->getContainer()->get('kernel');
132
        }
133
134 12
        $application = $this->createApplication($kernel);
135
136 12
        $input = new ArrayInput($params);
137 12
        $input->setInteractive(false);
138
139 12
        $fp = fopen('php://temp/maxmemory:'.$this->maxMemory, 'r+');
140 12
        $verbosityLevel = $this->getVerbosityLevel();
141
142 11
        $this->setVerbosityLevelEnv($verbosityLevel);
143 11
        $output = new StreamOutput($fp, $verbosityLevel, $this->getDecorated());
144
145 11
        $application->run($input, $output);
146
147 11
        rewind($fp);
148
149 11
        return stream_get_contents($fp);
150
    }
151
152
    /**
153
     * @param KernelInterface $kernel
154
     *
155
     * @return Application
156
     */
157 12
    protected function createApplication(KernelInterface $kernel)
158
    {
159 12
        $application = new Application($kernel);
160 12
        $application->setAutoExit(false);
161
162 12
        return $application;
163
    }
164
165
    /**
166
     * Retrieves the output verbosity level.
167
     *
168
     * @see \Symfony\Component\Console\Output\OutputInterface for available levels
169
     *
170
     * @return int
171
     *
172
     * @throws \OutOfBoundsException If the set value isn't accepted
173
     */
174 12
    protected function getVerbosityLevel()
175
    {
176
        // If `null`, is not yet set
177 12
        if (null === $this->verbosityLevel) {
178
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
179 6
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
180 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
181
182 6
            $this->verbosityLevel = constant($verbosity);
183 6
        }
184
185
        // If string, it is set by the developer, so check that the value is an accepted one
186 12
        if (is_string($this->verbosityLevel)) {
187 6
            $level = strtoupper($this->verbosityLevel);
188 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
189
190 6
            if (!defined($verbosity)) {
191 1
                throw new \OutOfBoundsException(
192 1
                    sprintf('The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".', $level)
193 1
                );
194
            }
195
196 5
            $this->verbosityLevel = constant($verbosity);
197 5
        }
198
199 11
        return $this->verbosityLevel;
200
    }
201
202 6
    public function setVerbosityLevel($level)
203
    {
204 6
        $this->verbosityLevel = $level;
205 6
    }
206
207
    /**
208
     * Set verbosity for Symfony 3.4+.
209
     *
210
     * @see https://github.com/symfony/symfony/pull/24425
211
     *
212
     * @param $level
213
     */
214 11
    private function setVerbosityLevelEnv($level)
215
    {
216 11
        putenv('SHELL_VERBOSITY='.$level);
217 11
    }
218
219
    /**
220
     * Retrieves the flag indicating if the output should be decorated or not.
221
     *
222
     * @return bool
223
     */
224 11
    protected function getDecorated()
225
    {
226 11
        if (null === $this->decorated) {
227
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
228 5
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
229 5
        }
230
231
        // Check the local decorated flag
232 11
        if (false === is_bool($this->decorated)) {
233
            throw new \OutOfBoundsException(
234
                sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated))
235
            );
236
        }
237
238 11
        return $this->decorated;
239
    }
240
241 6
    public function isDecorated($decorated)
242
    {
243 6
        $this->decorated = $decorated;
244 6
    }
245
246
    /**
247
     * Get an instance of the dependency injection container.
248
     * (this creates a kernel *without* parameters).
249
     *
250
     * @return ContainerInterface
251
     */
252 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...
253
    {
254 50
        if (!empty($this->kernelDir)) {
255
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
256
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
257
        }
258
259 50
        $cacheKey = $this->kernelDir.'|'.$this->environment;
260 50
        if (empty($this->containers[$cacheKey])) {
261
            $options = array(
262 49
                'environment' => $this->environment,
263 49
            );
264 49
            $kernel = $this->createKernel($options);
265 49
            $kernel->boot();
266
267 49
            $this->containers[$cacheKey] = $kernel->getContainer();
268 49
        }
269
270 50
        if (isset($tmpKernelDir)) {
271
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
272
        }
273
274 50
        return $this->containers[$cacheKey];
275
    }
276
277
    /**
278
     * This function finds the time when the data blocks of a class definition
279
     * file were being written to, that is, the time when the content of the
280
     * file was changed.
281
     *
282
     * @param string $class The fully qualified class name of the fixture class to
283
     *                      check modification date on
284
     *
285
     * @return \DateTime|null
286
     */
287 3
    protected function getFixtureLastModified($class)
288
    {
289 3
        $lastModifiedDateTime = null;
290
291 3
        $reflClass = new \ReflectionClass($class);
292 3
        $classFileName = $reflClass->getFileName();
293
294 3
        if (file_exists($classFileName)) {
295 3
            $lastModifiedDateTime = new \DateTime();
296 3
            $lastModifiedDateTime->setTimestamp(filemtime($classFileName));
297 3
        }
298
299 3
        return $lastModifiedDateTime;
300
    }
301
302
    /**
303
     * Determine if the Fixtures that define a database backup have been
304
     * modified since the backup was made.
305
     *
306
     * @param array  $classNames The fixture classnames to check
307
     * @param string $backup     The fixture backup SQLite database file path
308
     *
309
     * @return bool TRUE if the backup was made since the modifications to the
310
     *              fixtures; FALSE otherwise
311
     */
312 7
    protected function isBackupUpToDate(array $classNames, $backup)
313
    {
314 7
        $backupLastModifiedDateTime = new \DateTime();
315 7
        $backupLastModifiedDateTime->setTimestamp(filemtime($backup));
316
317
        /** @var \Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader $loader */
318 7
        $loader = $this->getFixtureLoader($this->getContainer(), $classNames);
319
320
        // Use loader in order to fetch all the dependencies fixtures.
321 7
        foreach ($loader->getFixtures() as $className) {
322 3
            $fixtureLastModifiedDateTime = $this->getFixtureLastModified($className);
323 3
            if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) {
324 1
                return false;
325
            }
326 7
        }
327
328 7
        return true;
329
    }
330
331
    /**
332
     * Set the database to the provided fixtures.
333
     *
334
     * Drops the current database and then loads fixtures using the specified
335
     * classes. The parameter is a list of fully qualified class names of
336
     * classes that implement Doctrine\Common\DataFixtures\FixtureInterface
337
     * so that they can be loaded by the DataFixtures Loader::addFixture
338
     *
339
     * When using SQLite this method will automatically make a copy of the
340
     * loaded schema and fixtures which will be restored automatically in
341
     * case the same fixture classes are to be loaded again. Caveat: changes
342
     * to references and/or identities may go undetected.
343
     *
344
     * Depends on the doctrine data-fixtures library being available in the
345
     * class path.
346
     *
347
     * @param array  $classNames   List of fully qualified class names of fixtures to load
348
     * @param string $omName       The name of object manager to use
349
     * @param string $registryName The service id of manager registry to use
350
     * @param int    $purgeMode    Sets the ORM purge mode
351
     *
352
     * @return null|AbstractExecutor
353
     */
354 36
    protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
355
    {
356 36
        $container = $this->getContainer();
357
        /** @var ManagerRegistry $registry */
358 36
        $registry = $container->get($registryName);
359
        /** @var ObjectManager $om */
360 36
        $om = $registry->getManager($omName);
361 36
        $type = $registry->getName();
362
363 36
        $executorClass = 'PHPCR' === $type && class_exists('Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor')
364 36
            ? 'Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor'
365 36
            : 'Doctrine\\Common\\DataFixtures\\Executor\\'.$type.'Executor';
366 36
        $referenceRepository = new ProxyReferenceRepository($om);
367 36
        $cacheDriver = $om->getMetadataFactory()->getCacheDriver();
368
369 36
        if ($cacheDriver) {
370 36
            $cacheDriver->deleteAll();
371 36
        }
372
373 36
        if ('ORM' === $type) {
374 35
            $connection = $om->getConnection();
375 35
            if ($connection->getDriver() instanceof SqliteDriver) {
376 30
                $params = $connection->getParams();
377 30
                if (isset($params['master'])) {
378
                    $params = $params['master'];
379
                }
380
381 30
                $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
382 30
                if (!$name) {
383
                    throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
384
                }
385
386 30
                if (!isset(self::$cachedMetadatas[$omName])) {
387 10
                    self::$cachedMetadatas[$omName] = $om->getMetadataFactory()->getAllMetadata();
388 10
                    usort(self::$cachedMetadatas[$omName], function ($a, $b) {
389
                        return strcmp($a->name, $b->name);
390 10
                    });
391 10
                }
392 30
                $metadatas = self::$cachedMetadatas[$omName];
393
394 30
                if ($container->getParameter('liip_functional_test.cache_sqlite_db')) {
395 9
                    $backup = $container->getParameter('kernel.cache_dir').'/test_'.md5(serialize($metadatas).serialize($classNames)).'.db';
396 9
                    if (file_exists($backup) && file_exists($backup.'.ser') && $this->isBackupUpToDate($classNames, $backup)) {
397 7
                        $connection = $this->getContainer()->get('doctrine.orm.entity_manager')->getConnection();
398 7
                        if (null !== $connection) {
399 7
                            $connection->close();
400 7
                        }
401
402 7
                        $om->flush();
403 7
                        $om->clear();
404
405 7
                        $this->preFixtureBackupRestore($om, $referenceRepository, $backup);
406
407 7
                        copy($backup, $name);
408
409 7
                        $executor = new $executorClass($om);
410 7
                        $executor->setReferenceRepository($referenceRepository);
411 7
                        $executor->getReferenceRepository()->load($backup);
412
413 7
                        $this->postFixtureBackupRestore($backup);
414
415 7
                        return $executor;
416
                    }
417 3
                }
418
419
                // TODO: handle case when using persistent connections. Fail loudly?
420 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...
421 24
                $schemaTool->dropDatabase();
422 24
                if (!empty($metadatas)) {
423 24
                    $schemaTool->createSchema($metadatas);
424 24
                }
425 24
                $this->postFixtureSetup();
426
427 24
                $executor = new $executorClass($om);
428 24
                $executor->setReferenceRepository($referenceRepository);
429 24
            }
430 29
        }
431
432 30
        if (empty($executor)) {
433 6
            $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
434 6
            if ('PHPCR' === $type) {
435 1
                $purger = new $purgerClass($om);
436 1
                $initManager = $container->has('doctrine_phpcr.initializer_manager')
437 1
                    ? $container->get('doctrine_phpcr.initializer_manager')
438 1
                    : null;
439
440 1
                $executor = new $executorClass($om, $purger, $initManager);
441 1
            } else {
442 5
                if ('ORM' === $type) {
443 5
                    $purger = new $purgerClass(null, $this->excludedDoctrineTables);
444 5
                } else {
445
                    $purger = new $purgerClass();
446
                }
447
448 5
                if (null !== $purgeMode) {
449 2
                    $purger->setPurgeMode($purgeMode);
450 2
                }
451
452 5
                $executor = new $executorClass($om, $purger);
453
            }
454
455 6
            $executor->setReferenceRepository($referenceRepository);
456 6
            $executor->purge();
457 6
        }
458
459 30
        $loader = $this->getFixtureLoader($container, $classNames);
460
461 30
        $executor->execute($loader->getFixtures(), true);
462
463 30
        if (isset($name) && isset($backup)) {
464 3
            $this->preReferenceSave($om, $executor, $backup);
465
466 3
            $executor->getReferenceRepository()->save($backup);
467 3
            copy($name, $backup);
468
469 3
            $this->postReferenceSave($om, $executor, $backup);
470 3
        }
471
472 30
        return $executor;
473
    }
474
475
    /**
476
     * Clean database.
477
     *
478
     * @param ManagerRegistry $registry
479
     * @param EntityManager   $om
480
     * @param null            $omName
481
     * @param string          $registryName
482
     * @param int             $purgeMode
483
     */
484 9
    private function cleanDatabase(ManagerRegistry $registry, EntityManager $om, $omName = null, $registryName = 'doctrine', $purgeMode = null)
485
    {
486 9
        $connection = $om->getConnection();
487
488 9
        $mysql = ('ORM' === $registry->getName()
489 9
            && $connection->getDatabasePlatform() instanceof MySqlPlatform);
490
491 9
        if ($mysql) {
492 1
            $connection->query('SET FOREIGN_KEY_CHECKS=0');
493 1
        }
494
495 9
        $this->loadFixtures(array(), $omName, $registryName, $purgeMode);
496
497 9
        if ($mysql) {
498 1
            $connection->query('SET FOREIGN_KEY_CHECKS=1');
499 1
        }
500 9
    }
501
502
    /**
503
     * Locate fixture files.
504
     *
505
     * @param array $paths
506
     *
507
     * @return array $files
508
     *
509
     * @throws \InvalidArgumentException if a wrong path is given outside a bundle
510
     */
511 10
    private function locateResources($paths)
512
    {
513 10
        $files = array();
514
515 10
        $kernel = $this->getContainer()->get('kernel');
516
517 10
        foreach ($paths as $path) {
518 10
            if ('@' !== $path[0]) {
519 3
                if (!file_exists($path)) {
520 1
                    throw new \InvalidArgumentException(sprintf('Unable to find file "%s".', $path));
521
                }
522 2
                $files[] = $path;
523
524 2
                continue;
525
            }
526
527 7
            $files[] = $kernel->locateResource($path);
528 8
        }
529
530 8
        return $files;
531
    }
532
533
    /**
534
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
535
     * @param bool   $append
536
     * @param null   $omName
537
     * @param string $registryName
538
     * @param int    $purgeMode
539
     *
540
     * @return array
541
     *
542
     * @throws \BadMethodCallException
543
     */
544 10
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine', $purgeMode = null)
545
    {
546 10
        if (!class_exists('Nelmio\Alice\Fixtures')) {
547
            // This class is available during tests, no exception will be thrown.
548
            // @codeCoverageIgnoreStart
549
            throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
550
            // @codeCoverageIgnoreEnd
551
        }
552
553
        /** @var ContainerInterface $container */
554 10
        $container = $this->getContainer();
555
556
        /** @var ManagerRegistry $registry */
557 10
        $registry = $container->get($registryName);
558
559
        /** @var EntityManager $om */
560 10
        $om = $registry->getManager($omName);
561
562 10
        if (false === $append) {
563 9
            $this->cleanDatabase($registry, $om, $omName, $registryName, $purgeMode);
564 9
        }
565
566 10
        $files = $this->locateResources($paths);
567
568
        // Check if the Hautelook AliceBundle is registered and if yes, use it instead of Nelmio Alice
569 8
        $hautelookLoaderServiceName = 'hautelook_alice.fixtures.loader';
570 8
        if ($container->has($hautelookLoaderServiceName)) {
571 3
            $loaderService = $container->get($hautelookLoaderServiceName);
572 3
            $persisterClass = class_exists('Nelmio\Alice\ORM\Doctrine') ?
573 3
                'Nelmio\Alice\ORM\Doctrine' :
574 3
                'Nelmio\Alice\Persister\Doctrine';
575
576 3
            return $loaderService->load(new $persisterClass($om), $files);
577
        }
578
579 5
        return Fixtures::load($files, $om);
580
    }
581
582
    /**
583
     * Callback function to be executed after Schema creation.
584
     * Use this to execute acl:init or other things necessary.
585
     */
586 24
    protected function postFixtureSetup()
587
    {
588 24
    }
589
590
    /**
591
     * Callback function to be executed after Schema restore.
592
     *
593
     * @return WebTestCase
594
     *
595
     * @deprecated since version 1.8, to be removed in 2.0. Use postFixtureBackupRestore method instead.
596
     */
597 7
    protected function postFixtureRestore()
598
    {
599 7
    }
600
601
    /**
602
     * Callback function to be executed before Schema restore.
603
     *
604
     * @param ObjectManager            $manager             The object manager
605
     * @param ProxyReferenceRepository $referenceRepository The reference repository
606
     *
607
     * @return WebTestCase
608
     *
609
     * @deprecated since version 1.8, to be removed in 2.0. Use preFixtureBackupRestore method instead.
610
     */
611 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...
612
    {
613 7
    }
614
615
    /**
616
     * Callback function to be executed after Schema restore.
617
     *
618
     * @param string $backupFilePath Path of file used to backup the references of the data fixtures
619
     *
620
     * @return WebTestCase
621
     */
622 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...
623
    {
624 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...
625
626 7
        return $this;
627
    }
628
629
    /**
630
     * Callback function to be executed before Schema restore.
631
     *
632
     * @param ObjectManager            $manager             The object manager
633
     * @param ProxyReferenceRepository $referenceRepository The reference repository
634
     * @param string                   $backupFilePath      Path of file used to backup the references of the data fixtures
635
     *
636
     * @return WebTestCase
637
     */
638 7
    protected function preFixtureBackupRestore(
639
        ObjectManager $manager,
640
        ProxyReferenceRepository $referenceRepository,
641
        $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...
642
    ) {
643 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...
644
645 7
        return $this;
646
    }
647
648
    /**
649
     * Callback function to be executed after save of references.
650
     *
651
     * @param ObjectManager    $manager        The object manager
652
     * @param AbstractExecutor $executor       Executor of the data fixtures
653
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
654
     *
655
     * @return WebTestCase
656
     */
657 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...
658
    {
659 3
    }
660
661
    /**
662
     * Callback function to be executed before save of references.
663
     *
664
     * @param ObjectManager    $manager        The object manager
665
     * @param AbstractExecutor $executor       Executor of the data fixtures
666
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
667
     *
668
     * @return WebTestCase
669
     */
670 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...
671
    {
672 3
    }
673
674
    /**
675
     * Retrieve Doctrine DataFixtures loader.
676
     *
677
     * @param ContainerInterface $container
678
     * @param array              $classNames
679
     *
680
     * @return Loader
681
     */
682 36
    protected function getFixtureLoader(ContainerInterface $container, array $classNames)
683
    {
684 36
        $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
685 36
            ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
686 36
            : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
687
                // This class is not available during tests.
688
                // @codeCoverageIgnoreStart
689
                ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
690
                // @codeCoverageIgnoreEnd
691 36
                : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
692
693 36
        $loader = new $loaderClass($container);
694
695 36
        foreach ($classNames as $className) {
696 11
            $this->loadFixtureClass($loader, $className);
697 36
        }
698
699 36
        return $loader;
700
    }
701
702
    /**
703
     * Load a data fixture class.
704
     *
705
     * @param Loader $loader
706
     * @param string $className
707
     */
708 11
    protected function loadFixtureClass($loader, $className)
709
    {
710 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...
711
712 11
        if ($this->getContainer()->has($className)) {
713
            $fixture = $this->getContainer()->get($className);
714
        } else {
715 11
            $fixture = new $className();
716
        }
717
718 11
        if ($loader->hasFixture($fixture)) {
719 2
            unset($fixture);
720
721 2
            return;
722
        }
723
724 11
        $loader->addFixture($fixture);
725
726 11
        if ($fixture instanceof DependentFixtureInterface) {
727 2
            foreach ($fixture->getDependencies() as $dependency) {
728 2
                $this->loadFixtureClass($loader, $dependency);
729 2
            }
730 2
        }
731 11
    }
732
733
    /**
734
     * Creates an instance of a lightweight Http client.
735
     *
736
     * If $authentication is set to 'true' it will use the content of
737
     * 'liip_functional_test.authentication' to log in.
738
     *
739
     * $params can be used to pass headers to the client, note that they have
740
     * to follow the naming format used in $_SERVER.
741
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
742
     *
743
     * @param bool|array $authentication
744
     * @param array      $params
745
     *
746
     * @return Client
747
     */
748 55
    protected function makeClient($authentication = false, array $params = array())
749
    {
750 55
        if ($authentication) {
751 2
            if (true === $authentication) {
752
                $authentication = array(
753 1
                    'username' => $this->getContainer()
754 1
                        ->getParameter('liip_functional_test.authentication.username'),
755 1
                    'password' => $this->getContainer()
756 1
                        ->getParameter('liip_functional_test.authentication.password'),
757 1
                );
758 1
            }
759
760 2
            $params = array_merge($params, array(
761 2
                'PHP_AUTH_USER' => $authentication['username'],
762 2
                'PHP_AUTH_PW' => $authentication['password'],
763 2
            ));
764 2
        }
765
766 55
        $client = static::createClient(array('environment' => $this->environment), $params);
767
768 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...
769
            // has to be set otherwise "hasPreviousSession" in Request returns false.
770 2
            $options = $client->getContainer()->getParameter('session.storage.options');
771
772 2
            if (!$options || !isset($options['name'])) {
773
                throw new \InvalidArgumentException('Missing session.storage.options#name');
774
            }
775
776 2
            $session = $client->getContainer()->get('session');
777
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
778 2
            if ($session instanceof Session) {
779 2
                $session->setId(uniqid());
780 2
            }
781
782 2
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
783
784
            /** @var $user UserInterface */
785 2
            foreach ($this->firewallLogins as $firewallName => $user) {
786 2
                $token = $this->createUserToken($user, $firewallName);
787
788
                // BC: security.token_storage is available on Symfony 2.6+
789
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
790 2
                if ($client->getContainer()->has('security.token_storage')) {
791 2
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
792 2
                } else {
793
                    // This block will never be reached with Symfony 2.6+
794
                    // @codeCoverageIgnoreStart
795
                    $tokenStorage = $client->getContainer()->get('security.context');
796
                    // @codeCoverageIgnoreEnd
797
                }
798
799 2
                $tokenStorage->setToken($token);
800 2
                $session->set('_security_'.$firewallName, serialize($token));
801 2
            }
802
803 2
            $session->save();
804 2
        }
805
806 55
        return $client;
807
    }
808
809
    /**
810
     * Create User Token.
811
     *
812
     * Factory method for creating a User Token object for the firewall based on
813
     * the user object provided. By default it will be a Username/Password
814
     * Token based on the user's credentials, but may be overridden for custom
815
     * tokens in your applications.
816
     *
817
     * @param UserInterface $user         The user object to base the token off of
818
     * @param string        $firewallName name of the firewall provider to use
819
     *
820
     * @return TokenInterface The token to be used in the security context
821
     */
822 2
    protected function createUserToken(UserInterface $user, $firewallName)
823
    {
824 2
        return new UsernamePasswordToken(
825 2
            $user,
826 2
            null,
827 2
            $firewallName,
828 2
            $user->getRoles()
0 ignored issues
show
Documentation introduced by
$user->getRoles() is of type array<integer,object<Sym...Core\Role\Role>|string>, but the function expects a array<integer,object<Sym...\RoleInterface>|string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
829 2
        );
830
    }
831
832
    /**
833
     * Extracts the location from the given route.
834
     *
835
     * @param string $route    The name of the route
836
     * @param array  $params   Set of parameters
837
     * @param int    $absolute
838
     *
839
     * @return string
840
     */
841 1
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
842
    {
843 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
844
    }
845
846
    /**
847
     * Checks the success state of a response.
848
     *
849
     * @param Response $response Response object
850
     * @param bool     $success  to define whether the response is expected to be successful
851
     * @param string   $type
852
     */
853 6
    public function isSuccessful(Response $response, $success = true, $type = 'text/html')
854
    {
855 6
        HttpAssertions::isSuccessful($response, $success, $type);
856 5
    }
857
858
    /**
859
     * Executes a request on the given url and returns the response contents.
860
     *
861
     * This method also asserts the request was successful.
862
     *
863
     * @param string $path           path of the requested page
864
     * @param string $method         The HTTP method to use, defaults to GET
865
     * @param bool   $authentication Whether to use authentication, defaults to false
866
     * @param bool   $success        to define whether the response is expected to be successful
867
     *
868
     * @return string
869
     */
870 1
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
871
    {
872 1
        $client = $this->makeClient($authentication);
873 1
        $client->request($method, $path);
874
875 1
        $content = $client->getResponse()->getContent();
876 1
        if (is_bool($success)) {
877 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...
878 1
        }
879
880 1
        return $content;
881
    }
882
883
    /**
884
     * Executes a request on the given url and returns a Crawler object.
885
     *
886
     * This method also asserts the request was successful.
887
     *
888
     * @param string $path           path of the requested page
889
     * @param string $method         The HTTP method to use, defaults to GET
890
     * @param bool   $authentication Whether to use authentication, defaults to false
891
     * @param bool   $success        Whether the response is expected to be successful
892
     *
893
     * @return Crawler
894
     */
895 1
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
896
    {
897 1
        $client = $this->makeClient($authentication);
898 1
        $crawler = $client->request($method, $path);
899
900 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...
901
902 1
        return $crawler;
903
    }
904
905
    /**
906
     * @param UserInterface $user
907
     * @param string        $firewallName
908
     *
909
     * @return WebTestCase
910
     */
911 2
    public function loginAs(UserInterface $user, $firewallName)
912
    {
913 2
        $this->firewallLogins[$firewallName] = $user;
914
915 2
        return $this;
916
    }
917
918
    /**
919
     * Asserts that the HTTP response code of the last request performed by
920
     * $client matches the expected code. If not, raises an error with more
921
     * information.
922
     *
923
     * @param $expectedStatusCode
924
     * @param Client $client
925
     */
926 12
    public function assertStatusCode($expectedStatusCode, Client $client)
927
    {
928 12
        HttpAssertions::assertStatusCode($expectedStatusCode, $client);
929 9
    }
930
931
    /**
932
     * Assert that the last validation errors within $container match the
933
     * expected keys.
934
     *
935
     * @param array              $expected  A flat array of field names
936
     * @param ContainerInterface $container
937
     */
938 3
    public function assertValidationErrors(array $expected, ContainerInterface $container)
939
    {
940 3
        HttpAssertions::assertValidationErrors($expected, $container);
941 1
    }
942
943
    /**
944
     * @param array $excludedDoctrineTables
945
     */
946 1
    public function setExcludedDoctrineTables($excludedDoctrineTables)
947
    {
948 1
        $this->excludedDoctrineTables = $excludedDoctrineTables;
949 1
    }
950
}
951