Completed
Pull Request — master (#230)
by Adamo
08:36
created

WebTestCase::getDecorated()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.3332

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 16
ccs 6
cts 9
cp 0.6667
rs 9.4285
cc 3
eloc 7
nc 4
nop 0
crap 3.3332
1
<?php
2
3
/*
4
 * This file is part of the Liip/FunctionalTestBundle
5
 *
6
 * (c) Lukas Kahwe Smith <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Liip\FunctionalTestBundle\Test;
13
14
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
15
use Symfony\Bundle\FrameworkBundle\Console\Application;
16
use Symfony\Bundle\FrameworkBundle\Client;
17
use Symfony\Component\Console\Input\ArrayInput;
18
use Symfony\Component\Console\Output\OutputInterface;
19
use Symfony\Component\Console\Output\StreamOutput;
20
use Symfony\Component\DomCrawler\Crawler;
21
use Symfony\Component\BrowserKit\Cookie;
22
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
23
use Symfony\Component\Security\Core\User\UserInterface;
24
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
25
use Symfony\Component\DependencyInjection\ContainerInterface;
26
use Symfony\Component\HttpFoundation\Session\Session;
27
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
28
use Symfony\Bridge\Doctrine\ManagerRegistry;
29
use Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader;
30
use Doctrine\Common\Persistence\ObjectManager;
31
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
32
use Doctrine\Common\DataFixtures\Executor\AbstractExecutor;
33
use Doctrine\Common\DataFixtures\ProxyReferenceRepository;
34
use Doctrine\DBAL\Driver\PDOSqlite\Driver as SqliteDriver;
35
use Doctrine\DBAL\Platforms\MySqlPlatform;
36
use Doctrine\ORM\Tools\SchemaTool;
37
use Nelmio\Alice\Fixtures;
38
39
/**
40
 * @author Lea Haensenberger
41
 * @author Lukas Kahwe Smith <[email protected]>
42
 * @author Benjamin Eberlei <[email protected]>
43
 */
44
abstract class WebTestCase extends BaseWebTestCase
45
{
46
    protected $environment = 'test';
47
    protected $containers;
48
    protected $kernelDir;
49
    // 5 * 1024 * 1024 KB
50
    protected $maxMemory = 5242880;
51
52
    // RUN COMMAND
53
    protected $verbosityLevel;
54
    protected $decorated;
55
56
    /**
57
     * @var array
58
     */
59
    private $firewallLogins = array();
60
61
    /**
62
     * @var array
63
     */
64
    private static $cachedMetadatas = array();
65
66 1
    protected static function getKernelClass()
0 ignored issues
show
Coding Style introduced by
getKernelClass uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
67
    {
68 1
        $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : self::getPhpUnitXmlDir();
69
70 1
        list($appname) = explode('\\', get_called_class());
71
72 1
        $class = $appname.'Kernel';
73 1
        $file = $dir.'/'.strtolower($appname).'/'.$class.'.php';
74 1
        if (!file_exists($file)) {
75 1
            return parent::getKernelClass();
76
        }
77
        require_once $file;
78
79
        return $class;
80
    }
81
82
    /**
83
     * Creates a mock object of a service identified by its id.
84
     *
85
     * @param string $id
86
     *
87
     * @return PHPUnit_Framework_MockObject_MockBuilder
88
     */
89 1
    protected function getServiceMockBuilder($id)
90
    {
91
        $service = $this->getContainer()->get($id);
92
        $class = get_class($service);
93
94
        return $this->getMockBuilder($class)->disableOriginalConstructor();
95 1
    }
96
97
    /**
98
     * Builds up the environment to run the given command.
99
     *
100
     * @param string $name
101
     * @param array  $params
102
     * @param bool   $reuseKernel
103
     *
104
     * @return string
105
     */
106 9
    protected function runCommand($name, array $params = array(), $reuseKernel = false)
107
    {
108 9
        array_unshift($params, $name);
109
110 9
        if (!$reuseKernel) {
111 9
            if (null !== static::$kernel) {
112 8
                static::$kernel->shutdown();
113 8
            }
114
115 9
            $kernel = static::$kernel = $this->createKernel(array('environment' => $this->environment));
116 9
            $kernel->boot();
117 9
        } else {
118 2
            $kernel = $this->getContainer()->get('kernel');
119
        }
120
121 9
        $application = new Application($kernel);
122 9
        $application->setAutoExit(false);
123
124 9
        if (\Symfony\Component\HttpKernel\Kernel::VERSION_ID === '20301') {
125
            $params = $this->configureVerbosityForSymfony20301($params);
126
        }
127
128 9
        $input = new ArrayInput($params);
129 9
        $input->setInteractive(false);
130
131 9
        $fp = fopen('php://temp/maxmemory:'.$this->maxMemory, 'r+');
132 9
        $output = new StreamOutput($fp, $this->getVerbosityLevel(), $this->getDecorated());
133
134 9
        $application->run($input, $output);
135
136 9
        rewind($fp);
137
138 9
        return stream_get_contents($fp);
139
    }
140
141
    /**
142
     * Retrieves the output verbosity level.
143
     *
144
     * @see Symfony\Component\Console\Output\OutputInterface for available levels
145
     *
146
     * @return integer
147
     * @throws \OutOfBoundsException If the set value isn't accepted
148
     */
149 9
    protected function getVerbosityLevel()
150
    {
151
        // If `null`, is not yet set
152 9
        if (null === $this->verbosityLevel) {
153
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
154 4
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
155 4
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
156
157 4
            $this->verbosityLevel = constant($verbosity);
158 4
        }
159
160
        // If string, it is set by the developer, so check that the value is an accepted one
161 9
        if (is_string($this->verbosityLevel)) {
162 5
            $level = strtoupper($this->verbosityLevel);
163 5
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
164
165 5
            if (null === constant($verbosity)) {
166
                throw new \OutOfBoundsException(
167
                    'The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".
168
                    ');
169
            }
170
171 5
            $this->verbosityLevel = constant($verbosity);
172 5
        }
173
174 9
        return $this->verbosityLevel;
175
    }
176
177
    private function configureVerbosityForSymfony20301(array $params) {
178
        switch($this->getVerbosityLevel()) {
179
            case OutputInterface::VERBOSITY_QUIET:
180
                $params['-q'] = '-q';
181
                break;
182
183
            case OutputInterface::VERBOSITY_NORMAL:
184
                $params['-v'] = '-v';
185
                break;
186
187
            case OutputInterface::VERBOSITY_VERBOSE:
188
                $params['-vv'] = '-vv';
189
                break;
190
191
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
192
            case OutputInterface::VERBOSITY_DEBUG:
193
                $params['-vvv'] = '-vvv';
194
                break;
195
        }
196
197
        return $params;
198
    }
199
200 5
    public function setVerbosityLevel($level)
201
    {
202 5
        $this->verbosityLevel = $level;
203 5
    }
204
205
    /**
206
     * Retrieves the flag indicating if the output should be decorated or not.
207
     *
208
     * @return bool
209
     */
210 9
    protected function getDecorated()
211
    {
212 9
        if (null === $this->decorated) {
213
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
214 3
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
215 3
        }
216
217
        // Check the local decorated flag
218 9
        if (false === is_bool($this->decorated)) {
219
            throw new \OutOfBoundsException(
220
                sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated))
221
            );
222
        }
223
224 9
        return $this->decorated;
225
    }
226
227 6
    public function isDecorated($decorated)
228
    {
229 6
        $this->decorated = $decorated;
230 6
    }
231
232
    /**
233
     * Get an instance of the dependency injection container.
234
     * (this creates a kernel *without* parameters).
235
     *
236
     * @return ContainerInterface
237
     */
238 29
    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...
239
    {
240 29
        if (!empty($this->kernelDir)) {
241
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
242
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
243
        }
244
245 29
        $cacheKey = $this->kernelDir.'|'.$this->environment;
246 29
        if (empty($this->containers[$cacheKey])) {
247
            $options = array(
248 29
                'environment' => $this->environment,
249 29
            );
250 29
            $kernel = $this->createKernel($options);
251 29
            $kernel->boot();
252
253 29
            $this->containers[$cacheKey] = $kernel->getContainer();
254 29
        }
255
256 29
        if (isset($tmpKernelDir)) {
257
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
258
        }
259
260 29
        return $this->containers[$cacheKey];
261
    }
262
263
    /**
264
     * This function finds the time when the data blocks of a class definition
265
     * file were being written to, that is, the time when the content of the
266
     * file was changed.
267
     *
268
     * @param string $class The fully qualified class name of the fixture class to
269
     *                      check modification date on.
270
     *
271
     * @return \DateTime|null
272
     */
273 4
    protected function getFixtureLastModified($class)
274
    {
275 4
        $lastModifiedDateTime = null;
276
277 4
        $reflClass = new \ReflectionClass($class);
278 4
        $classFileName = $reflClass->getFileName();
279
280 4
        if (file_exists($classFileName)) {
281 4
            $lastModifiedDateTime = new \DateTime();
282 4
            $lastModifiedDateTime->setTimestamp(filemtime($classFileName));
283 4
        }
284
285 4
        return $lastModifiedDateTime;
286
    }
287
288
    /**
289
     * Determine if the Fixtures that define a database backup have been
290
     * modified since the backup was made.
291
     *
292
     * @param array  $classNames The fixture classnames to check
293
     * @param string $backup     The fixture backup SQLite database file path
294
     *
295
     * @return bool TRUE if the backup was made since the modifications to the
296
     *              fixtures; FALSE otherwise
297
     */
298 22
    protected function isBackupUpToDate(array $classNames, $backup)
299
    {
300 22
        $backupLastModifiedDateTime = new \DateTime();
301 22
        $backupLastModifiedDateTime->setTimestamp(filemtime($backup));
302
303 22
        foreach ($classNames as &$className) {
304 4
            $fixtureLastModifiedDateTime = $this->getFixtureLastModified($className);
305 4
            if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) {
306
                return false;
307
            }
308 22
        }
309
310 22
        return true;
311
    }
312
313
    /**
314
     * Set the database to the provided fixtures.
315
     *
316
     * Drops the current database and then loads fixtures using the specified
317
     * classes. The parameter is a list of fully qualified class names of
318
     * classes that implement Doctrine\Common\DataFixtures\FixtureInterface
319
     * so that they can be loaded by the DataFixtures Loader::addFixture
320
     *
321
     * When using SQLite this method will automatically make a copy of the
322
     * loaded schema and fixtures which will be restored automatically in
323
     * case the same fixture classes are to be loaded again. Caveat: changes
324
     * to references and/or identities may go undetected.
325
     *
326
     * Depends on the doctrine data-fixtures library being available in the
327
     * class path.
328
     *
329
     * @param array  $classNames   List of fully qualified class names of fixtures to load
330
     * @param string $omName       The name of object manager to use
331
     * @param string $registryName The service id of manager registry to use
332
     * @param int    $purgeMode    Sets the ORM purge mode
333
     *
334
     * @return null|AbstractExecutor
335
     */
336 24
    protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
337
    {
338 24
        $container = $this->getContainer();
339
        /** @var ManagerRegistry $registry */
340 24
        $registry = $container->get($registryName);
341 24
        $om = $registry->getManager($omName);
342 24
        $type = $registry->getName();
343
344 24
        $executorClass = 'PHPCR' === $type && class_exists('Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor')
345 24
            ? 'Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor'
346 24
            : 'Doctrine\\Common\\DataFixtures\\Executor\\'.$type.'Executor';
347 24
        $referenceRepository = new ProxyReferenceRepository($om);
348 24
        $cacheDriver = $om->getMetadataFactory()->getCacheDriver();
349
350 24
        if ($cacheDriver) {
351 24
            $cacheDriver->deleteAll();
352 24
        }
353
354 24
        if ('ORM' === $type) {
355 24
            $connection = $om->getConnection();
356 24
            if ($connection->getDriver() instanceof SqliteDriver) {
357 24
                $params = $connection->getParams();
358 24
                if (isset($params['master'])) {
359
                    $params = $params['master'];
360
                }
361
362 24
                $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
363 24
                if (!$name) {
364
                    throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
365
                }
366
367 24
                if (!isset(self::$cachedMetadatas[$omName])) {
368 1
                    self::$cachedMetadatas[$omName] = $om->getMetadataFactory()->getAllMetadata();
369
                    usort(self::$cachedMetadatas[$omName], function ($a, $b) { return strcmp($a->name, $b->name); });
370 1
                }
371 24
                $metadatas = self::$cachedMetadatas[$omName];
372
373 24
                if ($container->getParameter('liip_functional_test.cache_sqlite_db')) {
374 24
                    $backup = $container->getParameter('kernel.cache_dir').'/test_'.md5(serialize($metadatas).serialize($classNames)).'.db';
375 24
                    if (file_exists($backup) && file_exists($backup.'.ser') && $this->isBackupUpToDate($classNames, $backup)) {
376 22
                        $om->flush();
377 22
                        $om->clear();
378
379 22
                        $this->preFixtureRestore($om, $referenceRepository);
380
381 22
                        copy($backup, $name);
382
383 22
                        $executor = new $executorClass($om);
384 22
                        $executor->setReferenceRepository($referenceRepository);
385 22
                        $executor->getReferenceRepository()->load($backup);
386
387 22
                        $this->postFixtureRestore();
388
389 22
                        return $executor;
390
                    }
391 2
                }
392
393
                // TODO: handle case when using persistent connections. Fail loudly?
394 2
                $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...
395 2
                $schemaTool->dropDatabase($name);
0 ignored issues
show
Unused Code introduced by
The call to SchemaTool::dropDatabase() has too many arguments starting with $name.

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

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

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

Loading history...
396 2
                if (!empty($metadatas)) {
397 2
                    $schemaTool->createSchema($metadatas);
398 2
                }
399 2
                $this->postFixtureSetup();
400
401 2
                $executor = new $executorClass($om);
402 2
                $executor->setReferenceRepository($referenceRepository);
403 2
            }
404 2
        }
405
406 2
        if (empty($executor)) {
407
            $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
408
            if ('PHPCR' === $type) {
409
                $purger = new $purgerClass($om);
410
                $initManager = $container->has('doctrine_phpcr.initializer_manager')
411
                    ? $container->get('doctrine_phpcr.initializer_manager')
412
                    : null;
413
414
                $executor = new $executorClass($om, $purger, $initManager);
415
            } else {
416
                $purger = new $purgerClass();
417
                if (null !== $purgeMode) {
418
                    $purger->setPurgeMode($purgeMode);
419
                }
420
421
                $executor = new $executorClass($om, $purger);
422
            }
423
424
            $executor->setReferenceRepository($referenceRepository);
425
            $executor->purge();
426
        }
427
428 2
        $loader = $this->getFixtureLoader($container, $classNames);
429
430 2
        $executor->execute($loader->getFixtures(), true);
431
432 2
        if (isset($name) && isset($backup)) {
433 2
            $this->preReferenceSave($om, $executor, $backup);
434
435 2
            $executor->getReferenceRepository()->save($backup);
436 2
            copy($name, $backup);
437
438 2
            $this->postReferenceSave($om, $executor, $backup);
439 2
        }
440
441 2
        return $executor;
442
    }
443
444
    /**
445
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
446
     * @param bool   $append
447
     * @param null   $omName
448
     * @param string $registryName
449
     *
450
     * @return array
451
     *
452
     * @throws \BadMethodCallException
453
     */
454 2
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine')
455
    {
456 2
        if (!class_exists('Nelmio\Alice\Fixtures')) {
457
            throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
458
        }
459
460
        /** @var ManagerRegistry $registry */
461 2
        $registry = $this->getContainer()->get($registryName);
462 2
        $om = $registry->getManager($omName);
463
464 2
        if ($append === false) {
465
            //Clean database
466 2
            $connection = $om->getConnection();
467 2
            if ($registry->getName() === 'ORM' && $connection->getDatabasePlatform() instanceof MySqlPlatform) {
468
                $connection->query('SET FOREIGN_KEY_CHECKS=0');
469
            }
470
471 2
            $this->loadFixtures(array());
472
473 2
            if ($registry->getName() === 'ORM' && $connection->getDatabasePlatform() instanceof MySqlPlatform) {
474
                $connection->query('SET FOREIGN_KEY_CHECKS=1');
475
            }
476 2
        }
477
478 2
        $files = array();
479 2
        $kernel = $this->getContainer()->get('kernel');
480 2
        foreach ($paths as $path) {
481 2
            if ($path[0] !== '@' && file_exists($path) === true) {
482 1
                $files[] = $path;
483 1
                continue;
484
            }
485
486 1
            $files[] = $kernel->locateResource($path);
487 2
        }
488
489 2
        return Fixtures::load($files, $om);
490
    }
491
492
    /**
493
     * Callback function to be executed after Schema creation.
494
     * Use this to execute acl:init or other things necessary.
495
     */
496 2
    protected function postFixtureSetup()
497
    {
498 2
    }
499
500
    /**
501
     * Callback function to be executed after Schema restore.
502
     *
503
     * @return WebTestCase
504
     */
505 22
    protected function postFixtureRestore()
506
    {
507 22
    }
508
509
    /**
510
     * Callback function to be executed before Schema restore.
511
     *
512
     * @param ObjectManager            $manager             The object manager
513
     * @param ProxyReferenceRepository $referenceRepository The reference repository
514
     *
515
     * @return WebTestCase
516
     */
517 22
    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...
518
    {
519 22
    }
520
521
    /**
522
     * Callback function to be executed after save of references.
523
     *
524
     * @param ObjectManager    $manager        The object manager
525
     * @param AbstractExecutor $executor       Executor of the data fixtures
526
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
527
     *
528
     * @return WebTestCase
529
     */
530 2
    protected function postReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
0 ignored issues
show
Unused Code introduced by
The parameter $manager is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $executor is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $backupFilePath is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
531
    {
532 2
    }
533
534
    /**
535
     * Callback function to be executed before save of references.
536
     *
537
     * @param ObjectManager    $manager        The object manager
538
     * @param AbstractExecutor $executor       Executor of the data fixtures
539
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
540
     *
541
     * @return WebTestCase
542
     */
543 2
    protected function preReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
0 ignored issues
show
Unused Code introduced by
The parameter $manager is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $executor is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $backupFilePath is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
544
    {
545 2
    }
546
547
    /**
548
     * Retrieve Doctrine DataFixtures loader.
549
     *
550
     * @param ContainerInterface $container
551
     * @param array              $classNames
552
     *
553
     * @return Loader
554
     */
555 2
    protected function getFixtureLoader(ContainerInterface $container, array $classNames)
556
    {
557 2
        $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
558 2
            ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
559 2
            : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
560
                ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
561 2
                : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
562
563 2
        $loader = new $loaderClass($container);
564
565 2
        foreach ($classNames as $className) {
566 1
            $this->loadFixtureClass($loader, $className);
567 2
        }
568
569 2
        return $loader;
570
    }
571
572
    /**
573
     * Load a data fixture class.
574
     *
575
     * @param Loader $loader
576
     * @param string $className
577
     */
578 1
    protected function loadFixtureClass($loader, $className)
579
    {
580 1
        $fixture = new $className();
581
582 1
        if ($loader->hasFixture($fixture)) {
583
            unset($fixture);
584
585
            return;
586
        }
587
588 1
        $loader->addFixture($fixture);
589
590 1
        if ($fixture instanceof DependentFixtureInterface) {
591
            foreach ($fixture->getDependencies() as $dependency) {
592
                $this->loadFixtureClass($loader, $dependency);
593
            }
594
        }
595 1
    }
596
597
    /**
598
     * Creates an instance of a lightweight Http client.
599
     *
600
     * If $authentication is set to 'true' it will use the content of
601
     * 'liip_functional_test.authentication' to log in.
602
     *
603
     * $params can be used to pass headers to the client, note that they have
604
     * to follow the naming format used in $_SERVER.
605
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
606
     *
607
     * @param bool|array $authentication
608
     * @param array      $params
609
     *
610
     * @return Client
611
     */
612 24
    protected function makeClient($authentication = false, array $params = array())
613
    {
614 24
        if ($authentication) {
615 5
            if ($authentication === true) {
616 3
                $authentication = $this->getContainer()->getParameter('liip_functional_test.authentication');
617 3
            }
618
619 5
            $params = array_merge($params, array(
620 5
                'PHP_AUTH_USER' => $authentication['username'],
621 5
                'PHP_AUTH_PW' => $authentication['password'],
622 5
            ));
623 5
        }
624
625 24
        $client = static::createClient(array('environment' => $this->environment), $params);
626
627 24
        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...
628
            // has to be set otherwise "hasPreviousSession" in Request returns false.
629 1
            $options = $client->getContainer()->getParameter('session.storage.options');
630
631 1
            if (!$options || !isset($options['name'])) {
632
                throw new \InvalidArgumentException('Missing session.storage.options#name');
633
            }
634
635 1
            $session = $client->getContainer()->get('session');
636
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
637 1
            if ($session instanceof Session) {
638 1
                $session->setId(uniqid());
639 1
            }
640
641 1
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
642
643
            /** @var $user UserInterface */
644 1
            foreach ($this->firewallLogins as $firewallName => $user) {
645 1
                $token = $this->createUserToken($user, $firewallName);
646
647
                // BC: security.token_storage is available on Symfony 2.6+
648
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
649 1
                if ($client->getContainer()->has('security.token_storage')) {
650 1
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
651 1
                } else {
652
                    // This block will never be reached with Symfony 2.5+
653
                    // @codeCoverageIgnoreStart
654
                    $tokenStorage = $client->getContainer()->get('security.context');
655
                    // @codeCoverageIgnoreEnd
656
                }
657
658 1
                $tokenStorage->setToken($token);
659 1
                $session->set('_security_'.$firewallName, serialize($token));
660 1
            }
661
662 1
            $session->save();
663 1
        }
664
665 24
        return $client;
666
    }
667
668
    /**
669
     * Create User Token.
670
     *
671
     * Factory method for creating a User Token object for the firewall based on
672
     * the user object provided. By default it will be a Username/Password
673
     * Token based on the user's credentials, but may be overridden for custom
674
     * tokens in your applications.
675
     *
676
     * @param UserInterface $user         The user object to base the token off of
677
     * @param string        $firewallName name of the firewall provider to use
678
     *
679
     * @return TokenInterface The token to be used in the security context
680
     */
681 1
    protected function createUserToken(UserInterface $user, $firewallName)
682
    {
683 1
        return new UsernamePasswordToken(
684 1
            $user,
685 1
            null,
686 1
            $firewallName,
687 1
            $user->getRoles()
688 1
        );
689
    }
690
691
    /**
692
     * Extracts the location from the given route.
693
     *
694
     * @param string $route    The name of the route
695
     * @param array  $params   Set of parameters
696
     * @param bool   $absolute
697
     *
698
     * @return string
699
     */
700 1
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
701
    {
702 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
703
    }
704
705
    /**
706
     * Checks the success state of a response.
707
     *
708
     * @param Response $response Response object
709
     * @param bool     $success  to define whether the response is expected to be successful
710
     * @param string   $type
711
     */
712 7
    public function isSuccessful($response, $success = true, $type = 'text/html')
713
    {
714
        try {
715 7
            $crawler = new Crawler();
716 7
            $crawler->addContent($response->getContent(), $type);
717 7
            if (!count($crawler->filter('title'))) {
718 2
                $title = '['.$response->getStatusCode().'] - '.$response->getContent();
719 2
            } else {
720 5
                $title = $crawler->filter('title')->text();
721
            }
722 7
        } catch (\Exception $e) {
723
            $title = $e->getMessage();
724
        }
725
726 7
        if ($success) {
727 5
            $this->assertTrue($response->isSuccessful(), 'The Response was not successful: '.$title);
728 5
        } else {
729 2
            $this->assertFalse($response->isSuccessful(), 'The Response was successful: '.$title);
730
        }
731 7
    }
732
733
    /**
734
     * Executes a request on the given url and returns the response contents.
735
     *
736
     * This method also asserts the request was successful.
737
     *
738
     * @param string $path           path of the requested page
739
     * @param string $method         The HTTP method to use, defaults to GET
740
     * @param bool   $authentication Whether to use authentication, defaults to false
741
     * @param bool   $success        to define whether the response is expected to be successful
742
     *
743
     * @return string
744
     */
745 1
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
746
    {
747 1
        $client = $this->makeClient($authentication);
748 1
        $client->request($method, $path);
749
750 1
        $content = $client->getResponse()->getContent();
751 1
        if (is_bool($success)) {
752 1
            $this->isSuccessful($client->getResponse(), $success);
753 1
        }
754
755 1
        return $content;
756
    }
757
758
    /**
759
     * Executes a request on the given url and returns a Crawler object.
760
     *
761
     * This method also asserts the request was successful.
762
     *
763
     * @param string $path           path of the requested page
764
     * @param string $method         The HTTP method to use, defaults to GET
765
     * @param bool   $authentication Whether to use authentication, defaults to false
766
     * @param bool   $success        Whether the response is expected to be successful
767
     *
768
     * @return Crawler
769
     */
770 1
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
771
    {
772 1
        $client = $this->makeClient($authentication);
773 1
        $crawler = $client->request($method, $path);
774
775 1
        $this->isSuccessful($client->getResponse(), $success);
776
777 1
        return $crawler;
778
    }
779
780
    /**
781
     * @param UserInterface $user
782
     *
783
     * @return WebTestCase
784
     */
785 1
    public function loginAs(UserInterface $user, $firewallName)
786
    {
787 1
        $this->firewallLogins[$firewallName] = $user;
788
789 1
        return $this;
790
    }
791
792
    /**
793
     * Asserts that the HTTP response code of the last request performed by
794
     * $client matches the expected code. If not, raises an error with more
795
     * information.
796
     *
797
     * @param $expectedStatusCode
798
     * @param Client $client
799
     */
800 13
    public function assertStatusCode($expectedStatusCode, Client $client)
801
    {
802 13
        $helpfulErrorMessage = null;
803
804 13
        if ($expectedStatusCode !== $client->getResponse()->getStatusCode()) {
805
            // Get a more useful error message, if available
806
            if ($exception = $client->getContainer()->get('liip_functional_test.exception_listener')->getLastException()) {
807
                $helpfulErrorMessage = $exception->getMessage();
808
            } elseif (count($validationErrors = $client->getContainer()->get('liip_functional_test.validator')->getLastErrors())) {
809
                $helpfulErrorMessage = "Unexpected validation errors:\n";
810
811
                foreach ($validationErrors as $error) {
812
                    $helpfulErrorMessage .= sprintf("+ %s: %s\n", $error->getPropertyPath(), $error->getMessage());
813
                }
814
            } else {
815
                $helpfulErrorMessage = substr($client->getResponse(), 0, 200);
816
            }
817
        }
818
819 13
        self::assertEquals($expectedStatusCode, $client->getResponse()->getStatusCode(), $helpfulErrorMessage);
820 13
    }
821
822
    /**
823
     * Assert that the last validation errors within $container match the
824
     * expected keys.
825
     *
826
     * @param array              $expected  A flat array of field names
827
     * @param ContainerInterface $container
828
     */
829 2
    public function assertValidationErrors(array $expected, ContainerInterface $container)
830
    {
831 2
        self::assertThat(
832 2
            $container->get('liip_functional_test.validator')->getLastErrors(),
833 2
            new ValidationErrorsConstraint($expected),
834
            'Validation errors should match.'
835 2
        );
836 1
    }
837
}
838