Passed
Pull Request — master (#308)
by
unknown
09:46 queued 04:12
created

WebTestCase::getCallingClassPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 0
cts 4
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 0
crap 2
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\Filesystem\Filesystem;
23
use Symfony\Component\HttpKernel\Kernel;
24
use Symfony\Component\HttpFoundation\Response;
25
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
26
use Symfony\Component\Security\Core\User\UserInterface;
27
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
28
use Symfony\Component\DependencyInjection\ContainerInterface;
29
use Symfony\Component\HttpFoundation\Session\Session;
30
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
31
use Symfony\Bridge\Doctrine\ManagerRegistry;
32
use Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader;
33
use Doctrine\Common\Persistence\ObjectManager;
34
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
35
use Doctrine\Common\DataFixtures\Executor\AbstractExecutor;
36
use Doctrine\Common\DataFixtures\ProxyReferenceRepository;
37
use Doctrine\DBAL\Driver\PDOSqlite\Driver as SqliteDriver;
38
use Doctrine\DBAL\Platforms\MySqlPlatform;
39
use Doctrine\ORM\EntityManager;
40
use Doctrine\ORM\Tools\SchemaTool;
41
use Nelmio\Alice\Fixtures;
42
use Liip\FunctionalTestBundle\Utils\HttpAssertions;
43
44
/**
45
 * @author Lea Haensenberger
46
 * @author Lukas Kahwe Smith <[email protected]>
47
 * @author Benjamin Eberlei <[email protected]>
48
 */
49
abstract class WebTestCase extends BaseWebTestCase
50
{
51
    protected $environment = 'test';
52
    protected $containers;
53
    protected $kernelDir;
54
    // 5 * 1024 * 1024 KB
55
    protected $maxMemory = 5242880;
56
57
    // RUN COMMAND
58
    protected $verbosityLevel;
59
    protected $decorated;
60
61
    /**
62
     * @var array
63
     */
64
    private $firewallLogins = array();
65
66
    /**
67
     * @var array
68
     */
69
    private $excludedDoctrineTables = array();
70
71
    /**
72
     * @var array
73
     */
74
    private static $cachedMetadatas = array();
75
76
    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...
77
    {
78
        $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : static::getPhpUnitXmlDir();
79
80
        list($appname) = explode('\\', get_called_class());
81
82
        $class = $appname.'Kernel';
83
        $file = $dir.'/'.strtolower($appname).'/'.$class.'.php';
84
        if (!file_exists($file)) {
85
            return parent::getKernelClass();
86
        }
87
        require_once $file;
88
89
        return $class;
90
    }
91
92
    /**
93
     * Creates a mock object of a service identified by its id.
94
     *
95
     * @param string $id
96
     *
97
     * @return \PHPUnit_Framework_MockObject_MockBuilder
98
     */
99
    protected function getServiceMockBuilder($id)
100
    {
101
        $service = $this->getContainer()->get($id);
102
        $class = get_class($service);
103
104
        return $this->getMockBuilder($class)->disableOriginalConstructor();
105
    }
106
107
    /**
108
     * Builds up the environment to run the given command.
109
     *
110
     * @param string $name
111
     * @param array  $params
112
     * @param bool   $reuseKernel
113
     *
114
     * @return string
115
     */
116 12
    protected function runCommand($name, array $params = array(), $reuseKernel = false)
117
    {
118 12
        array_unshift($params, $name);
119
120 12
        if (!$reuseKernel) {
121 12
            if (null !== static::$kernel) {
122 9
                static::$kernel->shutdown();
123 9
            }
124
125 12
            $kernel = static::$kernel = $this->createKernel(array('environment' => $this->environment));
126 12
            $kernel->boot();
127 12
        } else {
128 2
            $kernel = $this->getContainer()->get('kernel');
129
        }
130
131 12
        $application = new Application($kernel);
132 12
        $application->setAutoExit(false);
133
134
        // @codeCoverageIgnoreStart
135
        if ('203' === substr(Kernel::VERSION_ID, 0, 3)) {
136
            $params = $this->configureVerbosityForSymfony203($params);
137
        }
138
        // @codeCoverageIgnoreEnd
139
140 12
        $input = new ArrayInput($params);
141 12
        $input->setInteractive(false);
142
143 12
        $fp = fopen('php://temp/maxmemory:'.$this->maxMemory, 'r+');
144 12
        $output = new StreamOutput($fp, $this->getVerbosityLevel(), $this->getDecorated());
145
146 11
        $application->run($input, $output);
147
148 11
        rewind($fp);
149
150 11
        return stream_get_contents($fp);
151
    }
152
153
    /**
154
     * Retrieves the output verbosity level.
155
     *
156
     * @see Symfony\Component\Console\Output\OutputInterface for available levels
157
     *
158
     * @return int
159
     *
160
     * @throws \OutOfBoundsException If the set value isn't accepted
161
     */
162 12
    protected function getVerbosityLevel()
163
    {
164
        // If `null`, is not yet set
165 12
        if (null === $this->verbosityLevel) {
166
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
167 6
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
168 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
169
170 6
            $this->verbosityLevel = constant($verbosity);
171 6
        }
172
173
        // If string, it is set by the developer, so check that the value is an accepted one
174 12
        if (is_string($this->verbosityLevel)) {
175 6
            $level = strtoupper($this->verbosityLevel);
176 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
177
178 6
            if (!defined($verbosity)) {
179 1
                throw new \OutOfBoundsException(
180 1
                    sprintf('The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".', $level)
181 1
                );
182
            }
183
184 5
            $this->verbosityLevel = constant($verbosity);
185 5
        }
186
187 11
        return $this->verbosityLevel;
188
    }
189
190
    /**
191
     * In Symfony 2.3.* the verbosity level has to be set through {Symfony\Component\Console\Input\ArrayInput} and not
192
     * in {Symfony\Component\Console\Output\OutputInterface}.
193
     *
194
     * This method builds $params to be passed to {Symfony\Component\Console\Input\ArrayInput}.
195
     *
196
     * @codeCoverageIgnore
197
     *
198
     * @param array $params
199
     *
200
     * @return array
201
     */
202
    private function configureVerbosityForSymfony203(array $params)
203
    {
204
        switch ($this->getVerbosityLevel()) {
205
            case OutputInterface::VERBOSITY_QUIET:
206
                $params['-q'] = '-q';
207
                break;
208
209
            case OutputInterface::VERBOSITY_VERBOSE:
210
                $params['-v'] = '';
211
                break;
212
213
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
214
                $params['-vv'] = '';
215
                break;
216
217
            case OutputInterface::VERBOSITY_DEBUG:
218
                $params['-vvv'] = '';
219
                break;
220
        }
221
222
        return $params;
223
    }
224
225 6
    public function setVerbosityLevel($level)
226
    {
227 6
        $this->verbosityLevel = $level;
228 6
    }
229
230
    /**
231
     * Retrieves the flag indicating if the output should be decorated or not.
232
     *
233
     * @return bool
234
     */
235 11
    protected function getDecorated()
236
    {
237 11
        if (null === $this->decorated) {
238
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
239 5
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
240 5
        }
241
242
        // Check the local decorated flag
243 11
        if (false === is_bool($this->decorated)) {
244
            throw new \OutOfBoundsException(
245
                sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated))
246
            );
247
        }
248
249 11
        return $this->decorated;
250
    }
251
252 6
    public function isDecorated($decorated)
253
    {
254 6
        $this->decorated = $decorated;
255 6
    }
256
257
    /**
258
     * Get an instance of the dependency injection container.
259
     * (this creates a kernel *without* parameters).
260
     *
261
     * @return ContainerInterface
262
     */
263 49
    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...
264
    {
265 49
        if (!empty($this->kernelDir)) {
266
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
267
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
268
        }
269
270 49
        $cacheKey = $this->kernelDir.'|'.$this->environment;
271 49
        if (empty($this->containers[$cacheKey])) {
272
            $options = array(
273 48
                'environment' => $this->environment,
274 48
            );
275 48
            $kernel = $this->createKernel($options);
276 48
            $kernel->boot();
277
278 48
            $this->containers[$cacheKey] = $kernel->getContainer();
279 48
        }
280
281 49
        if (isset($tmpKernelDir)) {
282
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
283
        }
284
285 49
        return $this->containers[$cacheKey];
286
    }
287
288
    /**
289
     * This function finds the time when the data blocks of a class definition
290
     * file were being written to, that is, the time when the content of the
291
     * file was changed.
292
     *
293
     * @param string $class The fully qualified class name of the fixture class to
294
     *                      check modification date on
295
     *
296
     * @return \DateTime|null
297
     */
298 3
    protected function getFixtureLastModified($class)
299
    {
300 3
        $lastModifiedDateTime = null;
301
302 3
        $reflClass = new \ReflectionClass($class);
303 3
        $classFileName = $reflClass->getFileName();
304
305 3
        if (file_exists($classFileName)) {
306 3
            $lastModifiedDateTime = new \DateTime();
307 3
            $lastModifiedDateTime->setTimestamp(filemtime($classFileName));
308 3
        }
309
310 3
        return $lastModifiedDateTime;
311
    }
312
313
    /**
314
     * Determine if the Fixtures that define a database backup have been
315
     * modified since the backup was made.
316
     *
317
     * @param array  $classNames The fixture classnames to check
318
     * @param string $backup     The fixture backup SQLite database file path
319
     *
320
     * @return bool TRUE if the backup was made since the modifications to the
321
     *              fixtures; FALSE otherwise
322
     */
323 7
    protected function isBackupUpToDate(array $classNames, $backup)
324
    {
325 7
        $backupLastModifiedDateTime = new \DateTime();
326 7
        $backupLastModifiedDateTime->setTimestamp(filemtime($backup));
327
328
        /** @var \Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader $loader */
329 7
        $loader = $this->getFixtureLoader($this->getContainer(), $classNames);
330
331
        // Use loader in order to fetch all the dependencies fixtures.
332 7
        foreach ($loader->getFixtures() as $className) {
333 3
            $fixtureLastModifiedDateTime = $this->getFixtureLastModified($className);
334 3
            if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) {
335 1
                return false;
336
            }
337 7
        }
338
339 7
        return true;
340
    }
341
342
    /**
343
     * Set the database to the provided fixtures.
344
     *
345
     * Drops the current database and then loads fixtures using the specified
346
     * classes. The parameter is a list of fully qualified class names of
347
     * classes that implement Doctrine\Common\DataFixtures\FixtureInterface
348
     * so that they can be loaded by the DataFixtures Loader::addFixture
349
     *
350
     * When using SQLite this method will automatically make a copy of the
351
     * loaded schema and fixtures which will be restored automatically in
352
     * case the same fixture classes are to be loaded again. Caveat: changes
353
     * to references and/or identities may go undetected.
354
     *
355
     * Depends on the doctrine data-fixtures library being available in the
356
     * class path.
357
     *
358
     * @param array  $classNames   List of fully qualified class names of fixtures to load
359
     * @param string $omName       The name of object manager to use
360
     * @param string $registryName The service id of manager registry to use
361
     * @param int    $purgeMode    Sets the ORM purge mode
362
     *
363
     * @return null|AbstractExecutor
364
     */
365 36
    protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
366
    {
367 36
        $container = $this->getContainer();
368
        /** @var ManagerRegistry $registry */
369 36
        $registry = $container->get($registryName);
370
        /** @var ObjectManager $om */
371 36
        $om = $registry->getManager($omName);
372 36
        $type = $registry->getName();
373
374 36
        $executorClass = 'PHPCR' === $type && class_exists('Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor')
375 36
            ? 'Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor'
376 36
            : 'Doctrine\\Common\\DataFixtures\\Executor\\'.$type.'Executor';
377 36
        $referenceRepository = new ProxyReferenceRepository($om);
378 36
        $cacheDriver = $om->getMetadataFactory()->getCacheDriver();
379
380 36
        if ($cacheDriver) {
381 36
            $cacheDriver->deleteAll();
382 36
        }
383
384 36
        if ('ORM' === $type) {
385 35
            $connection = $om->getConnection();
386 35
            if ($connection->getDriver() instanceof SqliteDriver) {
387 30
                $params = $connection->getParams();
388 30
                if (isset($params['master'])) {
389
                    $params = $params['master'];
390
                }
391
392 30
                $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
393 30
                if (!$name) {
394
                    throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
395
                }
396
397 30
                if (!isset(self::$cachedMetadatas[$omName])) {
398 10
                    self::$cachedMetadatas[$omName] = $om->getMetadataFactory()->getAllMetadata();
399 10
                    usort(self::$cachedMetadatas[$omName], function ($a, $b) {
400
                        return strcmp($a->name, $b->name);
401 10
                    });
402 10
                }
403 30
                $metadatas = self::$cachedMetadatas[$omName];
404
405 30
                if ($container->getParameter('liip_functional_test.cache_sqlite_db')) {
406 9
                    $backup = $container->getParameter('kernel.cache_dir').'/test_'.md5(serialize($metadatas).serialize($classNames)).'.db';
407 9
                    if (file_exists($backup) && file_exists($backup.'.ser') && $this->isBackupUpToDate($classNames, $backup)) {
408 7
                        $om->flush();
409 7
                        $om->clear();
410
411 7
                        $this->preFixtureRestore($om, $referenceRepository);
412
413 7
                        copy($backup, $name);
414
415 7
                        $executor = new $executorClass($om);
416 7
                        $executor->setReferenceRepository($referenceRepository);
417 7
                        $executor->getReferenceRepository()->load($backup);
418
419 7
                        $this->postFixtureRestore();
420
421 7
                        return $executor;
422
                    }
423 3
                }
424
425
                // TODO: handle case when using persistent connections. Fail loudly?
426 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...
427 24
                $schemaTool->dropDatabase();
428 24
                if (!empty($metadatas)) {
429 24
                    $schemaTool->createSchema($metadatas);
430 24
                }
431 24
                $this->postFixtureSetup();
432
433 24
                $executor = new $executorClass($om);
434 24
                $executor->setReferenceRepository($referenceRepository);
435 24
            }
436 29
        }
437
438 30
        if (empty($executor)) {
439 6
            $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
440 6
            if ('PHPCR' === $type) {
441 1
                $purger = new $purgerClass($om);
442 1
                $initManager = $container->has('doctrine_phpcr.initializer_manager')
443 1
                    ? $container->get('doctrine_phpcr.initializer_manager')
444 1
                    : null;
445
446 1
                $executor = new $executorClass($om, $purger, $initManager);
447 1
            } else {
448 5
                if ('ORM' === $type) {
449 5
                    $purger = new $purgerClass(null, $this->excludedDoctrineTables);
450 5
                } else {
451
                    $purger = new $purgerClass();
452
                }
453
454 5
                if (null !== $purgeMode) {
455 2
                    $purger->setPurgeMode($purgeMode);
456 2
                }
457
458 5
                $executor = new $executorClass($om, $purger);
459
            }
460
461 6
            $executor->setReferenceRepository($referenceRepository);
462 6
            $executor->purge();
463 6
        }
464
465 30
        $loader = $this->getFixtureLoader($container, $classNames);
466
467 30
        $executor->execute($loader->getFixtures(), true);
468
469 30
        if (isset($name) && isset($backup)) {
470 3
            $this->preReferenceSave($om, $executor, $backup);
471
472 3
            $executor->getReferenceRepository()->save($backup);
473 3
            copy($name, $backup);
474
475 3
            $this->postReferenceSave($om, $executor, $backup);
476 3
        }
477
478 30
        return $executor;
479
    }
480
481
    /**
482
     * Clean database.
483
     *
484
     * @param ManagerRegistry $registry
485
     * @param EntityManager   $om
486
     * @param null            $omName
487
     */
488 9
    private function cleanDatabase(ManagerRegistry $registry, EntityManager $om, $omName = null)
489
    {
490 9
        $connection = $om->getConnection();
491
492 9
        $mysql = ($registry->getName() === 'ORM'
493 9
            && $connection->getDatabasePlatform() instanceof MySqlPlatform);
494
495 9
        if ($mysql) {
496 1
            $connection->query('SET FOREIGN_KEY_CHECKS=0');
497 1
        }
498
499 9
        $this->loadFixtures(array(), $omName);
500
501 9
        if ($mysql) {
502 1
            $connection->query('SET FOREIGN_KEY_CHECKS=1');
503 1
        }
504 9
    }
505
506
    /**
507
     * Locate fixture files.
508
     *
509
     * @param array $paths
510
     *
511
     * @return array $files
512
     *
513
     * @throws \InvalidArgumentException if a wrong path is given outside a bundle
514
     */
515 9
    private function locateResources($paths)
516
    {
517 9
        $files = array();
518 9
        $kernel = $this->getContainer()->get('kernel');
519 9
        foreach ($paths as $path) {
520 9
            if ($path[0] !== '@') {
521 3
                $fs = new Filesystem();
522 3
                if (!$fs->isAbsolutePath($path)) {
523
                    $path = $this->getCallingClassPath().'/'.$path;
524
                }
525 3
                if (!file_exists($path)) {
526 1
                    throw new \InvalidArgumentException(sprintf('Unable to find file "%s".', $path));
527
                }
528 2
                $files[] = $path;
529 2
                continue;
530
            }
531 6
            $files[] = $kernel->locateResource($path);
532 7
        }
533
534 7
        return $files;
535
    }
536
537
    private function getCallingClassPath()
538
    {
539
        $reflector = new \ReflectionClass($this->getCallingClass());
540
        $callingClassFilename = $reflector->getFileName();
541
542
        return dirname($callingClassFilename);
543
    }
544
545
    private function getCallingClass()
546
    {
547
        $trace = debug_backtrace();
548
        $class = $trace[1]['class'];
549
        for ($i = 1; $i < count($trace); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
550
            if (isset($trace[$i])) {
551
                if ($class != $trace[$i]['class']) {
552
                    return $trace[$i]['class'];
553
                }
554
            }
555
        }
556
    }
557
558
    /**
559
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
560
     * @param bool   $append
561
     * @param null   $omName
562
     * @param string $registryName
563
     *
564
     * @return array
565
     *
566
     * @throws \BadMethodCallException
567
     */
568 9
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine')
569
    {
570 9
        if (!class_exists('Nelmio\Alice\Fixtures')) {
571
            // This class is available during tests, no exception will be thrown.
572
            // @codeCoverageIgnoreStart
573
            throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
574
            // @codeCoverageIgnoreEnd
575
        }
576
577
        /** @var ContainerInterface $container */
578 9
        $container = $this->getContainer();
579
580
        /** @var ManagerRegistry $registry */
581 9
        $registry = $container->get($registryName);
582
583
        /** @var EntityManager $om */
584 9
        $om = $registry->getManager($omName);
585
586 9
        if ($append === false) {
587 9
            $this->cleanDatabase($registry, $om, $omName);
588 9
        }
589
590 9
        $files = $this->locateResources($paths);
591
592
        // Check if the Hautelook AliceBundle is registered and if yes, use it instead of Nelmio Alice
593 7
        $hautelookLoaderServiceName = 'hautelook_alice.fixtures.loader';
594 7
        if ($container->has($hautelookLoaderServiceName)) {
595 3
            $loaderService = $container->get($hautelookLoaderServiceName);
596 3
            $persisterClass = class_exists('Nelmio\Alice\ORM\Doctrine') ?
597 3
                'Nelmio\Alice\ORM\Doctrine' :
598 3
                'Nelmio\Alice\Persister\Doctrine';
599
600 3
            return $loaderService->load(new $persisterClass($om), $files);
601
        }
602
603 4
        return Fixtures::load($files, $om);
604
    }
605
606
    /**
607
     * Callback function to be executed after Schema creation.
608
     * Use this to execute acl:init or other things necessary.
609
     */
610 24
    protected function postFixtureSetup()
611
    {
612 24
    }
613
614
    /**
615
     * Callback function to be executed after Schema restore.
616
     *
617
     * @return WebTestCase
618
     */
619 7
    protected function postFixtureRestore()
620
    {
621 7
    }
622
623
    /**
624
     * Callback function to be executed before Schema restore.
625
     *
626
     * @param ObjectManager            $manager             The object manager
627
     * @param ProxyReferenceRepository $referenceRepository The reference repository
628
     *
629
     * @return WebTestCase
630
     */
631 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...
632
    {
633 7
    }
634
635
    /**
636
     * Callback function to be executed after save of references.
637
     *
638
     * @param ObjectManager    $manager        The object manager
639
     * @param AbstractExecutor $executor       Executor of the data fixtures
640
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
641
     *
642
     * @return WebTestCase
643
     */
644 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...
645
    {
646 3
    }
647
648
    /**
649
     * Callback function to be executed before save of references.
650
     *
651
     * @param ObjectManager    $manager        The object manager
652
     * @param AbstractExecutor $executor       Executor of the data fixtures
653
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
654
     *
655
     * @return WebTestCase
656
     */
657 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...
658
    {
659 3
    }
660
661
    /**
662
     * Retrieve Doctrine DataFixtures loader.
663
     *
664
     * @param ContainerInterface $container
665
     * @param array              $classNames
666
     *
667
     * @return Loader
668
     */
669 36
    protected function getFixtureLoader(ContainerInterface $container, array $classNames)
670
    {
671 36
        $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
672 36
            ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
673 36
            : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
674
                // This class is not available during tests.
675
                // @codeCoverageIgnoreStart
676
                ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
677
                // @codeCoverageIgnoreEnd
678 36
                : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
679
680 36
        $loader = new $loaderClass($container);
681
682 36
        foreach ($classNames as $className) {
683 11
            $this->loadFixtureClass($loader, $className);
684 36
        }
685
686 36
        return $loader;
687
    }
688
689
    /**
690
     * Load a data fixture class.
691
     *
692
     * @param Loader $loader
693
     * @param string $className
694
     */
695 11
    protected function loadFixtureClass($loader, $className)
696
    {
697 11
        $fixture = new $className();
698
699 11
        if ($loader->hasFixture($fixture)) {
700 2
            unset($fixture);
701
702 2
            return;
703
        }
704
705 11
        $loader->addFixture($fixture);
706
707 11
        if ($fixture instanceof DependentFixtureInterface) {
708 2
            foreach ($fixture->getDependencies() as $dependency) {
709 2
                $this->loadFixtureClass($loader, $dependency);
710 2
            }
711 2
        }
712 11
    }
713
714
    /**
715
     * Creates an instance of a lightweight Http client.
716
     *
717
     * If $authentication is set to 'true' it will use the content of
718
     * 'liip_functional_test.authentication' to log in.
719
     *
720
     * $params can be used to pass headers to the client, note that they have
721
     * to follow the naming format used in $_SERVER.
722
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
723
     *
724
     * @param bool|array $authentication
725
     * @param array      $params
726
     *
727
     * @return Client
728
     */
729 54
    protected function makeClient($authentication = false, array $params = array())
730
    {
731 54
        if ($authentication) {
732 2
            if ($authentication === true) {
733
                $authentication = array(
734 1
                    'username' => $this->getContainer()
735 1
                        ->getParameter('liip_functional_test.authentication.username'),
736 1
                    'password' => $this->getContainer()
737 1
                        ->getParameter('liip_functional_test.authentication.password'),
738 1
                );
739 1
            }
740
741 2
            $params = array_merge($params, array(
742 2
                'PHP_AUTH_USER' => $authentication['username'],
743 2
                'PHP_AUTH_PW' => $authentication['password'],
744 2
            ));
745 2
        }
746
747 54
        $client = static::createClient(array('environment' => $this->environment), $params);
748
749 54
        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...
750
            // has to be set otherwise "hasPreviousSession" in Request returns false.
751 2
            $options = $client->getContainer()->getParameter('session.storage.options');
752
753 2
            if (!$options || !isset($options['name'])) {
754
                throw new \InvalidArgumentException('Missing session.storage.options#name');
755
            }
756
757 2
            $session = $client->getContainer()->get('session');
758
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
759 2
            if ($session instanceof Session) {
760 2
                $session->setId(uniqid());
761 2
            }
762
763 2
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
764
765
            /** @var $user UserInterface */
766 2
            foreach ($this->firewallLogins as $firewallName => $user) {
767 2
                $token = $this->createUserToken($user, $firewallName);
768
769
                // BC: security.token_storage is available on Symfony 2.6+
770
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
771 2
                if ($client->getContainer()->has('security.token_storage')) {
772 2
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
773 2
                } else {
774
                    // This block will never be reached with Symfony 2.6+
775
                    // @codeCoverageIgnoreStart
776
                    $tokenStorage = $client->getContainer()->get('security.context');
777
                    // @codeCoverageIgnoreEnd
778
                }
779
780 2
                $tokenStorage->setToken($token);
781 2
                $session->set('_security_'.$firewallName, serialize($token));
782 2
            }
783
784 2
            $session->save();
785 2
        }
786
787 54
        return $client;
788
    }
789
790
    /**
791
     * Create User Token.
792
     *
793
     * Factory method for creating a User Token object for the firewall based on
794
     * the user object provided. By default it will be a Username/Password
795
     * Token based on the user's credentials, but may be overridden for custom
796
     * tokens in your applications.
797
     *
798
     * @param UserInterface $user         The user object to base the token off of
799
     * @param string        $firewallName name of the firewall provider to use
800
     *
801
     * @return TokenInterface The token to be used in the security context
802
     */
803 2
    protected function createUserToken(UserInterface $user, $firewallName)
804
    {
805 2
        return new UsernamePasswordToken(
806 2
            $user,
807 2
            null,
808 2
            $firewallName,
809 2
            $user->getRoles()
810 2
        );
811
    }
812
813
    /**
814
     * Extracts the location from the given route.
815
     *
816
     * @param string $route    The name of the route
817
     * @param array  $params   Set of parameters
818
     * @param int    $absolute
819
     *
820
     * @return string
821
     */
822 1
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
823
    {
824 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
825
    }
826
827
    /**
828
     * Checks the success state of a response.
829
     *
830
     * @param Response $response Response object
831
     * @param bool     $success  to define whether the response is expected to be successful
832
     * @param string   $type
833
     */
834 6
    public function isSuccessful(Response $response, $success = true, $type = 'text/html')
835
    {
836 6
        HttpAssertions::isSuccessful($response, $success, $type);
837 5
    }
838
839
    /**
840
     * Executes a request on the given url and returns the response contents.
841
     *
842
     * This method also asserts the request was successful.
843
     *
844
     * @param string $path           path of the requested page
845
     * @param string $method         The HTTP method to use, defaults to GET
846
     * @param bool   $authentication Whether to use authentication, defaults to false
847
     * @param bool   $success        to define whether the response is expected to be successful
848
     *
849
     * @return string
850
     */
851 1
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
852
    {
853 1
        $client = $this->makeClient($authentication);
854 1
        $client->request($method, $path);
855
856 1
        $content = $client->getResponse()->getContent();
857 1
        if (is_bool($success)) {
858 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...
859 1
        }
860
861 1
        return $content;
862
    }
863
864
    /**
865
     * Executes a request on the given url and returns a Crawler object.
866
     *
867
     * This method also asserts the request was successful.
868
     *
869
     * @param string $path           path of the requested page
870
     * @param string $method         The HTTP method to use, defaults to GET
871
     * @param bool   $authentication Whether to use authentication, defaults to false
872
     * @param bool   $success        Whether the response is expected to be successful
873
     *
874
     * @return Crawler
875
     */
876 1
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
877
    {
878 1
        $client = $this->makeClient($authentication);
879 1
        $crawler = $client->request($method, $path);
880
881 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...
882
883 1
        return $crawler;
884
    }
885
886
    /**
887
     * @param UserInterface $user
888
     * @param string        $firewallName
889
     *
890
     * @return WebTestCase
891
     */
892 2
    public function loginAs(UserInterface $user, $firewallName)
893
    {
894 2
        $this->firewallLogins[$firewallName] = $user;
895
896 2
        return $this;
897
    }
898
899
    /**
900
     * Asserts that the HTTP response code of the last request performed by
901
     * $client matches the expected code. If not, raises an error with more
902
     * information.
903
     *
904
     * @param $expectedStatusCode
905
     * @param Client $client
906
     */
907 12
    public function assertStatusCode($expectedStatusCode, Client $client)
908
    {
909 12
        HttpAssertions::assertStatusCode($expectedStatusCode, $client);
910 9
    }
911
912
    /**
913
     * Assert that the last validation errors within $container match the
914
     * expected keys.
915
     *
916
     * @param array              $expected  A flat array of field names
917
     * @param ContainerInterface $container
918
     */
919 3
    public function assertValidationErrors(array $expected, ContainerInterface $container)
920
    {
921 3
        HttpAssertions::assertValidationErrors($expected, $container);
922 1
    }
923
924
    /**
925
     * @param array $excludedDoctrineTables
926
     */
927 1
    public function setExcludedDoctrineTables($excludedDoctrineTables)
928
    {
929 1
        $this->excludedDoctrineTables = $excludedDoctrineTables;
930 1
    }
931
}
932