Completed
Pull Request — master (#351)
by Alexis
05:39
created

WebTestCase::setVerbosityLevelEnv()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
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
Coding Style introduced by
getKernelClass uses the super-global variable $_SERVER 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($_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 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
     */
244 11
    private function setVerbosityLevelEnv($level)
245
    {
246 11
        putenv('SHELL_VERBOSITY='.$level);
247 11
    }
248
249
    /**
250
     * Retrieves the flag indicating if the output should be decorated or not.
251
     *
252
     * @return bool
253
     */
254 11
    protected function getDecorated()
255
    {
256 11
        if (null === $this->decorated) {
257
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
258 5
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
259 5
        }
260
261
        // Check the local decorated flag
262 11
        if (false === is_bool($this->decorated)) {
263
            throw new \OutOfBoundsException(
264
                sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated))
265
            );
266
        }
267
268 11
        return $this->decorated;
269
    }
270
271 6
    public function isDecorated($decorated)
272
    {
273 6
        $this->decorated = $decorated;
274 6
    }
275
276
    /**
277
     * Get an instance of the dependency injection container.
278
     * (this creates a kernel *without* parameters).
279
     *
280
     * @return ContainerInterface
281
     */
282 50
    protected function getContainer()
0 ignored issues
show
Coding Style introduced by
getContainer uses the super-global variable $_SERVER 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...
283
    {
284 50
        if (!empty($this->kernelDir)) {
285
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
286
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
287
        }
288
289 50
        $cacheKey = $this->kernelDir.'|'.$this->environment;
290 50
        if (empty($this->containers[$cacheKey])) {
291
            $options = array(
292 49
                'environment' => $this->environment,
293 49
            );
294 49
            $kernel = $this->createKernel($options);
295 49
            $kernel->boot();
296
297 49
            $this->containers[$cacheKey] = $kernel->getContainer();
298 49
        }
299
300 50
        if (isset($tmpKernelDir)) {
301
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
302
        }
303
304 50
        return $this->containers[$cacheKey];
305
    }
306
307
    /**
308
     * This function finds the time when the data blocks of a class definition
309
     * file were being written to, that is, the time when the content of the
310
     * file was changed.
311
     *
312
     * @param string $class The fully qualified class name of the fixture class to
313
     *                      check modification date on
314
     *
315
     * @return \DateTime|null
316
     */
317 3
    protected function getFixtureLastModified($class)
318
    {
319 3
        $lastModifiedDateTime = null;
320
321 3
        $reflClass = new \ReflectionClass($class);
322 3
        $classFileName = $reflClass->getFileName();
323
324 3
        if (file_exists($classFileName)) {
325 3
            $lastModifiedDateTime = new \DateTime();
326 3
            $lastModifiedDateTime->setTimestamp(filemtime($classFileName));
327 3
        }
328
329 3
        return $lastModifiedDateTime;
330
    }
331
332
    /**
333
     * Determine if the Fixtures that define a database backup have been
334
     * modified since the backup was made.
335
     *
336
     * @param array  $classNames The fixture classnames to check
337
     * @param string $backup     The fixture backup SQLite database file path
338
     *
339
     * @return bool TRUE if the backup was made since the modifications to the
340
     *              fixtures; FALSE otherwise
341
     */
342 7
    protected function isBackupUpToDate(array $classNames, $backup)
343
    {
344 7
        $backupLastModifiedDateTime = new \DateTime();
345 7
        $backupLastModifiedDateTime->setTimestamp(filemtime($backup));
346
347
        /** @var \Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader $loader */
348 7
        $loader = $this->getFixtureLoader($this->getContainer(), $classNames);
349
350
        // Use loader in order to fetch all the dependencies fixtures.
351 7
        foreach ($loader->getFixtures() as $className) {
352 3
            $fixtureLastModifiedDateTime = $this->getFixtureLastModified($className);
353 3
            if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) {
354 1
                return false;
355
            }
356 7
        }
357
358 7
        return true;
359
    }
360
361
    /**
362
     * Set the database to the provided fixtures.
363
     *
364
     * Drops the current database and then loads fixtures using the specified
365
     * classes. The parameter is a list of fully qualified class names of
366
     * classes that implement Doctrine\Common\DataFixtures\FixtureInterface
367
     * so that they can be loaded by the DataFixtures Loader::addFixture
368
     *
369
     * When using SQLite this method will automatically make a copy of the
370
     * loaded schema and fixtures which will be restored automatically in
371
     * case the same fixture classes are to be loaded again. Caveat: changes
372
     * to references and/or identities may go undetected.
373
     *
374
     * Depends on the doctrine data-fixtures library being available in the
375
     * class path.
376
     *
377
     * @param array  $classNames   List of fully qualified class names of fixtures to load
378
     * @param string $omName       The name of object manager to use
379
     * @param string $registryName The service id of manager registry to use
380
     * @param int    $purgeMode    Sets the ORM purge mode
381
     *
382
     * @return null|AbstractExecutor
383
     */
384 36
    protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
385
    {
386 36
        $container = $this->getContainer();
387
        /** @var ManagerRegistry $registry */
388 36
        $registry = $container->get($registryName);
389
        /** @var ObjectManager $om */
390 36
        $om = $registry->getManager($omName);
391 36
        $type = $registry->getName();
392
393 36
        $executorClass = 'PHPCR' === $type && class_exists('Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor')
394 36
            ? 'Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor'
395 36
            : 'Doctrine\\Common\\DataFixtures\\Executor\\'.$type.'Executor';
396 36
        $referenceRepository = new ProxyReferenceRepository($om);
397 36
        $cacheDriver = $om->getMetadataFactory()->getCacheDriver();
398
399 36
        if ($cacheDriver) {
400 36
            $cacheDriver->deleteAll();
401 36
        }
402
403 36
        if ('ORM' === $type) {
404 35
            $connection = $om->getConnection();
405 35
            if ($connection->getDriver() instanceof SqliteDriver) {
406 30
                $params = $connection->getParams();
407 30
                if (isset($params['master'])) {
408
                    $params = $params['master'];
409
                }
410
411 30
                $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
412 30
                if (!$name) {
413
                    throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
414
                }
415
416 30
                if (!isset(self::$cachedMetadatas[$omName])) {
417 10
                    self::$cachedMetadatas[$omName] = $om->getMetadataFactory()->getAllMetadata();
418 10
                    usort(self::$cachedMetadatas[$omName], function ($a, $b) {
419
                        return strcmp($a->name, $b->name);
420 10
                    });
421 10
                }
422 30
                $metadatas = self::$cachedMetadatas[$omName];
423
424 30
                if ($container->getParameter('liip_functional_test.cache_sqlite_db')) {
425 9
                    $backup = $container->getParameter('kernel.cache_dir').'/test_'.md5(serialize($metadatas).serialize($classNames)).'.db';
426 9
                    if (file_exists($backup) && file_exists($backup.'.ser') && $this->isBackupUpToDate($classNames, $backup)) {
427 7
                        $connection = $this->getContainer()->get('doctrine.orm.entity_manager')->getConnection();
428 7
                        if (null !== $connection) {
429 7
                            $connection->close();
430 7
                        }
431
432 7
                        $om->flush();
433 7
                        $om->clear();
434
435 7
                        $this->preFixtureBackupRestore($om, $referenceRepository, $backup);
436
437 7
                        copy($backup, $name);
438
439 7
                        $executor = new $executorClass($om);
440 7
                        $executor->setReferenceRepository($referenceRepository);
441 7
                        $executor->getReferenceRepository()->load($backup);
442
443 7
                        $this->postFixtureBackupRestore($backup);
444
445 7
                        return $executor;
446
                    }
447 3
                }
448
449
                // TODO: handle case when using persistent connections. Fail loudly?
450 24
                $schemaTool = new SchemaTool($om);
0 ignored issues
show
Compatibility introduced by
$om of type object<Doctrine\Common\Persistence\ObjectManager> is not a sub-type of object<Doctrine\ORM\EntityManagerInterface>. It seems like you assume a child interface of the interface Doctrine\Common\Persistence\ObjectManager to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
451 24
                $schemaTool->dropDatabase();
452 24
                if (!empty($metadatas)) {
453 24
                    $schemaTool->createSchema($metadatas);
454 24
                }
455 24
                $this->postFixtureSetup();
456
457 24
                $executor = new $executorClass($om);
458 24
                $executor->setReferenceRepository($referenceRepository);
459 24
            }
460 29
        }
461
462 30
        if (empty($executor)) {
463 6
            $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
464 6
            if ('PHPCR' === $type) {
465 1
                $purger = new $purgerClass($om);
466 1
                $initManager = $container->has('doctrine_phpcr.initializer_manager')
467 1
                    ? $container->get('doctrine_phpcr.initializer_manager')
468 1
                    : null;
469
470 1
                $executor = new $executorClass($om, $purger, $initManager);
471 1
            } else {
472 5
                if ('ORM' === $type) {
473 5
                    $purger = new $purgerClass(null, $this->excludedDoctrineTables);
474 5
                } else {
475
                    $purger = new $purgerClass();
476
                }
477
478 5
                if (null !== $purgeMode) {
479 2
                    $purger->setPurgeMode($purgeMode);
480 2
                }
481
482 5
                $executor = new $executorClass($om, $purger);
483
            }
484
485 6
            $executor->setReferenceRepository($referenceRepository);
486 6
            $executor->purge();
487 6
        }
488
489 30
        $loader = $this->getFixtureLoader($container, $classNames);
490
491 30
        $executor->execute($loader->getFixtures(), true);
492
493 30
        if (isset($name) && isset($backup)) {
494 3
            $this->preReferenceSave($om, $executor, $backup);
495
496 3
            $executor->getReferenceRepository()->save($backup);
497 3
            copy($name, $backup);
498
499 3
            $this->postReferenceSave($om, $executor, $backup);
500 3
        }
501
502 30
        return $executor;
503
    }
504
505
    /**
506
     * Clean database.
507
     *
508
     * @param ManagerRegistry $registry
509
     * @param EntityManager   $om
510
     * @param null            $omName
511
     * @param string          $registryName
512
     * @param int             $purgeMode
513
     */
514 9
    private function cleanDatabase(ManagerRegistry $registry, EntityManager $om, $omName = null, $registryName = 'doctrine', $purgeMode = null)
515
    {
516 9
        $connection = $om->getConnection();
517
518 9
        $mysql = ('ORM' === $registry->getName()
519 9
            && $connection->getDatabasePlatform() instanceof MySqlPlatform);
520
521 9
        if ($mysql) {
522 1
            $connection->query('SET FOREIGN_KEY_CHECKS=0');
523 1
        }
524
525 9
        $this->loadFixtures(array(), $omName, $registryName, $purgeMode);
526
527 9
        if ($mysql) {
528 1
            $connection->query('SET FOREIGN_KEY_CHECKS=1');
529 1
        }
530 9
    }
531
532
    /**
533
     * Locate fixture files.
534
     *
535
     * @param array $paths
536
     *
537
     * @return array $files
538
     *
539
     * @throws \InvalidArgumentException if a wrong path is given outside a bundle
540
     */
541 10
    private function locateResources($paths)
542
    {
543 10
        $files = array();
544
545 10
        $kernel = $this->getContainer()->get('kernel');
546
547 10
        foreach ($paths as $path) {
548 10
            if ('@' !== $path[0]) {
549 3
                if (!file_exists($path)) {
550 1
                    throw new \InvalidArgumentException(sprintf('Unable to find file "%s".', $path));
551
                }
552 2
                $files[] = $path;
553
554 2
                continue;
555
            }
556
557 7
            $files[] = $kernel->locateResource($path);
558 8
        }
559
560 8
        return $files;
561
    }
562
563
    /**
564
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
565
     * @param bool   $append
566
     * @param null   $omName
567
     * @param string $registryName
568
     * @param int    $purgeMode
569
     *
570
     * @return array
571
     *
572
     * @throws \BadMethodCallException
573
     */
574 10
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine', $purgeMode = null)
575
    {
576 10
        if (!class_exists('Nelmio\Alice\Fixtures')) {
577
            // This class is available during tests, no exception will be thrown.
578
            // @codeCoverageIgnoreStart
579
            throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
580
            // @codeCoverageIgnoreEnd
581
        }
582
583
        /** @var ContainerInterface $container */
584 10
        $container = $this->getContainer();
585
586
        /** @var ManagerRegistry $registry */
587 10
        $registry = $container->get($registryName);
588
589
        /** @var EntityManager $om */
590 10
        $om = $registry->getManager($omName);
591
592 10
        if (false === $append) {
593 9
            $this->cleanDatabase($registry, $om, $omName, $registryName, $purgeMode);
594 9
        }
595
596 10
        $files = $this->locateResources($paths);
597
598
        // Check if the Hautelook AliceBundle is registered and if yes, use it instead of Nelmio Alice
599 8
        $hautelookLoaderServiceName = 'hautelook_alice.fixtures.loader';
600 8
        if ($container->has($hautelookLoaderServiceName)) {
601 3
            $loaderService = $container->get($hautelookLoaderServiceName);
602 3
            $persisterClass = class_exists('Nelmio\Alice\ORM\Doctrine') ?
603 3
                'Nelmio\Alice\ORM\Doctrine' :
604 3
                'Nelmio\Alice\Persister\Doctrine';
605
606 3
            return $loaderService->load(new $persisterClass($om), $files);
607
        }
608
609 5
        return Fixtures::load($files, $om);
610
    }
611
612
    /**
613
     * Callback function to be executed after Schema creation.
614
     * Use this to execute acl:init or other things necessary.
615
     */
616 24
    protected function postFixtureSetup()
617
    {
618 24
    }
619
620
    /**
621
     * Callback function to be executed after Schema restore.
622
     *
623
     * @return WebTestCase
624
     *
625
     * @deprecated since version 1.8, to be removed in 2.0. Use postFixtureBackupRestore method instead.
626
     */
627 7
    protected function postFixtureRestore()
628
    {
629 7
    }
630
631
    /**
632
     * Callback function to be executed before Schema restore.
633
     *
634
     * @param ObjectManager            $manager             The object manager
635
     * @param ProxyReferenceRepository $referenceRepository The reference repository
636
     *
637
     * @return WebTestCase
638
     *
639
     * @deprecated since version 1.8, to be removed in 2.0. Use preFixtureBackupRestore method instead.
640
     */
641 7
    protected function preFixtureRestore(ObjectManager $manager, ProxyReferenceRepository $referenceRepository)
0 ignored issues
show
Unused Code introduced by
The parameter $manager is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $referenceRepository is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
642
    {
643 7
    }
644
645
    /**
646
     * Callback function to be executed after Schema restore.
647
     *
648
     * @param string $backupFilePath Path of file used to backup the references of the data fixtures
649
     *
650
     * @return WebTestCase
651
     */
652 7
    protected function postFixtureBackupRestore($backupFilePath)
0 ignored issues
show
Unused Code introduced by
The parameter $backupFilePath is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
653
    {
654 7
        $this->postFixtureRestore();
0 ignored issues
show
Deprecated Code introduced by
The method Liip\FunctionalTestBundl...e::postFixtureRestore() has been deprecated with message: since version 1.8, to be removed in 2.0. Use postFixtureBackupRestore method instead.

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...
655
656 7
        return $this;
657
    }
658
659
    /**
660
     * Callback function to be executed before Schema restore.
661
     *
662
     * @param ObjectManager            $manager             The object manager
663
     * @param ProxyReferenceRepository $referenceRepository The reference repository
664
     * @param string                   $backupFilePath      Path of file used to backup the references of the data fixtures
665
     *
666
     * @return WebTestCase
667
     */
668 7
    protected function preFixtureBackupRestore(
669
        ObjectManager $manager,
670
        ProxyReferenceRepository $referenceRepository,
671
        $backupFilePath
0 ignored issues
show
Unused Code introduced by
The parameter $backupFilePath is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
672
    ) {
673 7
        $this->preFixtureRestore($manager, $referenceRepository);
0 ignored issues
show
Deprecated Code introduced by
The method Liip\FunctionalTestBundl...se::preFixtureRestore() has been deprecated with message: since version 1.8, to be removed in 2.0. Use preFixtureBackupRestore method instead.

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...
674
675 7
        return $this;
676
    }
677
678
    /**
679
     * Callback function to be executed after save of references.
680
     *
681
     * @param ObjectManager    $manager        The object manager
682
     * @param AbstractExecutor $executor       Executor of the data fixtures
683
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
684
     *
685
     * @return WebTestCase
686
     */
687 3
    protected function postReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
0 ignored issues
show
Unused Code introduced by
The parameter $manager is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $executor is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $backupFilePath is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
688
    {
689 3
    }
690
691
    /**
692
     * Callback function to be executed before save of references.
693
     *
694
     * @param ObjectManager    $manager        The object manager
695
     * @param AbstractExecutor $executor       Executor of the data fixtures
696
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
697
     *
698
     * @return WebTestCase
699
     */
700 3
    protected function preReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
0 ignored issues
show
Unused Code introduced by
The parameter $manager is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $executor is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $backupFilePath is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
701
    {
702 3
    }
703
704
    /**
705
     * Retrieve Doctrine DataFixtures loader.
706
     *
707
     * @param ContainerInterface $container
708
     * @param array              $classNames
709
     *
710
     * @return Loader
711
     */
712 36
    protected function getFixtureLoader(ContainerInterface $container, array $classNames)
713
    {
714 36
        $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
715 36
            ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
716 36
            : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
717
                // This class is not available during tests.
718
                // @codeCoverageIgnoreStart
719
                ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
720
                // @codeCoverageIgnoreEnd
721 36
                : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
722
723 36
        $loader = new $loaderClass($container);
724
725 36
        foreach ($classNames as $className) {
726 11
            $this->loadFixtureClass($loader, $className);
727 36
        }
728
729 36
        return $loader;
730
    }
731
732
    /**
733
     * Load a data fixture class.
734
     *
735
     * @param Loader $loader
736
     * @param string $className
737
     */
738 11
    protected function loadFixtureClass($loader, $className)
739
    {
740 11
        $fixture = new $className();
741
742 11
        if ($loader->hasFixture($fixture)) {
743 2
            unset($fixture);
744
745 2
            return;
746
        }
747
748 11
        $loader->addFixture($fixture);
749
750 11
        if ($fixture instanceof DependentFixtureInterface) {
751 2
            foreach ($fixture->getDependencies() as $dependency) {
752 2
                $this->loadFixtureClass($loader, $dependency);
753 2
            }
754 2
        }
755 11
    }
756
757
    /**
758
     * Creates an instance of a lightweight Http client.
759
     *
760
     * If $authentication is set to 'true' it will use the content of
761
     * 'liip_functional_test.authentication' to log in.
762
     *
763
     * $params can be used to pass headers to the client, note that they have
764
     * to follow the naming format used in $_SERVER.
765
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
766
     *
767
     * @param bool|array $authentication
768
     * @param array      $params
769
     *
770
     * @return Client
771
     */
772 55
    protected function makeClient($authentication = false, array $params = array())
773
    {
774 55
        if ($authentication) {
775 2
            if (true === $authentication) {
776
                $authentication = array(
777 1
                    'username' => $this->getContainer()
778 1
                        ->getParameter('liip_functional_test.authentication.username'),
779 1
                    'password' => $this->getContainer()
780 1
                        ->getParameter('liip_functional_test.authentication.password'),
781 1
                );
782 1
            }
783
784 2
            $params = array_merge($params, array(
785 2
                'PHP_AUTH_USER' => $authentication['username'],
786 2
                'PHP_AUTH_PW' => $authentication['password'],
787 2
            ));
788 2
        }
789
790 55
        $client = static::createClient(array('environment' => $this->environment), $params);
791
792 55
        if ($this->firewallLogins) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->firewallLogins of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
793
            // has to be set otherwise "hasPreviousSession" in Request returns false.
794 2
            $options = $client->getContainer()->getParameter('session.storage.options');
795
796 2
            if (!$options || !isset($options['name'])) {
797
                throw new \InvalidArgumentException('Missing session.storage.options#name');
798
            }
799
800 2
            $session = $client->getContainer()->get('session');
801
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
802 2
            if ($session instanceof Session) {
803 2
                $session->setId(uniqid());
804 2
            }
805
806 2
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
807
808
            /** @var $user UserInterface */
809 2
            foreach ($this->firewallLogins as $firewallName => $user) {
810 2
                $token = $this->createUserToken($user, $firewallName);
811
812
                // BC: security.token_storage is available on Symfony 2.6+
813
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
814 2
                if ($client->getContainer()->has('security.token_storage')) {
815 2
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
816 2
                } else {
817
                    // This block will never be reached with Symfony 2.6+
818
                    // @codeCoverageIgnoreStart
819
                    $tokenStorage = $client->getContainer()->get('security.context');
820
                    // @codeCoverageIgnoreEnd
821
                }
822
823 2
                $tokenStorage->setToken($token);
824 2
                $session->set('_security_'.$firewallName, serialize($token));
825 2
            }
826
827 2
            $session->save();
828 2
        }
829
830 55
        return $client;
831
    }
832
833
    /**
834
     * Create User Token.
835
     *
836
     * Factory method for creating a User Token object for the firewall based on
837
     * the user object provided. By default it will be a Username/Password
838
     * Token based on the user's credentials, but may be overridden for custom
839
     * tokens in your applications.
840
     *
841
     * @param UserInterface $user         The user object to base the token off of
842
     * @param string        $firewallName name of the firewall provider to use
843
     *
844
     * @return TokenInterface The token to be used in the security context
845
     */
846 2
    protected function createUserToken(UserInterface $user, $firewallName)
847
    {
848 2
        return new UsernamePasswordToken(
849 2
            $user,
850 2
            null,
851 2
            $firewallName,
852 2
            $user->getRoles()
0 ignored issues
show
Documentation introduced by
$user->getRoles() is of type array<integer,object<Sym...Core\Role\Role>|string>, but the function expects a array<integer,object<Sym...\RoleInterface>|string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
853 2
        );
854
    }
855
856
    /**
857
     * Extracts the location from the given route.
858
     *
859
     * @param string $route    The name of the route
860
     * @param array  $params   Set of parameters
861
     * @param int    $absolute
862
     *
863
     * @return string
864
     */
865 1
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
866
    {
867 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
868
    }
869
870
    /**
871
     * Checks the success state of a response.
872
     *
873
     * @param Response $response Response object
874
     * @param bool     $success  to define whether the response is expected to be successful
875
     * @param string   $type
876
     */
877 6
    public function isSuccessful(Response $response, $success = true, $type = 'text/html')
878
    {
879 6
        HttpAssertions::isSuccessful($response, $success, $type);
880 5
    }
881
882
    /**
883
     * Executes a request on the given url and returns the response contents.
884
     *
885
     * This method also asserts the request was successful.
886
     *
887
     * @param string $path           path of the requested page
888
     * @param string $method         The HTTP method to use, defaults to GET
889
     * @param bool   $authentication Whether to use authentication, defaults to false
890
     * @param bool   $success        to define whether the response is expected to be successful
891
     *
892
     * @return string
893
     */
894 1
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
895
    {
896 1
        $client = $this->makeClient($authentication);
897 1
        $client->request($method, $path);
898
899 1
        $content = $client->getResponse()->getContent();
900 1
        if (is_bool($success)) {
901 1
            $this->isSuccessful($client->getResponse(), $success);
0 ignored issues
show
Bug introduced by
It seems like $client->getResponse() can be null; however, isSuccessful() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
902 1
        }
903
904 1
        return $content;
905
    }
906
907
    /**
908
     * Executes a request on the given url and returns a Crawler object.
909
     *
910
     * This method also asserts the request was successful.
911
     *
912
     * @param string $path           path of the requested page
913
     * @param string $method         The HTTP method to use, defaults to GET
914
     * @param bool   $authentication Whether to use authentication, defaults to false
915
     * @param bool   $success        Whether the response is expected to be successful
916
     *
917
     * @return Crawler
918
     */
919 1
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
920
    {
921 1
        $client = $this->makeClient($authentication);
922 1
        $crawler = $client->request($method, $path);
923
924 1
        $this->isSuccessful($client->getResponse(), $success);
0 ignored issues
show
Bug introduced by
It seems like $client->getResponse() can be null; however, isSuccessful() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
925
926 1
        return $crawler;
927
    }
928
929
    /**
930
     * @param UserInterface $user
931
     * @param string        $firewallName
932
     *
933
     * @return WebTestCase
934
     */
935 2
    public function loginAs(UserInterface $user, $firewallName)
936
    {
937 2
        $this->firewallLogins[$firewallName] = $user;
938
939 2
        return $this;
940
    }
941
942
    /**
943
     * Asserts that the HTTP response code of the last request performed by
944
     * $client matches the expected code. If not, raises an error with more
945
     * information.
946
     *
947
     * @param $expectedStatusCode
948
     * @param Client $client
949
     */
950 12
    public function assertStatusCode($expectedStatusCode, Client $client)
951
    {
952 12
        HttpAssertions::assertStatusCode($expectedStatusCode, $client);
953 9
    }
954
955
    /**
956
     * Assert that the last validation errors within $container match the
957
     * expected keys.
958
     *
959
     * @param array              $expected  A flat array of field names
960
     * @param ContainerInterface $container
961
     */
962 3
    public function assertValidationErrors(array $expected, ContainerInterface $container)
963
    {
964 3
        HttpAssertions::assertValidationErrors($expected, $container);
965 1
    }
966
967
    /**
968
     * @param array $excludedDoctrineTables
969
     */
970 1
    public function setExcludedDoctrineTables($excludedDoctrineTables)
971
    {
972 1
        $this->excludedDoctrineTables = $excludedDoctrineTables;
973 1
    }
974
}
975