Completed
Push — refactor-loadFixtureFiles ( 91ff66 )
by Alexis
09:08
created

WebTestCase::cleanDatabase()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 17
ccs 12
cts 12
cp 1
rs 9.2
cc 4
eloc 9
nc 8
nop 2
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\Bundle\DoctrineBundle\Registry;
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\ORM\EntityManager;
39
use Doctrine\DBAL\Platforms\MySqlPlatform;
40
use Doctrine\ORM\Tools\SchemaTool;
41
use Nelmio\Alice\Fixtures;
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 static $cachedMetadatas = array();
69
70 1
    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...
71
    {
72 1
        $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : self::getPhpUnitXmlDir();
73
74 1
        list($appname) = explode('\\', get_called_class());
75
76 1
        $class = $appname.'Kernel';
77 1
        $file = $dir.'/'.strtolower($appname).'/'.$class.'.php';
78 1
        if (!file_exists($file)) {
79 1
            return parent::getKernelClass();
80
        }
81
        require_once $file;
82
83
        return $class;
84
    }
85
86
    /**
87
     * Creates a mock object of a service identified by its id.
88
     *
89
     * @param string $id
90
     *
91
     * @return PHPUnit_Framework_MockObject_MockBuilder
92
     */
93 2
    protected function getServiceMockBuilder($id)
94
    {
95 2
        $service = $this->getContainer()->get($id);
96
        $class = get_class($service);
97
98
        return $this->getMockBuilder($class)->disableOriginalConstructor();
99
    }
100
101
    /**
102
     * Builds up the environment to run the given command.
103
     *
104
     * @param string $name
105
     * @param array  $params
106
     * @param bool   $reuseKernel
107
     *
108
     * @return string
109
     */
110 12
    protected function runCommand($name, array $params = array(), $reuseKernel = false)
111
    {
112 12
        array_unshift($params, $name);
113
114 12
        if (!$reuseKernel) {
115 12
            if (null !== static::$kernel) {
116 9
                static::$kernel->shutdown();
117 9
            }
118
119 12
            $kernel = static::$kernel = $this->createKernel(array('environment' => $this->environment));
120 12
            $kernel->boot();
121 12
        } else {
122 2
            $kernel = $this->getContainer()->get('kernel');
123
        }
124
125 12
        $application = new Application($kernel);
126 12
        $application->setAutoExit(false);
127
128
        // @codeCoverageIgnoreStart
129
        if ('20301' === Kernel::VERSION_ID) {
130
            $params = $this->configureVerbosityForSymfony20301($params);
131
        }
132
        // @codeCoverageIgnoreEnd
133
134 12
        $input = new ArrayInput($params);
135 12
        $input->setInteractive(false);
136
137 12
        $fp = fopen('php://temp/maxmemory:'.$this->maxMemory, 'r+');
138 12
        $output = new StreamOutput($fp, $this->getVerbosityLevel(), $this->getDecorated());
139
140 11
        $application->run($input, $output);
141
142 11
        rewind($fp);
143
144 11
        return stream_get_contents($fp);
145
    }
146
147
    /**
148
     * Retrieves the output verbosity level.
149
     *
150
     * @see Symfony\Component\Console\Output\OutputInterface for available levels
151
     *
152
     * @return int
153
     *
154
     * @throws \OutOfBoundsException If the set value isn't accepted
155
     */
156 12
    protected function getVerbosityLevel()
157
    {
158
        // If `null`, is not yet set
159 12
        if (null === $this->verbosityLevel) {
160
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
161 6
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
162 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
163
164 6
            $this->verbosityLevel = constant($verbosity);
165 6
        }
166
167
        // If string, it is set by the developer, so check that the value is an accepted one
168 12
        if (is_string($this->verbosityLevel)) {
169 6
            $level = strtoupper($this->verbosityLevel);
170 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
171
172 6
            if (!defined($verbosity)) {
173 1
                throw new \OutOfBoundsException(
174 1
                    sprintf('The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".', $level)
175 1
                    );
176
            }
177
178 5
            $this->verbosityLevel = constant($verbosity);
179 5
        }
180
181 11
        return $this->verbosityLevel;
182
    }
183
184
    /**
185
     * In Symfony 2.3.1 the verbosity level has to be set through {Symfony\Component\Console\Input\ArrayInput} and not
186
     * in {Symfony\Component\Console\Output\OutputInterface}.
187
     *
188
     * This method builds $params to be passed to {Symfony\Component\Console\Input\ArrayInput}.
189
     *
190
     * @codeCoverageIgnore
191
     *
192
     * @param array $params
193
     *
194
     * @return array
195
     */
196
    private function configureVerbosityForSymfony20301(array $params)
197
    {
198
        switch ($this->getVerbosityLevel()) {
199
            case OutputInterface::VERBOSITY_QUIET:
200
                $params['-q'] = '-q';
201
                break;
202
203
            case OutputInterface::VERBOSITY_VERBOSE:
204
                $params['-v'] = '';
205
                break;
206
207
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
208
                $params['-vv'] = '';
209
                break;
210
211
            case OutputInterface::VERBOSITY_DEBUG:
212
                $params['-vvv'] = '';
213
                break;
214
        }
215
216
        return $params;
217
    }
218
219 6
    public function setVerbosityLevel($level)
220
    {
221 6
        $this->verbosityLevel = $level;
222 6
    }
223
224
    /**
225
     * Retrieves the flag indicating if the output should be decorated or not.
226
     *
227
     * @return bool
228
     */
229 11
    protected function getDecorated()
230
    {
231 11
        if (null === $this->decorated) {
232
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
233 5
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
234 5
        }
235
236
        // Check the local decorated flag
237 11
        if (false === is_bool($this->decorated)) {
238
            throw new \OutOfBoundsException(
239
                sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated))
240
            );
241
        }
242
243 11
        return $this->decorated;
244
    }
245
246 6
    public function isDecorated($decorated)
247
    {
248 6
        $this->decorated = $decorated;
249 6
    }
250
251
    /**
252
     * Get an instance of the dependency injection container.
253
     * (this creates a kernel *without* parameters).
254
     *
255
     * @return ContainerInterface
256
     */
257 34
    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...
258
    {
259 34
        if (!empty($this->kernelDir)) {
260
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
261
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
262
        }
263
264 34
        $cacheKey = $this->kernelDir.'|'.$this->environment;
265 34
        if (empty($this->containers[$cacheKey])) {
266
            $options = array(
267 34
                'environment' => $this->environment,
268 34
            );
269 34
            $kernel = $this->createKernel($options);
270 34
            $kernel->boot();
271
272 34
            $this->containers[$cacheKey] = $kernel->getContainer();
273 34
        }
274
275 34
        if (isset($tmpKernelDir)) {
276
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
277
        }
278
279 34
        return $this->containers[$cacheKey];
280
    }
281
282
    /**
283
     * This function finds the time when the data blocks of a class definition
284
     * file were being written to, that is, the time when the content of the
285
     * file was changed.
286
     *
287
     * @param string $class The fully qualified class name of the fixture class to
288
     *                      check modification date on.
289
     *
290
     * @return \DateTime|null
291
     */
292 2
    protected function getFixtureLastModified($class)
293
    {
294 2
        $lastModifiedDateTime = null;
295
296 2
        $reflClass = new \ReflectionClass($class);
297 2
        $classFileName = $reflClass->getFileName();
298
299 2
        if (file_exists($classFileName)) {
300 2
            $lastModifiedDateTime = new \DateTime();
301 2
            $lastModifiedDateTime->setTimestamp(filemtime($classFileName));
302 2
        }
303
304 2
        return $lastModifiedDateTime;
305
    }
306
307
    /**
308
     * Determine if the Fixtures that define a database backup have been
309
     * modified since the backup was made.
310
     *
311
     * @param array  $classNames The fixture classnames to check
312
     * @param string $backup     The fixture backup SQLite database file path
313
     *
314
     * @return bool TRUE if the backup was made since the modifications to the
315
     *              fixtures; FALSE otherwise
316
     */
317 3
    protected function isBackupUpToDate(array $classNames, $backup)
318
    {
319 3
        $backupLastModifiedDateTime = new \DateTime();
320 3
        $backupLastModifiedDateTime->setTimestamp(filemtime($backup));
321
322 3
        foreach ($classNames as &$className) {
323 2
            $fixtureLastModifiedDateTime = $this->getFixtureLastModified($className);
324 2
            if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) {
325
                return false;
326
            }
327 3
        }
328
329 3
        return true;
330
    }
331
332
    /**
333
     * Set the database to the provided fixtures.
334
     *
335
     * Drops the current database and then loads fixtures using the specified
336
     * classes. The parameter is a list of fully qualified class names of
337
     * classes that implement Doctrine\Common\DataFixtures\FixtureInterface
338
     * so that they can be loaded by the DataFixtures Loader::addFixture
339
     *
340
     * When using SQLite this method will automatically make a copy of the
341
     * loaded schema and fixtures which will be restored automatically in
342
     * case the same fixture classes are to be loaded again. Caveat: changes
343
     * to references and/or identities may go undetected.
344
     *
345
     * Depends on the doctrine data-fixtures library being available in the
346
     * class path.
347
     *
348
     * @param array  $classNames   List of fully qualified class names of fixtures to load
349
     * @param string $omName       The name of object manager to use
350
     * @param string $registryName The service id of manager registry to use
351
     * @param int    $purgeMode    Sets the ORM purge mode
352
     *
353
     * @return null|AbstractExecutor
354
     */
355 26
    protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
356
    {
357 26
        $container = $this->getContainer();
358
        /** @var ManagerRegistry $registry */
359 26
        $registry = $container->get($registryName);
360 26
        $om = $registry->getManager($omName);
361 26
        $type = $registry->getName();
362
363 26
        $executorClass = 'PHPCR' === $type && class_exists('Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor')
364 26
            ? 'Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor'
365 26
            : 'Doctrine\\Common\\DataFixtures\\Executor\\'.$type.'Executor';
366 26
        $referenceRepository = new ProxyReferenceRepository($om);
367 26
        $cacheDriver = $om->getMetadataFactory()->getCacheDriver();
368
369 26
        if ($cacheDriver) {
370 26
            $cacheDriver->deleteAll();
371 26
        }
372
373 26
        if ('ORM' === $type) {
374 25
            $connection = $om->getConnection();
375 25
            if ($connection->getDriver() instanceof SqliteDriver) {
376 21
                $params = $connection->getParams();
377 21
                if (isset($params['master'])) {
378
                    $params = $params['master'];
379
                }
380
381 21
                $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
382 21
                if (!$name) {
383
                    throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
384
                }
385
386 21
                if (!isset(self::$cachedMetadatas[$omName])) {
387 6
                    self::$cachedMetadatas[$omName] = $om->getMetadataFactory()->getAllMetadata();
388
                    usort(self::$cachedMetadatas[$omName], function ($a, $b) { return strcmp($a->name, $b->name); });
389 6
                }
390 21
                $metadatas = self::$cachedMetadatas[$omName];
391
392 21
                if ($container->getParameter('liip_functional_test.cache_sqlite_db')) {
393 5
                    $backup = $container->getParameter('kernel.cache_dir').'/test_'.md5(serialize($metadatas).serialize($classNames)).'.db';
394 5
                    if (file_exists($backup) && file_exists($backup.'.ser') && $this->isBackupUpToDate($classNames, $backup)) {
395 3
                        $om->flush();
396 3
                        $om->clear();
397
398 3
                        $this->preFixtureRestore($om, $referenceRepository);
399
400 3
                        copy($backup, $name);
401
402 3
                        $executor = new $executorClass($om);
403 3
                        $executor->setReferenceRepository($referenceRepository);
404 3
                        $executor->getReferenceRepository()->load($backup);
405
406 3
                        $this->postFixtureRestore();
407
408 3
                        return $executor;
409
                    }
410 2
                }
411
412
                // TODO: handle case when using persistent connections. Fail loudly?
413 18
                $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...
414 18
                $schemaTool->dropDatabase($name);
0 ignored issues
show
Unused Code introduced by
The call to SchemaTool::dropDatabase() has too many arguments starting with $name.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

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