Completed
Pull Request — master (#260)
by Alexis
07:36
created

Test/WebTestCase.php (1 issue)

Labels
Severity

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
42
/**
43
 * @author Lea Haensenberger
44
 * @author Lukas Kahwe Smith <[email protected]>
45
 * @author Benjamin Eberlei <[email protected]>
46
 */
47
abstract class WebTestCase extends BaseWebTestCase
48
{
49
    protected $environment = 'test';
50
    protected $containers;
51
    protected $kernelDir;
52
    // 5 * 1024 * 1024 KB
53
    protected $maxMemory = 5242880;
54
55
    // RUN COMMAND
56
    protected $verbosityLevel;
57
    protected $decorated;
58
59
    /**
60
     * @var array
61
     */
62
    private $firewallLogins = array();
63
64
    /**
65
     * @var array
66
     */
67
    private static $cachedMetadatas = array();
68
69
    protected static function getKernelClass()
70
    {
71
        $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : static::getPhpUnitXmlDir();
72
73
        list($appname) = explode('\\', get_called_class());
74
75
        $class = $appname.'Kernel';
76
        $file = $dir.'/'.strtolower($appname).'/'.$class.'.php';
77
        if (!file_exists($file)) {
78
            return parent::getKernelClass();
79
        }
80
        require_once $file;
81
82
        return $class;
83
    }
84
85
    /**
86
     * Creates a mock object of a service identified by its id.
87
     *
88
     * @param string $id
89
     *
90
     * @return \PHPUnit_Framework_MockObject_MockBuilder
91
     */
92 2
    protected function getServiceMockBuilder($id)
93
    {
94
        $service = $this->getContainer()->get($id);
95 2
        $class = get_class($service);
96
97
        return $this->getMockBuilder($class)->disableOriginalConstructor();
98
    }
99
100
    /**
101
     * Builds up the environment to run the given command.
102
     *
103
     * @param string $name
104
     * @param array  $params
105
     * @param bool   $reuseKernel
106
     *
107
     * @return string
108
     */
109 12
    protected function runCommand($name, array $params = array(), $reuseKernel = false)
110
    {
111 12
        array_unshift($params, $name);
112
113 12
        if (!$reuseKernel) {
114 12
            if (null !== static::$kernel) {
115 9
                static::$kernel->shutdown();
116 9
            }
117
118 12
            $kernel = static::$kernel = $this->createKernel(array('environment' => $this->environment));
119 12
            $kernel->boot();
120 12
        } else {
121 2
            $kernel = $this->getContainer()->get('kernel');
122
        }
123
124 12
        $application = new Application($kernel);
125 12
        $application->setAutoExit(false);
126
127
        // @codeCoverageIgnoreStart
128
        if ('20301' === Kernel::VERSION_ID) {
129
            $params = $this->configureVerbosityForSymfony20301($params);
130
        }
131
        // @codeCoverageIgnoreEnd
132
133 12
        $input = new ArrayInput($params);
134 12
        $input->setInteractive(false);
135
136 12
        $fp = fopen('php://temp/maxmemory:'.$this->maxMemory, 'r+');
137 12
        $output = new StreamOutput($fp, $this->getVerbosityLevel(), $this->getDecorated());
138
139 11
        $application->run($input, $output);
140
141 11
        rewind($fp);
142
143 11
        return stream_get_contents($fp);
144
    }
145
146
    /**
147
     * Retrieves the output verbosity level.
148
     *
149
     * @see Symfony\Component\Console\Output\OutputInterface for available levels
150
     *
151
     * @return int
152
     *
153
     * @throws \OutOfBoundsException If the set value isn't accepted
154
     */
155 12
    protected function getVerbosityLevel()
156
    {
157
        // If `null`, is not yet set
158 12
        if (null === $this->verbosityLevel) {
159
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
160 6
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
161 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
162
163 6
            $this->verbosityLevel = constant($verbosity);
164 6
        }
165
166
        // If string, it is set by the developer, so check that the value is an accepted one
167 12
        if (is_string($this->verbosityLevel)) {
168 6
            $level = strtoupper($this->verbosityLevel);
169 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
170
171 6
            if (!defined($verbosity)) {
172 1
                throw new \OutOfBoundsException(
173 1
                    sprintf('The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".', $level)
174 1
                    );
175
            }
176
177 5
            $this->verbosityLevel = constant($verbosity);
178 5
        }
179
180 11
        return $this->verbosityLevel;
181
    }
182
183
    /**
184
     * In Symfony 2.3.1 the verbosity level has to be set through {Symfony\Component\Console\Input\ArrayInput} and not
185
     * in {Symfony\Component\Console\Output\OutputInterface}.
186
     *
187
     * This method builds $params to be passed to {Symfony\Component\Console\Input\ArrayInput}.
188
     *
189
     * @codeCoverageIgnore
190
     *
191
     * @param array $params
192
     *
193
     * @return array
194
     */
195
    private function configureVerbosityForSymfony20301(array $params)
196
    {
197
        switch ($this->getVerbosityLevel()) {
198
            case OutputInterface::VERBOSITY_QUIET:
199
                $params['-q'] = '-q';
200
                break;
201
202
            case OutputInterface::VERBOSITY_VERBOSE:
203
                $params['-v'] = '';
204
                break;
205
206
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
207
                $params['-vv'] = '';
208
                break;
209
210
            case OutputInterface::VERBOSITY_DEBUG:
211
                $params['-vvv'] = '';
212
                break;
213
        }
214
215
        return $params;
216
    }
217
218 6
    public function setVerbosityLevel($level)
219
    {
220 6
        $this->verbosityLevel = $level;
221 6
    }
222
223
    /**
224
     * Retrieves the flag indicating if the output should be decorated or not.
225
     *
226
     * @return bool
227
     */
228 11
    protected function getDecorated()
229
    {
230 11
        if (null === $this->decorated) {
231
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
232 5
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
233 5
        }
234
235
        // Check the local decorated flag
236 11
        if (false === is_bool($this->decorated)) {
237
            throw new \OutOfBoundsException(
238 1
                sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated))
239
            );
240
        }
241
242 11
        return $this->decorated;
243
    }
244
245 6
    public function isDecorated($decorated)
246
    {
247 6
        $this->decorated = $decorated;
248 6
    }
249
250
    /**
251
     * Get an instance of the dependency injection container.
252
     * (this creates a kernel *without* parameters).
253
     *
254
     * @return ContainerInterface
255
     */
256 45
    protected function getContainer()
257
    {
258 45
        if (!empty($this->kernelDir)) {
259
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
260
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
261
        }
262
263 45
        $cacheKey = $this->kernelDir.'|'.$this->environment;
264 45
        if (empty($this->containers[$cacheKey])) {
265
            $options = array(
266 44
                'environment' => $this->environment,
267 44
            );
268 44
            $kernel = $this->createKernel($options);
269 44
            $kernel->boot();
270
271 44
            $this->containers[$cacheKey] = $kernel->getContainer();
272 44
        }
273
274 45
        if (isset($tmpKernelDir)) {
275
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
276
        }
277
278 45
        return $this->containers[$cacheKey];
279
    }
280
281
    /**
282
     * This function finds the time when the data blocks of a class definition
283
     * file were being written to, that is, the time when the content of the
284
     * file was changed.
285
     *
286
     * @param string $class The fully qualified class name of the fixture class to
287
     *                      check modification date on.
288
     *
289
     * @return \DateTime|null
290
     */
291 2
    protected function getFixtureLastModified($class)
292
    {
293 2
        $lastModifiedDateTime = null;
294
295 2
        $reflClass = new \ReflectionClass($class);
296 2
        $classFileName = $reflClass->getFileName();
297
298 2
        if (file_exists($classFileName)) {
299 2
            $lastModifiedDateTime = new \DateTime();
300 2
            $lastModifiedDateTime->setTimestamp(filemtime($classFileName));
301 2
        }
302
303 2
        return $lastModifiedDateTime;
304
    }
305
306
    /**
307
     * Determine if the Fixtures that define a database backup have been
308
     * modified since the backup was made.
309
     *
310
     * @param array  $classNames The fixture classnames to check
311
     * @param string $backup     The fixture backup SQLite database file path
312
     *
313
     * @return bool TRUE if the backup was made since the modifications to the
314
     *              fixtures; FALSE otherwise
315
     */
316 6
    protected function isBackupUpToDate(array $classNames, $backup)
317
    {
318 6
        $backupLastModifiedDateTime = new \DateTime();
319 6
        $backupLastModifiedDateTime->setTimestamp(filemtime($backup));
320
321 6
        /** @var \Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader $loader */
322 2
        $loader = $this->getFixtureLoader($this->getContainer(), $classNames);
323 2
324
        // Use loader in order to fetch all the dependencies fixtures.
325
        foreach ($loader->getFixtures() as &$className) {
0 ignored issues
show
The expression $loader->getFixtures() cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$itemValue is assigned by reference. This is possible because the expression (in the example $array) can be used as a reference target.

However, if we were to replace $array with something different like the result of a function call as in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &getArray() { $array = array(); return $array; }

foreach (getArray() as &$itemValue) { }
Loading history...
326 6
            $fixtureLastModifiedDateTime = $this->getFixtureLastModified($className);
327
            if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) {
328 6
                return false;
329
            }
330
        }
331
332
        return true;
333
    }
334
335
    /**
336
     * Set the database to the provided fixtures.
337
     *
338
     * Drops the current database and then loads fixtures using the specified
339
     * classes. The parameter is a list of fully qualified class names of
340
     * classes that implement Doctrine\Common\DataFixtures\FixtureInterface
341
     * so that they can be loaded by the DataFixtures Loader::addFixture
342
     *
343
     * When using SQLite this method will automatically make a copy of the
344
     * loaded schema and fixtures which will be restored automatically in
345
     * case the same fixture classes are to be loaded again. Caveat: changes
346
     * to references and/or identities may go undetected.
347
     *
348
     * Depends on the doctrine data-fixtures library being available in the
349
     * class path.
350
     *
351
     * @param array  $classNames   List of fully qualified class names of fixtures to load
352
     * @param string $omName       The name of object manager to use
353
     * @param string $registryName The service id of manager registry to use
354 31
     * @param int    $purgeMode    Sets the ORM purge mode
355
     *
356 31
     * @return null|AbstractExecutor
357
     */
358 31
    protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
359
    {
360 31
        $container = $this->getContainer();
361 31
        /** @var ManagerRegistry $registry */
362
        $registry = $container->get($registryName);
363 31
        /** @var ObjectManager $om */
364 31
        $om = $registry->getManager($omName);
365 31
        $type = $registry->getName();
366 31
367 31
        $executorClass = 'PHPCR' === $type && class_exists('Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor')
368
            ? 'Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor'
369 31
            : 'Doctrine\\Common\\DataFixtures\\Executor\\'.$type.'Executor';
370 31
        $referenceRepository = new ProxyReferenceRepository($om);
371 31
        $cacheDriver = $om->getMetadataFactory()->getCacheDriver();
372
373 31
        if ($cacheDriver) {
374 30
            $cacheDriver->deleteAll();
375 30
        }
376 26
377 26
        if ('ORM' === $type) {
378
            $connection = $om->getConnection();
379
            if ($connection->getDriver() instanceof SqliteDriver) {
380
                $params = $connection->getParams();
381 26
                if (isset($params['master'])) {
382 26
                    $params = $params['master'];
383
                }
384
385
                $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
386 26
                if (!$name) {
387 9
                    throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
388
                }
389 9
390 26
                if (!isset(self::$cachedMetadatas[$omName])) {
391
                    self::$cachedMetadatas[$omName] = $om->getMetadataFactory()->getAllMetadata();
392 26
                    usort(self::$cachedMetadatas[$omName], function ($a, $b) { return strcmp($a->name, $b->name); });
393 8
                }
394 8
                $metadatas = self::$cachedMetadatas[$omName];
395 6
396 6
                if ($container->getParameter('liip_functional_test.cache_sqlite_db')) {
397
                    $backup = $container->getParameter('kernel.cache_dir').'/test_'.md5(serialize($metadatas).serialize($classNames)).'.db';
398 6
                    if (file_exists($backup) && file_exists($backup.'.ser') && $this->isBackupUpToDate($classNames, $backup)) {
399
                        $om->flush();
400 6
                        $om->clear();
401
402 6
                        $this->preFixtureRestore($om, $referenceRepository);
403 6
404 6
                        copy($backup, $name);
405
406 6
                        $executor = new $executorClass($om);
407
                        $executor->setReferenceRepository($referenceRepository);
408 6
                        $executor->getReferenceRepository()->load($backup);
409
410 2
                        $this->postFixtureRestore();
411
412
                        return $executor;
413 20
                    }
414 20
                }
415 20
416 20
                // TODO: handle case when using persistent connections. Fail loudly?
417 20
                $schemaTool = new SchemaTool($om);
418 20
                $schemaTool->dropDatabase();
419
                if (!empty($metadatas)) {
420 20
                    $schemaTool->createSchema($metadatas);
421 20
                }
422 20
                $this->postFixtureSetup();
423 24
424
                $executor = new $executorClass($om);
425 25
                $executor->setReferenceRepository($referenceRepository);
426 5
            }
427 5
        }
428 1
429 1
        if (empty($executor)) {
430 1
            $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
431 1
            if ('PHPCR' === $type) {
432
                $purger = new $purgerClass($om);
433 1
                $initManager = $container->has('doctrine_phpcr.initializer_manager')
434 1
                    ? $container->get('doctrine_phpcr.initializer_manager')
435 4
                    : null;
436 4
437 1
                $executor = new $executorClass($om, $purger, $initManager);
438 1
            } else {
439
                $purger = new $purgerClass();
440 4
                if (null !== $purgeMode) {
441
                    $purger->setPurgeMode($purgeMode);
442
                }
443 5
444 5
                $executor = new $executorClass($om, $purger);
445 5
            }
446
447 25
            $executor->setReferenceRepository($referenceRepository);
448
            $executor->purge();
449 25
        }
450
451 25
        $loader = $this->getFixtureLoader($container, $classNames);
452 2
453
        $executor->execute($loader->getFixtures(), true);
454 2
455 2
        if (isset($name) && isset($backup)) {
456
            $this->preReferenceSave($om, $executor, $backup);
457 2
458 2
            $executor->getReferenceRepository()->save($backup);
459
            copy($name, $backup);
460 25
461
            $this->postReferenceSave($om, $executor, $backup);
462
        }
463
464
        return $executor;
465
    }
466
467
    /**
468
     * Clean database.
469 6
     *
470
     * @param ManagerRegistry $registry
471 6
     * @param EntityManager   $om
472
     */
473 6
    private function cleanDatabase(ManagerRegistry $registry, EntityManager $om)
474 6
    {
475
        $connection = $om->getConnection();
476 6
477 1
        $mysql = ($registry->getName() === 'ORM'
478 1
            && $connection->getDatabasePlatform() instanceof MySqlPlatform);
479
480 6
        if ($mysql) {
481
            $connection->query('SET FOREIGN_KEY_CHECKS=0');
482 6
        }
483 1
484 1
        $this->loadFixtures(array());
485 6
486
        if ($mysql) {
487
            $connection->query('SET FOREIGN_KEY_CHECKS=1');
488
        }
489
    }
490
491
    /**
492
     * Locate fixture files.
493
     *
494 6
     * @param array $paths
495
     *
496 6
     * @return array $files
497
     */
498 6
    private function locateResources($paths)
499
    {
500 6
        $files = array();
501 6
502 1
        $kernel = $this->getContainer()->get('kernel');
503 1
504
        foreach ($paths as $path) {
505
            if ($path[0] !== '@' && file_exists($path) === true) {
506 5
                $files[] = $path;
507 6
                continue;
508
            }
509 6
510
            $files[] = $kernel->locateResource($path);
511
        }
512
513
        return $files;
514
    }
515
516
    /**
517
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
518
     * @param bool   $append
519
     * @param null   $omName
520
     * @param string $registryName
521
     *
522 6
     * @return array
523
     *
524 6
     * @throws \BadMethodCallException
525
     */
526
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine')
527
    {
528
        if (!class_exists('Nelmio\Alice\Fixtures')) {
529
            // This class is available during tests, no exception will be thrown.
530
            // @codeCoverageIgnoreStart
531
            throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
532 6
            // @codeCoverageIgnoreEnd
533
        }
534
535 6
        /** @var ContainerInterface $container */
536
        $container = $this->getContainer();
537
538 6
        /** @var ManagerRegistry $registry */
539
        $registry = $container->get($registryName);
540 6
541 6
        /** @var EntityManager $om */
542 6
        $om = $registry->getManager($omName);
543
544 6
        if ($append === false) {
545
            $this->cleanDatabase($registry, $om);
546
        }
547 6
548 6
        $files = $this->locateResources($paths);
549 3
550 3
        // Check if the Hautelook AliceBundle is registered and if yes, use it instead of Nelmio Alice
551 3
        $hautelookLoaderServiceName = 'hautelook_alice.fixtures.loader';
552 3
        if ($container->has($hautelookLoaderServiceName)) {
553
            $loaderService = $container->get($hautelookLoaderServiceName);
554 3
            $persisterClass = class_exists('Nelmio\Alice\ORM\Doctrine') ?
555
                'Nelmio\Alice\ORM\Doctrine' :
556
                'Nelmio\Alice\Persister\Doctrine';
557 3
558
            return $loaderService->load(new $persisterClass($om), $files);
559
        }
560
561
        return Fixtures::load($files, $om);
562
    }
563
564 20
    /**
565
     * Callback function to be executed after Schema creation.
566 20
     * Use this to execute acl:init or other things necessary.
567
     */
568
    protected function postFixtureSetup()
569
    {
570
    }
571
572
    /**
573 6
     * Callback function to be executed after Schema restore.
574
     *
575 6
     * @return WebTestCase
576
     */
577
    protected function postFixtureRestore()
578
    {
579
    }
580
581
    /**
582
     * Callback function to be executed before Schema restore.
583
     *
584
     * @param ObjectManager            $manager             The object manager
585 6
     * @param ProxyReferenceRepository $referenceRepository The reference repository
586
     *
587 6
     * @return WebTestCase
588
     */
589
    protected function preFixtureRestore(ObjectManager $manager, ProxyReferenceRepository $referenceRepository)
590
    {
591
    }
592
593
    /**
594
     * Callback function to be executed after save of references.
595
     *
596
     * @param ObjectManager    $manager        The object manager
597
     * @param AbstractExecutor $executor       Executor of the data fixtures
598 2
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
599
     *
600 2
     * @return WebTestCase
601
     */
602
    protected function postReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
603
    {
604
    }
605
606
    /**
607
     * Callback function to be executed before save of references.
608
     *
609
     * @param ObjectManager    $manager        The object manager
610
     * @param AbstractExecutor $executor       Executor of the data fixtures
611 2
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
612
     *
613 2
     * @return WebTestCase
614
     */
615
    protected function preReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
616
    {
617
    }
618
619
    /**
620
     * Retrieve Doctrine DataFixtures loader.
621
     *
622
     * @param ContainerInterface $container
623 25
     * @param array              $classNames
624
     *
625 25
     * @return Loader
626 25
     */
627 25
    protected function getFixtureLoader(ContainerInterface $container, array $classNames)
628
    {
629
        $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
630
            ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
631
            : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
632 25
                // This class is not available during tests.
633
                // @codeCoverageIgnoreStart
634 25
                ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
635
                // @codeCoverageIgnoreEnd
636 25
                : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
637 7
638 25
        $loader = new $loaderClass($container);
639
640 25
        foreach ($classNames as $className) {
641
            $this->loadFixtureClass($loader, $className);
642
        }
643
644
        return $loader;
645
    }
646
647
    /**
648
     * Load a data fixture class.
649 7
     *
650
     * @param Loader $loader
651 7
     * @param string $className
652
     */
653 7
    protected function loadFixtureClass($loader, $className)
654 1
    {
655
        $fixture = new $className();
656 1
657
        if ($loader->hasFixture($fixture)) {
658
            unset($fixture);
659 7
660
            return;
661 7
        }
662 1
663 1
        $loader->addFixture($fixture);
664 1
665 1
        if ($fixture instanceof DependentFixtureInterface) {
666 7
            foreach ($fixture->getDependencies() as $dependency) {
667
                $this->loadFixtureClass($loader, $dependency);
668
            }
669
        }
670
    }
671
672
    /**
673
     * Creates an instance of a lightweight Http client.
674
     *
675
     * If $authentication is set to 'true' it will use the content of
676
     * 'liip_functional_test.authentication' to log in.
677
     *
678
     * $params can be used to pass headers to the client, note that they have
679
     * to follow the naming format used in $_SERVER.
680
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
681
     *
682
     * @param bool|array $authentication
683 45
     * @param array      $params
684
     *
685 45
     * @return Client
686 2
     */
687
    protected function makeClient($authentication = false, array $params = array())
688 1
    {
689 1
        if ($authentication) {
690 1
            if ($authentication === true) {
691 1
                $authentication = array(
692 1
                    'username' => $this->getContainer()
693 1
                        ->getParameter('liip_functional_test.authentication.username'),
694
                    'password' => $this->getContainer()
695 2
                        ->getParameter('liip_functional_test.authentication.password'),
696 2
                );
697 2
            }
698 2
699 2
            $params = array_merge($params, array(
700
                'PHP_AUTH_USER' => $authentication['username'],
701 45
                'PHP_AUTH_PW' => $authentication['password'],
702
            ));
703 45
        }
704
705 2
        $client = static::createClient(array('environment' => $this->environment), $params);
706
707 2
        if ($this->firewallLogins) {
708
            // has to be set otherwise "hasPreviousSession" in Request returns false.
709
            $options = $client->getContainer()->getParameter('session.storage.options');
710
711 2
            if (!$options || !isset($options['name'])) {
712
                throw new \InvalidArgumentException('Missing session.storage.options#name');
713 2
            }
714 2
715 2
            $session = $client->getContainer()->get('session');
716
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
717 2
            if ($session instanceof Session) {
718
                $session->setId(uniqid());
719
            }
720 2
721 2
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
722
723
            /** @var $user UserInterface */
724
            foreach ($this->firewallLogins as $firewallName => $user) {
725 2
                $token = $this->createUserToken($user, $firewallName);
726 2
727 2
                // BC: security.token_storage is available on Symfony 2.6+
728
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
729
                if ($client->getContainer()->has('security.token_storage')) {
730
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
731
                } else {
732
                    // This block will never be reached with Symfony 2.6+
733
                    // @codeCoverageIgnoreStart
734 2
                    $tokenStorage = $client->getContainer()->get('security.context');
735 2
                    // @codeCoverageIgnoreEnd
736 2
                }
737
738 2
                $tokenStorage->setToken($token);
739 2
                $session->set('_security_'.$firewallName, serialize($token));
740
            }
741 45
742
            $session->save();
743
        }
744
745
        return $client;
746
    }
747
748
    /**
749
     * Create User Token.
750
     *
751
     * Factory method for creating a User Token object for the firewall based on
752
     * the user object provided. By default it will be a Username/Password
753
     * Token based on the user's credentials, but may be overridden for custom
754
     * tokens in your applications.
755
     *
756
     * @param UserInterface $user         The user object to base the token off of
757 2
     * @param string        $firewallName name of the firewall provider to use
758
     *
759 2
     * @return TokenInterface The token to be used in the security context
760 2
     */
761 2
    protected function createUserToken(UserInterface $user, $firewallName)
762 2
    {
763 2
        return new UsernamePasswordToken(
764 2
            $user,
765
            null,
766
            $firewallName,
767
            $user->getRoles()
768
        );
769
    }
770
771
    /**
772
     * Extracts the location from the given route.
773
     *
774
     * @param string $route    The name of the route
775
     * @param array  $params   Set of parameters
776 1
     * @param int    $absolute
777
     *
778 1
     * @return string
779
     */
780
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
781
    {
782
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
783
    }
784
785
    /**
786
     * Checks the success state of a response.
787
     *
788 6
     * @param Response $response Response object
789
     * @param bool     $success  to define whether the response is expected to be successful
790
     * @param string   $type
791 6
     */
792 6
    public function isSuccessful(Response $response, $success = true, $type = 'text/html')
793 5
    {
794 1
        try {
795 1
            $crawler = new Crawler();
796 4
            $crawler->addContent($response->getContent(), $type);
797
            if (!count($crawler->filter('title'))) {
798 6
                $title = '['.$response->getStatusCode().'] - '.$response->getContent();
799 1
            } else {
800
                $title = $crawler->filter('title')->text();
801
            }
802 6
        } catch (\Exception $e) {
803 5
            $title = $e->getMessage();
804 4
        }
805 1
806
        if ($success) {
807 5
            $this->assertTrue($response->isSuccessful(), 'The Response was not successful: '.$title);
808
        } else {
809
            $this->assertFalse($response->isSuccessful(), 'The Response was successful: '.$title);
810
        }
811
    }
812
813
    /**
814
     * Executes a request on the given url and returns the response contents.
815
     *
816
     * This method also asserts the request was successful.
817
     *
818
     * @param string $path           path of the requested page
819
     * @param string $method         The HTTP method to use, defaults to GET
820
     * @param bool   $authentication Whether to use authentication, defaults to false
821 1
     * @param bool   $success        to define whether the response is expected to be successful
822
     *
823 1
     * @return string
824 1
     */
825
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
826 1
    {
827 1
        $client = $this->makeClient($authentication);
828 1
        $client->request($method, $path);
829 1
830
        $content = $client->getResponse()->getContent();
831 1
        if (is_bool($success)) {
832
            $this->isSuccessful($client->getResponse(), $success);
833
        }
834
835
        return $content;
836
    }
837
838
    /**
839
     * Executes a request on the given url and returns a Crawler object.
840
     *
841
     * This method also asserts the request was successful.
842
     *
843
     * @param string $path           path of the requested page
844
     * @param string $method         The HTTP method to use, defaults to GET
845
     * @param bool   $authentication Whether to use authentication, defaults to false
846 1
     * @param bool   $success        Whether the response is expected to be successful
847
     *
848 1
     * @return Crawler
849 1
     */
850
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
851 1
    {
852
        $client = $this->makeClient($authentication);
853 1
        $crawler = $client->request($method, $path);
854
855
        $this->isSuccessful($client->getResponse(), $success);
856
857
        return $crawler;
858
    }
859
860
    /**
861
     * @param UserInterface $user
862 2
     * @param string        $firewallName
863
     *
864 2
     * @return WebTestCase
865
     */
866 2
    public function loginAs(UserInterface $user, $firewallName)
867
    {
868
        $this->firewallLogins[$firewallName] = $user;
869
870
        return $this;
871
    }
872
873
    /**
874
     * Asserts that the HTTP response code of the last request performed by
875
     * $client matches the expected code. If not, raises an error with more
876
     * information.
877 11
     *
878
     * @param $expectedStatusCode
879 11
     * @param Client $client
880
     */
881 11
    public function assertStatusCode($expectedStatusCode, Client $client)
882
    {
883 3
        $helpfulErrorMessage = null;
884 1
885 3
        if ($expectedStatusCode !== $client->getResponse()->getStatusCode()) {
886 1
            // Get a more useful error message, if available
887
            if ($exception = $client->getContainer()->get('liip_functional_test.exception_listener')->getLastException()) {
888 1
                $helpfulErrorMessage = $exception->getMessage();
889 1
            } elseif (count($validationErrors = $client->getContainer()->get('liip_functional_test.validator')->getLastErrors())) {
890 1
                $helpfulErrorMessage = "Unexpected validation errors:\n";
891 1
892 1
                foreach ($validationErrors as $error) {
893
                    $helpfulErrorMessage .= sprintf("+ %s: %s\n", $error->getPropertyPath(), $error->getMessage());
894 3
                }
895
            } else {
896 11
                $helpfulErrorMessage = substr($client->getResponse(), 0, 200);
897 8
            }
898
        }
899
900
        self::assertEquals($expectedStatusCode, $client->getResponse()->getStatusCode(), $helpfulErrorMessage);
901
    }
902
903
    /**
904
     * Assert that the last validation errors within $container match the
905
     * expected keys.
906 2
     *
907
     * @param array              $expected  A flat array of field names
908 2
     * @param ContainerInterface $container
909 2
     */
910 2
    public function assertValidationErrors(array $expected, ContainerInterface $container)
911
    {
912 2
        self::assertThat(
913 1
            $container->get('liip_functional_test.validator')->getLastErrors(),
914
            new ValidationErrorsConstraint($expected),
915
            'Validation errors should match.'
916
        );
917
    }
918
}
919