Passed
Pull Request — master (#354)
by
unknown
05:48
created

Test/WebTestCase.php (1 issue)

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()
0 ignored issues
show
getKernelClass uses the super-global variable $_ENV which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

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