Passed
Push — master ( bc7a69...e18866 )
by Alexis
03:46
created

WebTestCase::locateResources()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

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