Completed
Pull Request — master (#351)
by Alexis
25:07
created

WebTestCase::cleanDatabase()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.5923

Importance

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