Test Setup Failed
Pull Request — master (#49)
by Alexis
22:35
created

WebTestCase::postFixtureBackupRestore()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 0
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
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\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
    protected function getServiceMockBuilder($id)
99
    {
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 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...
263
    {
264 49
        if (!empty($this->kernelDir)) {
265
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
266
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
267
        }
268
269 49
        $cacheKey = $this->kernelDir.'|'.$this->environment;
270 49
        if (empty($this->containers[$cacheKey])) {
271
            $options = array(
272 48
                'environment' => $this->environment,
273 48
            );
274 48
            $kernel = $this->createKernel($options);
275 48
            $kernel->boot();
276
277 48
            $this->containers[$cacheKey] = $kernel->getContainer();
278 48
        }
279
280 49
        if (isset($tmpKernelDir)) {
281
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
282
        }
283
284 49
        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
                            $connection->close();
410 7
                        }
411
412 7
                        $om->flush();
413
                        $om->clear();
414 7
415 7
                        $this->preFixtureBackupRestore($om, $referenceRepository, $backup);
416 7
417
                        copy($backup, $name);
418 7
419
                        $executor = new $executorClass($om);
420 7
                        $executor->setReferenceRepository($referenceRepository);
421
                        $executor->getReferenceRepository()->load($backup);
422 3
423
                        $this->postFixtureBackupRestore($backup);
424
425 24
                        return $executor;
426 24
                    }
427 24
                }
428 24
429 24
                // 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
                $schemaTool->dropDatabase();
432 24
                if (!empty($metadatas)) {
433 24
                    $schemaTool->createSchema($metadatas);
434 24
                }
435 29
                $this->postFixtureSetup();
436
437 30
                $executor = new $executorClass($om);
438 6
                $executor->setReferenceRepository($referenceRepository);
439 6
            }
440 1
        }
441 1
442 1
        if (empty($executor)) {
443 1
            $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
444
            if ('PHPCR' === $type) {
445 1
                $purger = new $purgerClass($om);
446 1
                $initManager = $container->has('doctrine_phpcr.initializer_manager')
447 5
                    ? $container->get('doctrine_phpcr.initializer_manager')
448 5
                    : null;
449 5
450
                $executor = new $executorClass($om, $purger, $initManager);
451
            } else {
452
                if ('ORM' === $type) {
453 5
                    $purger = new $purgerClass(null, $this->excludedDoctrineTables);
454 2
                } else {
455 2
                    $purger = new $purgerClass();
456
                }
457 5
458
                if (null !== $purgeMode) {
459
                    $purger->setPurgeMode($purgeMode);
460 6
                }
461 6
462 6
                $executor = new $executorClass($om, $purger);
463
            }
464 30
465
            $executor->setReferenceRepository($referenceRepository);
466 30
            $executor->purge();
467
        }
468 30
469 3
        $loader = $this->getFixtureLoader($container, $classNames);
470
471 3
        $executor->execute($loader->getFixtures(), true);
472 3
473
        if (isset($name) && isset($backup)) {
474 3
            $this->preReferenceSave($om, $executor, $backup);
475 3
476
            $executor->getReferenceRepository()->save($backup);
477 30
            copy($name, $backup);
478
479
            $this->postReferenceSave($om, $executor, $backup);
480
        }
481
482
        return $executor;
483
    }
484
485
    /**
486
     * Clean database.
487 9
     *
488
     * @param ManagerRegistry $registry
489 9
     * @param EntityManager   $om
490
     * @param null            $omName
491 9
     */
492 9
    private function cleanDatabase(ManagerRegistry $registry, EntityManager $om, $omName = null)
493
    {
494 9
        $connection = $om->getConnection();
495 1
496 1
        $mysql = ($registry->getName() === 'ORM'
497
            && $connection->getDatabasePlatform() instanceof MySqlPlatform);
498 9
499
        if ($mysql) {
500 9
            $connection->query('SET FOREIGN_KEY_CHECKS=0');
501 1
        }
502 1
503 9
        $this->loadFixtures(array(), $omName);
504
505
        if ($mysql) {
506
            $connection->query('SET FOREIGN_KEY_CHECKS=1');
507
        }
508
    }
509
510
    /**
511
     * Locate fixture files.
512
     *
513
     * @param array $paths
514 9
     *
515
     * @return array $files
516 9
     *
517
     * @throws \InvalidArgumentException if a wrong path is given outside a bundle
518 9
     */
519
    private function locateResources($paths)
520 9
    {
521 9
        $files = array();
522 3
523 1
        $kernel = $this->getContainer()->get('kernel');
524
525 2
        foreach ($paths as $path) {
526 2
            if ($path[0] !== '@') {
527
                if (!file_exists($path)) {
528
                    throw new \InvalidArgumentException(sprintf('Unable to find file "%s".', $path));
529 6
                }
530 7
                $files[] = $path;
531
                continue;
532 7
            }
533
534
            $files[] = $kernel->locateResource($path);
535
        }
536
537
        return $files;
538
    }
539
540
    /**
541
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
542
     * @param bool   $append
543
     * @param null   $omName
544
     * @param string $registryName
545 9
     *
546
     * @return array
547 9
     *
548
     * @throws \BadMethodCallException
549
     */
550
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine')
551
    {
552
        if (!class_exists('Nelmio\Alice\Fixtures')) {
553
            // This class is available during tests, no exception will be thrown.
554
            // @codeCoverageIgnoreStart
555 9
            throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
556
            // @codeCoverageIgnoreEnd
557
        }
558 9
559
        /** @var ContainerInterface $container */
560
        $container = $this->getContainer();
561 9
562
        /** @var ManagerRegistry $registry */
563 9
        $registry = $container->get($registryName);
564 9
565 9
        /** @var EntityManager $om */
566
        $om = $registry->getManager($omName);
567 9
568
        if ($append === false) {
569
            $this->cleanDatabase($registry, $om, $omName);
570 7
        }
571 7
572 3
        $files = $this->locateResources($paths);
573 3
574 3
        // Check if the Hautelook AliceBundle is registered and if yes, use it instead of Nelmio Alice
575 3
        $hautelookLoaderServiceName = 'hautelook_alice.fixtures.loader';
576
        if ($container->has($hautelookLoaderServiceName)) {
577 3
            $loaderService = $container->get($hautelookLoaderServiceName);
578
            $persisterClass = class_exists('Nelmio\Alice\ORM\Doctrine') ?
579
                'Nelmio\Alice\ORM\Doctrine' :
580 4
                'Nelmio\Alice\Persister\Doctrine';
581
582
            return $loaderService->load(new $persisterClass($om), $files);
583
        }
584
585
        return Fixtures::load($files, $om);
586
    }
587 24
588
    /**
589 24
     * Callback function to be executed after Schema creation.
590
     * Use this to execute acl:init or other things necessary.
591
     */
592
    protected function postFixtureSetup()
593
    {
594
    }
595
596 7
    /**
597
     * Callback function to be executed after Schema restore.
598 7
     *
599
     * @return WebTestCase
600
     *
601
     * @deprecated since version 1.8, to be removed in 2.0. Use postFixtureBackupRestore method instead.
602
     */
603
    protected function postFixtureRestore()
604
    {
605
    }
606
607
    /**
608 7
     * Callback function to be executed before Schema restore.
609
     *
610 7
     * @param ObjectManager            $manager             The object manager
611
     * @param ProxyReferenceRepository $referenceRepository The reference repository
612
     *
613
     * @return WebTestCase
614
     *
615
     * @deprecated since version 1.8, to be removed in 2.0. Use preFixtureBackupRestore method instead.
616
     */
617
    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...
618
    {
619
    }
620
621 3
    /**
622
     * Callback function to be executed after Schema restore.
623 3
     *
624
     * @param string $backupFilePath Path of file used to backup the references of the data fixtures
625
     *
626
     * @return WebTestCase
627
     */
628
    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...
629
    {
630
        $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...
631
632
        return $this;
633
    }
634 3
635
    /**
636 3
     * Callback function to be executed before Schema restore.
637
     *
638
     * @param ObjectManager            $manager             The object manager
639
     * @param ProxyReferenceRepository $referenceRepository The reference repository
640
     * @param string                   $backupFilePath      Path of file used to backup the references of the data fixtures
641
     *
642
     * @return WebTestCase
643
     */
644
    protected function preFixtureBackupRestore(
645
        ObjectManager $manager,
646 36
        ProxyReferenceRepository $referenceRepository,
647
        $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...
648 36
    ) {
649 36
        $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...
650 36
651
        return $this;
652
    }
653
654
    /**
655 36
     * Callback function to be executed after save of references.
656
     *
657 36
     * @param ObjectManager    $manager        The object manager
658
     * @param AbstractExecutor $executor       Executor of the data fixtures
659 36
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
660 11
     *
661 36
     * @return WebTestCase
662
     */
663 36
    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...
664
    {
665
    }
666
667
    /**
668
     * Callback function to be executed before save of references.
669
     *
670
     * @param ObjectManager    $manager        The object manager
671
     * @param AbstractExecutor $executor       Executor of the data fixtures
672 11
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
673
     *
674 11
     * @return WebTestCase
675
     */
676 11
    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...
677 2
    {
678
    }
679 2
680
    /**
681
     * Retrieve Doctrine DataFixtures loader.
682 11
     *
683
     * @param ContainerInterface $container
684 11
     * @param array              $classNames
685 2
     *
686 2
     * @return Loader
687 2
     */
688 2
    protected function getFixtureLoader(ContainerInterface $container, array $classNames)
689 11
    {
690
        $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
691
            ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
692
            : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
693
                // This class is not available during tests.
694
                // @codeCoverageIgnoreStart
695
                ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
696
                // @codeCoverageIgnoreEnd
697
                : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
698
699
        $loader = new $loaderClass($container);
700
701
        foreach ($classNames as $className) {
702
            $this->loadFixtureClass($loader, $className);
703
        }
704
705
        return $loader;
706 54
    }
707
708 54
    /**
709 2
     * Load a data fixture class.
710
     *
711 1
     * @param Loader $loader
712 1
     * @param string $className
713 1
     */
714 1
    protected function loadFixtureClass($loader, $className)
715 1
    {
716 1
        $fixture = new $className();
717
718 2
        if ($loader->hasFixture($fixture)) {
719 2
            unset($fixture);
720 2
721 2
            return;
722 2
        }
723
724 54
        $loader->addFixture($fixture);
725
726 54
        if ($fixture instanceof DependentFixtureInterface) {
727
            foreach ($fixture->getDependencies() as $dependency) {
728 2
                $this->loadFixtureClass($loader, $dependency);
729
            }
730 2
        }
731
    }
732
733
    /**
734 2
     * Creates an instance of a lightweight Http client.
735
     *
736 2
     * If $authentication is set to 'true' it will use the content of
737 2
     * 'liip_functional_test.authentication' to log in.
738 2
     *
739
     * $params can be used to pass headers to the client, note that they have
740 2
     * to follow the naming format used in $_SERVER.
741
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
742
     *
743 2
     * @param bool|array $authentication
744 2
     * @param array      $params
745
     *
746
     * @return Client
747
     */
748 2
    protected function makeClient($authentication = false, array $params = array())
749 2
    {
750 2
        if ($authentication) {
751
            if ($authentication === true) {
752
                $authentication = array(
753
                    'username' => $this->getContainer()
754
                        ->getParameter('liip_functional_test.authentication.username'),
755
                    'password' => $this->getContainer()
756
                        ->getParameter('liip_functional_test.authentication.password'),
757 2
                );
758 2
            }
759 2
760
            $params = array_merge($params, array(
761 2
                'PHP_AUTH_USER' => $authentication['username'],
762 2
                'PHP_AUTH_PW' => $authentication['password'],
763
            ));
764 54
        }
765
766
        $client = static::createClient(array('environment' => $this->environment), $params);
767
768
        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...
769
            // has to be set otherwise "hasPreviousSession" in Request returns false.
770
            $options = $client->getContainer()->getParameter('session.storage.options');
771
772
            if (!$options || !isset($options['name'])) {
773
                throw new \InvalidArgumentException('Missing session.storage.options#name');
774
            }
775
776
            $session = $client->getContainer()->get('session');
777
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
778
            if ($session instanceof Session) {
779
                $session->setId(uniqid());
780 2
            }
781
782 2
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
783 2
784 2
            /** @var $user UserInterface */
785 2
            foreach ($this->firewallLogins as $firewallName => $user) {
786 2
                $token = $this->createUserToken($user, $firewallName);
787 2
788
                // BC: security.token_storage is available on Symfony 2.6+
789
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
790
                if ($client->getContainer()->has('security.token_storage')) {
791
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
792
                } else {
793
                    // This block will never be reached with Symfony 2.6+
794
                    // @codeCoverageIgnoreStart
795
                    $tokenStorage = $client->getContainer()->get('security.context');
796
                    // @codeCoverageIgnoreEnd
797
                }
798
799 1
                $tokenStorage->setToken($token);
800
                $session->set('_security_'.$firewallName, serialize($token));
801 1
            }
802
803
            $session->save();
804
        }
805
806
        return $client;
807
    }
808
809
    /**
810
     * Create User Token.
811 6
     *
812
     * Factory method for creating a User Token object for the firewall based on
813 6
     * the user object provided. By default it will be a Username/Password
814 5
     * Token based on the user's credentials, but may be overridden for custom
815
     * tokens in your applications.
816
     *
817
     * @param UserInterface $user         The user object to base the token off of
818
     * @param string        $firewallName name of the firewall provider to use
819
     *
820
     * @return TokenInterface The token to be used in the security context
821
     */
822
    protected function createUserToken(UserInterface $user, $firewallName)
823
    {
824
        return new UsernamePasswordToken(
825
            $user,
826
            null,
827
            $firewallName,
828 1
            $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...
829
        );
830 1
    }
831 1
832
    /**
833 1
     * Extracts the location from the given route.
834 1
     *
835 1
     * @param string $route    The name of the route
836 1
     * @param array  $params   Set of parameters
837
     * @param int    $absolute
838 1
     *
839
     * @return string
840
     */
841
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
842
    {
843
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
844
    }
845
846
    /**
847
     * Checks the success state of a response.
848
     *
849
     * @param Response $response Response object
850
     * @param bool     $success  to define whether the response is expected to be successful
851
     * @param string   $type
852
     */
853 1
    public function isSuccessful(Response $response, $success = true, $type = 'text/html')
854
    {
855 1
        HttpAssertions::isSuccessful($response, $success, $type);
856 1
    }
857
858 1
    /**
859
     * Executes a request on the given url and returns the response contents.
860 1
     *
861
     * This method also asserts the request was successful.
862
     *
863
     * @param string $path           path of the requested page
864
     * @param string $method         The HTTP method to use, defaults to GET
865
     * @param bool   $authentication Whether to use authentication, defaults to false
866
     * @param bool   $success        to define whether the response is expected to be successful
867
     *
868
     * @return string
869 2
     */
870
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
871 2
    {
872
        $client = $this->makeClient($authentication);
873 2
        $client->request($method, $path);
874
875
        $content = $client->getResponse()->getContent();
876
        if (is_bool($success)) {
877
            $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...
878
        }
879
880
        return $content;
881
    }
882
883
    /**
884 12
     * Executes a request on the given url and returns a Crawler object.
885
     *
886 12
     * This method also asserts the request was successful.
887 9
     *
888
     * @param string $path           path of the requested page
889
     * @param string $method         The HTTP method to use, defaults to GET
890
     * @param bool   $authentication Whether to use authentication, defaults to false
891
     * @param bool   $success        Whether the response is expected to be successful
892
     *
893
     * @return Crawler
894
     */
895
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
896 3
    {
897
        $client = $this->makeClient($authentication);
898 3
        $crawler = $client->request($method, $path);
899 1
900
        $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...
901
902
        return $crawler;
903
    }
904 1
905
    /**
906 1
     * @param UserInterface $user
907 1
     * @param string        $firewallName
908
     *
909
     * @return WebTestCase
910
     */
911
    public function loginAs(UserInterface $user, $firewallName)
912
    {
913
        $this->firewallLogins[$firewallName] = $user;
914
915
        return $this;
916
    }
917
918
    /**
919
     * Asserts that the HTTP response code of the last request performed by
920
     * $client matches the expected code. If not, raises an error with more
921
     * information.
922
     *
923
     * @param $expectedStatusCode
924
     * @param Client $client
925
     */
926
    public function assertStatusCode($expectedStatusCode, Client $client)
927
    {
928
        HttpAssertions::assertStatusCode($expectedStatusCode, $client);
929
    }
930
931
    /**
932
     * Assert that the last validation errors within $container match the
933
     * expected keys.
934
     *
935
     * @param array              $expected  A flat array of field names
936
     * @param ContainerInterface $container
937
     */
938
    public function assertValidationErrors(array $expected, ContainerInterface $container)
939
    {
940
        HttpAssertions::assertValidationErrors($expected, $container);
941
    }
942
943
    /**
944
     * @param array $excludedDoctrineTables
945
     */
946
    public function setExcludedDoctrineTables($excludedDoctrineTables)
947
    {
948
        $this->excludedDoctrineTables = $excludedDoctrineTables;
949
    }
950
}
951