Test Setup Failed
Pull Request — master (#337)
by Sullivan
27:43
created

Test/WebTestCase.php (1 issue)

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
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 $excludedDoctrineTables = array();
69
70
    /**
71
     * @var array
72
     */
73
    private static $cachedMetadatas = array();
74
75
    protected static function getKernelClass()
76
    {
77
        $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : static::getPhpUnitXmlDir();
0 ignored issues
show
Deprecated Code introduced by
The method Symfony\Bundle\Framework...ase::getPhpUnitXmlDir() has been deprecated with message: since 3.4 and will be removed in 4.0.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

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