Completed
Pull Request — master (#278)
by Alexis
06:57
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
     * @param null            $omName
476
     */
477 6
    private function cleanDatabase(ManagerRegistry $registry, EntityManager $om, $omName = null)
478
    {
479 6
        $connection = $om->getConnection();
480
481 6
        $mysql = ($registry->getName() === 'ORM'
482 6
            && $connection->getDatabasePlatform() instanceof MySqlPlatform);
483
484 6
        if ($mysql) {
485 1
            $connection->query('SET FOREIGN_KEY_CHECKS=0');
486 1
        }
487
488 6
        $this->loadFixtures(array(), $omName);
489
490 6
        if ($mysql) {
491 1
            $connection->query('SET FOREIGN_KEY_CHECKS=1');
492 1
        }
493 6
    }
494
495
    /**
496
     * Locate fixture files.
497
     *
498
     * @param array $paths
499
     *
500
     * @return array $files
501
     */
502 6
    private function locateResources($paths)
503
    {
504 6
        $files = array();
505
506 6
        $kernel = $this->getContainer()->get('kernel');
507
508 6
        foreach ($paths as $path) {
509 6
            if ($path[0] !== '@' && file_exists($path) === true) {
510 1
                $files[] = $path;
511 1
                continue;
512
            }
513
514 5
            $files[] = $kernel->locateResource($path);
515 6
        }
516
517 6
        return $files;
518
    }
519
520
    /**
521
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
522
     * @param bool   $append
523
     * @param null   $omName
524
     * @param string $registryName
525
     *
526
     * @return array
527
     *
528
     * @throws \BadMethodCallException
529
     */
530 6
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine')
531
    {
532 6
        if (!class_exists('Nelmio\Alice\Fixtures')) {
533
            // This class is available during tests, no exception will be thrown.
534
            // @codeCoverageIgnoreStart
535
            throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
536
            // @codeCoverageIgnoreEnd
537
        }
538
539
        /** @var ContainerInterface $container */
540 6
        $container = $this->getContainer();
541
542
        /** @var ManagerRegistry $registry */
543 6
        $registry = $container->get($registryName);
544
545
        /** @var EntityManager $om */
546 6
        $om = $registry->getManager($omName);
547
548 6
        if ($append === false) {
549 6
            $this->cleanDatabase($registry, $om, $omName);
550 6
        }
551
552 6
        $files = $this->locateResources($paths);
553
554
        // Check if the Hautelook AliceBundle is registered and if yes, use it instead of Nelmio Alice
555 6
        $hautelookLoaderServiceName = 'hautelook_alice.fixtures.loader';
556 6
        if ($container->has($hautelookLoaderServiceName)) {
557 3
            $loaderService = $container->get($hautelookLoaderServiceName);
558 3
            $persisterClass = class_exists('Nelmio\Alice\ORM\Doctrine') ?
559 3
                'Nelmio\Alice\ORM\Doctrine' :
560 3
                'Nelmio\Alice\Persister\Doctrine';
561
562 3
            return $loaderService->load(new $persisterClass($om), $files);
563
        }
564
565 3
        return Fixtures::load($files, $om);
566
    }
567
568
    /**
569
     * Callback function to be executed after Schema creation.
570
     * Use this to execute acl:init or other things necessary.
571
     */
572 21
    protected function postFixtureSetup()
573
    {
574 21
    }
575
576
    /**
577
     * Callback function to be executed after Schema restore.
578
     *
579
     * @return WebTestCase
580
     */
581 7
    protected function postFixtureRestore()
582
    {
583 7
    }
584
585
    /**
586
     * Callback function to be executed before Schema restore.
587
     *
588
     * @param ObjectManager            $manager             The object manager
589
     * @param ProxyReferenceRepository $referenceRepository The reference repository
590
     *
591
     * @return WebTestCase
592
     */
593 7
    protected function preFixtureRestore(ObjectManager $manager, ProxyReferenceRepository $referenceRepository)
594
    {
595 7
    }
596
597
    /**
598
     * Callback function to be executed after save of references.
599
     *
600
     * @param ObjectManager    $manager        The object manager
601
     * @param AbstractExecutor $executor       Executor of the data fixtures
602
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
603
     *
604
     * @return WebTestCase
605
     */
606 3
    protected function postReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
607
    {
608 3
    }
609
610
    /**
611
     * Callback function to be executed before save of references.
612
     *
613
     * @param ObjectManager    $manager        The object manager
614
     * @param AbstractExecutor $executor       Executor of the data fixtures
615
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
616
     *
617
     * @return WebTestCase
618
     */
619 3
    protected function preReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
620
    {
621 3
    }
622
623
    /**
624
     * Retrieve Doctrine DataFixtures loader.
625
     *
626
     * @param ContainerInterface $container
627
     * @param array              $classNames
628
     *
629
     * @return Loader
630
     */
631 32
    protected function getFixtureLoader(ContainerInterface $container, array $classNames)
632
    {
633 32
        $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
634 32
            ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
635 32
            : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
636
                // This class is not available during tests.
637
                // @codeCoverageIgnoreStart
638
                ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
639
                // @codeCoverageIgnoreEnd
640 32
                : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
641
642 32
        $loader = new $loaderClass($container);
643
644 32
        foreach ($classNames as $className) {
645 10
            $this->loadFixtureClass($loader, $className);
646 32
        }
647
648 32
        return $loader;
649
    }
650
651
    /**
652
     * Load a data fixture class.
653
     *
654
     * @param Loader $loader
655
     * @param string $className
656
     */
657 10
    protected function loadFixtureClass($loader, $className)
658
    {
659 10
        $fixture = new $className();
660
661 10
        if ($loader->hasFixture($fixture)) {
662 2
            unset($fixture);
663
664 2
            return;
665
        }
666
667 10
        $loader->addFixture($fixture);
668
669 10
        if ($fixture instanceof DependentFixtureInterface) {
670 2
            foreach ($fixture->getDependencies() as $dependency) {
671 2
                $this->loadFixtureClass($loader, $dependency);
672 2
            }
673 2
        }
674 10
    }
675
676
    /**
677
     * Creates an instance of a lightweight Http client.
678
     *
679
     * If $authentication is set to 'true' it will use the content of
680
     * 'liip_functional_test.authentication' to log in.
681
     *
682
     * $params can be used to pass headers to the client, note that they have
683
     * to follow the naming format used in $_SERVER.
684
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
685
     *
686
     * @param bool|array $authentication
687
     * @param array      $params
688
     *
689
     * @return Client
690
     */
691 49
    protected function makeClient($authentication = false, array $params = array())
692
    {
693 49
        if ($authentication) {
694 2
            if ($authentication === true) {
695
                $authentication = array(
696 1
                    'username' => $this->getContainer()
697 1
                        ->getParameter('liip_functional_test.authentication.username'),
698 1
                    'password' => $this->getContainer()
699 1
                        ->getParameter('liip_functional_test.authentication.password'),
700 1
                );
701 1
            }
702
703 2
            $params = array_merge($params, array(
704 2
                'PHP_AUTH_USER' => $authentication['username'],
705 2
                'PHP_AUTH_PW' => $authentication['password'],
706 2
            ));
707 2
        }
708
709 49
        $client = static::createClient(array('environment' => $this->environment), $params);
710
711 49
        if ($this->firewallLogins) {
712
            // has to be set otherwise "hasPreviousSession" in Request returns false.
713 2
            $options = $client->getContainer()->getParameter('session.storage.options');
714
715 2
            if (!$options || !isset($options['name'])) {
716
                throw new \InvalidArgumentException('Missing session.storage.options#name');
717
            }
718
719 2
            $session = $client->getContainer()->get('session');
720
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
721 2
            if ($session instanceof Session) {
722 2
                $session->setId(uniqid());
723 2
            }
724
725 2
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
726
727
            /** @var $user UserInterface */
728 2
            foreach ($this->firewallLogins as $firewallName => $user) {
729 2
                $token = $this->createUserToken($user, $firewallName);
730
731
                // BC: security.token_storage is available on Symfony 2.6+
732
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
733 2
                if ($client->getContainer()->has('security.token_storage')) {
734 2
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
735 2
                } else {
736
                    // This block will never be reached with Symfony 2.6+
737
                    // @codeCoverageIgnoreStart
738
                    $tokenStorage = $client->getContainer()->get('security.context');
739
                    // @codeCoverageIgnoreEnd
740
                }
741
742 2
                $tokenStorage->setToken($token);
743 2
                $session->set('_security_'.$firewallName, serialize($token));
744 2
            }
745
746 2
            $session->save();
747 2
        }
748
749 49
        return $client;
750
    }
751
752
    /**
753
     * Create User Token.
754
     *
755
     * Factory method for creating a User Token object for the firewall based on
756
     * the user object provided. By default it will be a Username/Password
757
     * Token based on the user's credentials, but may be overridden for custom
758
     * tokens in your applications.
759
     *
760
     * @param UserInterface $user         The user object to base the token off of
761
     * @param string        $firewallName name of the firewall provider to use
762
     *
763
     * @return TokenInterface The token to be used in the security context
764
     */
765 2
    protected function createUserToken(UserInterface $user, $firewallName)
766
    {
767 2
        return new UsernamePasswordToken(
768 2
            $user,
769 2
            null,
770 2
            $firewallName,
771 2
            $user->getRoles()
772 2
        );
773
    }
774
775
    /**
776
     * Extracts the location from the given route.
777
     *
778
     * @param string $route    The name of the route
779
     * @param array  $params   Set of parameters
780
     * @param int    $absolute
781
     *
782
     * @return string
783
     */
784 1
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
785
    {
786 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
787
    }
788
789
    /**
790
     * Checks the success state of a response.
791
     *
792
     * @param Response $response Response object
793
     * @param bool     $success  to define whether the response is expected to be successful
794
     * @param string   $type
795
     */
796 6
    public function isSuccessful(Response $response, $success = true, $type = 'text/html')
797
    {
798 6
        HttpAssertions::isSuccessful($response, $success, $type);
799 5
    }
800
801
    /**
802
     * Executes a request on the given url and returns the response contents.
803
     *
804
     * This method also asserts the request was successful.
805
     *
806
     * @param string $path           path of the requested page
807
     * @param string $method         The HTTP method to use, defaults to GET
808
     * @param bool   $authentication Whether to use authentication, defaults to false
809
     * @param bool   $success        to define whether the response is expected to be successful
810
     *
811
     * @return string
812
     */
813 1
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
814
    {
815 1
        $client = $this->makeClient($authentication);
816 1
        $client->request($method, $path);
817
818 1
        $content = $client->getResponse()->getContent();
819 1
        if (is_bool($success)) {
820 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...
821 1
        }
822
823 1
        return $content;
824
    }
825
826
    /**
827
     * Executes a request on the given url and returns a Crawler object.
828
     *
829
     * This method also asserts the request was successful.
830
     *
831
     * @param string $path           path of the requested page
832
     * @param string $method         The HTTP method to use, defaults to GET
833
     * @param bool   $authentication Whether to use authentication, defaults to false
834
     * @param bool   $success        Whether the response is expected to be successful
835
     *
836
     * @return Crawler
837
     */
838 1
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
839
    {
840 1
        $client = $this->makeClient($authentication);
841 1
        $crawler = $client->request($method, $path);
842
843 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...
844
845 1
        return $crawler;
846
    }
847
848
    /**
849
     * @param UserInterface $user
850
     * @param string        $firewallName
851
     *
852
     * @return WebTestCase
853
     */
854 2
    public function loginAs(UserInterface $user, $firewallName)
855
    {
856 2
        $this->firewallLogins[$firewallName] = $user;
857
858 2
        return $this;
859
    }
860
861
    /**
862
     * Asserts that the HTTP response code of the last request performed by
863
     * $client matches the expected code. If not, raises an error with more
864
     * information.
865
     *
866
     * @param $expectedStatusCode
867
     * @param Client $client
868
     */
869 11
    public function assertStatusCode($expectedStatusCode, Client $client)
870
    {
871 11
        HttpAssertions::assertStatusCode($expectedStatusCode, $client);
872 8
    }
873
874
    /**
875
     * Assert that the last validation errors within $container match the
876
     * expected keys.
877
     *
878
     * @param array              $expected  A flat array of field names
879
     * @param ContainerInterface $container
880
     */
881 2
    public function assertValidationErrors(array $expected, ContainerInterface $container)
882
    {
883 2
        HttpAssertions::assertValidationErrors($expected, $container);
884 1
    }
885
}
886