Completed
Push — master ( 001af3...db0086 )
by Alexis
21:01 queued 13:42
created

Test/WebTestCase.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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

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...
820 1
        }
821
822 1
        return $content;
823
    }
824
825
    /**
826
     * Executes a request on the given url and returns a Crawler object.
827
     *
828
     * This method also asserts the request was successful.
829
     *
830
     * @param string $path           path of the requested page
831
     * @param string $method         The HTTP method to use, defaults to GET
832
     * @param bool   $authentication Whether to use authentication, defaults to false
833
     * @param bool   $success        Whether the response is expected to be successful
834
     *
835
     * @return Crawler
836
     */
837 1
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
838
    {
839 1
        $client = $this->makeClient($authentication);
840 1
        $crawler = $client->request($method, $path);
841
842 1
        $this->isSuccessful($client->getResponse(), $success);
0 ignored issues
show
$client->getResponse() is of type object|null, but the function expects a object<Symfony\Component\HttpFoundation\Response>.

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...
843
844 1
        return $crawler;
845
    }
846
847
    /**
848
     * @param UserInterface $user
849
     * @param string        $firewallName
850
     *
851
     * @return WebTestCase
852
     */
853 2
    public function loginAs(UserInterface $user, $firewallName)
854
    {
855 2
        $this->firewallLogins[$firewallName] = $user;
856
857 2
        return $this;
858
    }
859
860
    /**
861
     * Asserts that the HTTP response code of the last request performed by
862
     * $client matches the expected code. If not, raises an error with more
863
     * information.
864
     *
865
     * @param $expectedStatusCode
866
     * @param Client $client
867
     */
868 11
    public function assertStatusCode($expectedStatusCode, Client $client)
869
    {
870 11
        HttpAssertions::assertStatusCode($expectedStatusCode, $client);
871 8
    }
872
873
    /**
874
     * Assert that the last validation errors within $container match the
875
     * expected keys.
876
     *
877
     * @param array              $expected  A flat array of field names
878
     * @param ContainerInterface $container
879
     */
880 2
    public function assertValidationErrors(array $expected, ContainerInterface $container)
881
    {
882 2
        HttpAssertions::assertValidationErrors($expected, $container);
883 1
    }
884
}
885