Passed
Pull Request — master (#347)
by Alexis
05:31
created

WebTestCase::configureVerbosityForSymfony203()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 0
cts 0
cp 0
rs 8.439
c 0
b 0
f 0
cc 5
eloc 15
nc 5
nop 1
crap 30

2 Methods

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