Test Failed
Push — test-faker-provider ( bb2cd7 )
by Alexis
08:53
created

WebTestCase::locateResources()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 17
rs 9.2
cc 4
eloc 9
nc 3
nop 1
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
42
/**
43
 * @author Lea Haensenberger
44
 * @author Lukas Kahwe Smith <[email protected]>
45
 * @author Benjamin Eberlei <[email protected]>
46
 */
47
abstract class WebTestCase extends BaseWebTestCase
48
{
49
    protected $environment = 'test';
50
    protected $containers;
51
    protected $kernelDir;
52
    // 5 * 1024 * 1024 KB
53
    protected $maxMemory = 5242880;
54
55
    // RUN COMMAND
56
    protected $verbosityLevel;
57
    protected $decorated;
58
59
    /**
60
     * @var array
61
     */
62
    private $firewallLogins = array();
63
64
    /**
65
     * @var array
66
     */
67
    private static $cachedMetadatas = array();
68
69
    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...
70
    {
71
        $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : self::getPhpUnitXmlDir();
72
73
        list($appname) = explode('\\', get_called_class());
74
75
        $class = $appname.'Kernel';
76
        $file = $dir.'/'.strtolower($appname).'/'.$class.'.php';
77
        if (!file_exists($file)) {
78
            return parent::getKernelClass();
79
        }
80
        require_once $file;
81
82
        return $class;
83
    }
84
85
    /**
86
     * Creates a mock object of a service identified by its id.
87
     *
88
     * @param string $id
89
     *
90
     * @return \PHPUnit_Framework_MockObject_MockBuilder
91
     */
92
    protected function getServiceMockBuilder($id)
93
    {
94
        $service = $this->getContainer()->get($id);
95
        $class = get_class($service);
96
97
        return $this->getMockBuilder($class)->disableOriginalConstructor();
98
    }
99
100
    /**
101
     * Builds up the environment to run the given command.
102
     *
103
     * @param string $name
104
     * @param array  $params
105
     * @param bool   $reuseKernel
106
     *
107
     * @return string
108
     */
109
    protected function runCommand($name, array $params = array(), $reuseKernel = false)
110
    {
111
        array_unshift($params, $name);
112
113
        if (!$reuseKernel) {
114
            if (null !== static::$kernel) {
115
                static::$kernel->shutdown();
116
            }
117
118
            $kernel = static::$kernel = $this->createKernel(array('environment' => $this->environment));
119
            $kernel->boot();
120
        } else {
121
            $kernel = $this->getContainer()->get('kernel');
122
        }
123
124
        $application = new Application($kernel);
125
        $application->setAutoExit(false);
126
127
        // @codeCoverageIgnoreStart
128
        if ('20301' === Kernel::VERSION_ID) {
129
            $params = $this->configureVerbosityForSymfony20301($params);
130
        }
131
        // @codeCoverageIgnoreEnd
132
133
        $input = new ArrayInput($params);
134
        $input->setInteractive(false);
135
136
        $fp = fopen('php://temp/maxmemory:'.$this->maxMemory, 'r+');
137
        $output = new StreamOutput($fp, $this->getVerbosityLevel(), $this->getDecorated());
138
139
        $application->run($input, $output);
140
141
        rewind($fp);
142
143
        return stream_get_contents($fp);
144
    }
145
146
    /**
147
     * Retrieves the output verbosity level.
148
     *
149
     * @see Symfony\Component\Console\Output\OutputInterface for available levels
150
     *
151
     * @return int
152
     *
153
     * @throws \OutOfBoundsException If the set value isn't accepted
154
     */
155
    protected function getVerbosityLevel()
156
    {
157
        // If `null`, is not yet set
158
        if (null === $this->verbosityLevel) {
159
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
160
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
161
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
162
163
            $this->verbosityLevel = constant($verbosity);
164
        }
165
166
        // If string, it is set by the developer, so check that the value is an accepted one
167
        if (is_string($this->verbosityLevel)) {
168
            $level = strtoupper($this->verbosityLevel);
169
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
170
171
            if (!defined($verbosity)) {
172
                throw new \OutOfBoundsException(
173
                    sprintf('The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".', $level)
174
                    );
175
            }
176
177
            $this->verbosityLevel = constant($verbosity);
178
        }
179
180
        return $this->verbosityLevel;
181
    }
182
183
    /**
184
     * In Symfony 2.3.1 the verbosity level has to be set through {Symfony\Component\Console\Input\ArrayInput} and not
185
     * in {Symfony\Component\Console\Output\OutputInterface}.
186
     *
187
     * This method builds $params to be passed to {Symfony\Component\Console\Input\ArrayInput}.
188
     *
189
     * @codeCoverageIgnore
190
     *
191
     * @param array $params
192
     *
193
     * @return array
194
     */
195
    private function configureVerbosityForSymfony20301(array $params)
196
    {
197
        switch ($this->getVerbosityLevel()) {
198
            case OutputInterface::VERBOSITY_QUIET:
199
                $params['-q'] = '-q';
200
                break;
201
202
            case OutputInterface::VERBOSITY_VERBOSE:
203
                $params['-v'] = '';
204
                break;
205
206
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
207
                $params['-vv'] = '';
208
                break;
209
210
            case OutputInterface::VERBOSITY_DEBUG:
211
                $params['-vvv'] = '';
212
                break;
213
        }
214
215
        return $params;
216
    }
217
218
    public function setVerbosityLevel($level)
219
    {
220
        $this->verbosityLevel = $level;
221
    }
222
223
    /**
224
     * Retrieves the flag indicating if the output should be decorated or not.
225
     *
226
     * @return bool
227
     */
228
    protected function getDecorated()
229
    {
230
        if (null === $this->decorated) {
231
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
232
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
233
        }
234
235
        // Check the local decorated flag
236
        if (false === is_bool($this->decorated)) {
237
            throw new \OutOfBoundsException(
238
                sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated))
239
            );
240
        }
241
242
        return $this->decorated;
243
    }
244
245
    public function isDecorated($decorated)
246
    {
247
        $this->decorated = $decorated;
248
    }
249
250
    /**
251
     * Get an instance of the dependency injection container.
252
     * (this creates a kernel *without* parameters).
253
     *
254
     * @return ContainerInterface
255
     */
256
    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...
257
    {
258
        if (!empty($this->kernelDir)) {
259
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
260
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
261
        }
262
263
        $cacheKey = $this->kernelDir.'|'.$this->environment;
264
        if (empty($this->containers[$cacheKey])) {
265
            $options = array(
266
                'environment' => $this->environment,
267
            );
268
            $kernel = $this->createKernel($options);
269
            $kernel->boot();
270
271
            $this->containers[$cacheKey] = $kernel->getContainer();
272
        }
273
274
        if (isset($tmpKernelDir)) {
275
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
276
        }
277
278
        return $this->containers[$cacheKey];
279
    }
280
281
    /**
282
     * This function finds the time when the data blocks of a class definition
283
     * file were being written to, that is, the time when the content of the
284
     * file was changed.
285
     *
286
     * @param string $class The fully qualified class name of the fixture class to
287
     *                      check modification date on.
288
     *
289
     * @return \DateTime|null
290
     */
291
    protected function getFixtureLastModified($class)
292
    {
293
        $lastModifiedDateTime = null;
294
295
        $reflClass = new \ReflectionClass($class);
296
        $classFileName = $reflClass->getFileName();
297
298
        if (file_exists($classFileName)) {
299
            $lastModifiedDateTime = new \DateTime();
300
            $lastModifiedDateTime->setTimestamp(filemtime($classFileName));
301
        }
302
303
        return $lastModifiedDateTime;
304
    }
305
306
    /**
307
     * Determine if the Fixtures that define a database backup have been
308
     * modified since the backup was made.
309
     *
310
     * @param array  $classNames The fixture classnames to check
311
     * @param string $backup     The fixture backup SQLite database file path
312
     *
313
     * @return bool TRUE if the backup was made since the modifications to the
314
     *              fixtures; FALSE otherwise
315
     */
316
    protected function isBackupUpToDate(array $classNames, $backup)
317
    {
318
        $backupLastModifiedDateTime = new \DateTime();
319
        $backupLastModifiedDateTime->setTimestamp(filemtime($backup));
320
321
        foreach ($classNames as &$className) {
322
            $fixtureLastModifiedDateTime = $this->getFixtureLastModified($className);
323
            if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) {
324
                return false;
325
            }
326
        }
327
328
        return true;
329
    }
330
331
    /**
332
     * Set the database to the provided fixtures.
333
     *
334
     * Drops the current database and then loads fixtures using the specified
335
     * classes. The parameter is a list of fully qualified class names of
336
     * classes that implement Doctrine\Common\DataFixtures\FixtureInterface
337
     * so that they can be loaded by the DataFixtures Loader::addFixture
338
     *
339
     * When using SQLite this method will automatically make a copy of the
340
     * loaded schema and fixtures which will be restored automatically in
341
     * case the same fixture classes are to be loaded again. Caveat: changes
342
     * to references and/or identities may go undetected.
343
     *
344
     * Depends on the doctrine data-fixtures library being available in the
345
     * class path.
346
     *
347
     * @param array  $classNames   List of fully qualified class names of fixtures to load
348
     * @param string $omName       The name of object manager to use
349
     * @param string $registryName The service id of manager registry to use
350
     * @param int    $purgeMode    Sets the ORM purge mode
351
     *
352
     * @return null|AbstractExecutor
353
     */
354
    protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
355
    {
356
        $container = $this->getContainer();
357
        /** @var ManagerRegistry $registry */
358
        $registry = $container->get($registryName);
359
        /** @var ObjectManager $om */
360
        $om = $registry->getManager($omName);
361
        $type = $registry->getName();
362
363
        $executorClass = 'PHPCR' === $type && class_exists('Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor')
364
            ? 'Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor'
365
            : 'Doctrine\\Common\\DataFixtures\\Executor\\'.$type.'Executor';
366
        $referenceRepository = new ProxyReferenceRepository($om);
367
        $cacheDriver = $om->getMetadataFactory()->getCacheDriver();
368
369
        if ($cacheDriver) {
370
            $cacheDriver->deleteAll();
371
        }
372
373
        if ('ORM' === $type) {
374
            $connection = $om->getConnection();
375
            if ($connection->getDriver() instanceof SqliteDriver) {
376
                $params = $connection->getParams();
377
                if (isset($params['master'])) {
378
                    $params = $params['master'];
379
                }
380
381
                $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
382
                if (!$name) {
383
                    throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
384
                }
385
386
                if (!isset(self::$cachedMetadatas[$omName])) {
387
                    self::$cachedMetadatas[$omName] = $om->getMetadataFactory()->getAllMetadata();
388
                    usort(self::$cachedMetadatas[$omName], function ($a, $b) { return strcmp($a->name, $b->name); });
389
                }
390
                $metadatas = self::$cachedMetadatas[$omName];
391
392
                if ($container->getParameter('liip_functional_test.cache_sqlite_db')) {
393
                    $backup = $container->getParameter('kernel.cache_dir').'/test_'.md5(serialize($metadatas).serialize($classNames)).'.db';
394
                    if (file_exists($backup) && file_exists($backup.'.ser') && $this->isBackupUpToDate($classNames, $backup)) {
395
                        $om->flush();
396
                        $om->clear();
397
398
                        $this->preFixtureRestore($om, $referenceRepository);
399
400
                        copy($backup, $name);
401
402
                        $executor = new $executorClass($om);
403
                        $executor->setReferenceRepository($referenceRepository);
404
                        $executor->getReferenceRepository()->load($backup);
405
406
                        $this->postFixtureRestore();
407
408
                        return $executor;
409
                    }
410
                }
411
412
                // TODO: handle case when using persistent connections. Fail loudly?
413
                $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
                $schemaTool->dropDatabase();
415
                if (!empty($metadatas)) {
416
                    $schemaTool->createSchema($metadatas);
417
                }
418
                $this->postFixtureSetup();
419
420
                $executor = new $executorClass($om);
421
                $executor->setReferenceRepository($referenceRepository);
422
            }
423
        }
424
425
        if (empty($executor)) {
426
            $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
427
            if ('PHPCR' === $type) {
428
                $purger = new $purgerClass($om);
429
                $initManager = $container->has('doctrine_phpcr.initializer_manager')
430
                    ? $container->get('doctrine_phpcr.initializer_manager')
431
                    : null;
432
433
                $executor = new $executorClass($om, $purger, $initManager);
434
            } else {
435
                $purger = new $purgerClass();
436
                if (null !== $purgeMode) {
437
                    $purger->setPurgeMode($purgeMode);
438
                }
439
440
                $executor = new $executorClass($om, $purger);
441
            }
442
443
            $executor->setReferenceRepository($referenceRepository);
444
            $executor->purge();
445
        }
446
447
        $loader = $this->getFixtureLoader($container, $classNames);
448
449
        $executor->execute($loader->getFixtures(), true);
450
451
        if (isset($name) && isset($backup)) {
452
            $this->preReferenceSave($om, $executor, $backup);
453
454
            $executor->getReferenceRepository()->save($backup);
455
            copy($name, $backup);
456
457
            $this->postReferenceSave($om, $executor, $backup);
458
        }
459
460
        return $executor;
461
    }
462
463
    /**
464
     * Clean database.
465
     *
466
     * @param ManagerRegistry $registry
467
     * @param EntityManager   $om
468
     */
469
    private function cleanDatabase(ManagerRegistry $registry, EntityManager $om)
470
    {
471
        $connection = $om->getConnection();
472
473
        $mysql = ($registry->getName() === 'ORM'
474
            && $connection->getDatabasePlatform() instanceof MySqlPlatform);
475
476
        if ($mysql) {
477
            $connection->query('SET FOREIGN_KEY_CHECKS=0');
478
        }
479
480
        $this->loadFixtures(array());
481
482
        if ($mysql) {
483
            $connection->query('SET FOREIGN_KEY_CHECKS=1');
484
        }
485
    }
486
487
    /**
488
     * Locate fixture files.
489
     *
490
     * @param array $paths
491
     *
492
     * @return array $files
493
     */
494
    private function locateResources($paths)
495
    {
496
        $files = array();
497
498
        $kernel = $this->getContainer()->get('kernel');
499
500
        foreach ($paths as $path) {
501
            if ($path[0] !== '@' && file_exists($path) === true) {
502
                $files[] = $path;
503
                continue;
504
            }
505
506
            $files[] = $kernel->locateResource($path);
507
        }
508
509
        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
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine')
523
    {
524
        if (!class_exists('Nelmio\Alice\Fixtures')) {
525
            // This class is available during tests, no exception will be thrown.
526
            // @codeCoverageIgnoreStart
527
            throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
528
            // @codeCoverageIgnoreEnd
529
        }
530
531
        /** @var ContainerInterface $container */
532
        $container = $this->getContainer();
533
534
        /** @var ManagerRegistry $registry */
535
        $registry = $container->get($registryName);
536
537
        /** @var EntityManager $om */
538
        $om = $registry->getManager($omName);
539
540
        if ($append === false) {
541
            $this->cleanDatabase($registry, $om);
542
        }
543
544
        $files = $this->locateResources($paths);
545
546
        // Check if the Hautelook AliceBundle is registered and if yes, use it instead of Nelmio Alice
547
        $hautelookLoaderServiceName = 'hautelook_alice.fixtures.loader';
548
        if ($container->has($hautelookLoaderServiceName)) {
549
            $loaderService = $container->get($hautelookLoaderServiceName);
550
            $persisterClass = class_exists('Nelmio\Alice\ORM\Doctrine') ?
551
                'Nelmio\Alice\ORM\Doctrine' :
552
                'Nelmio\Alice\Persister\Doctrine';
553
554
            return $loaderService->load(new $persisterClass($om), $files);
555
        }
556
557
        return Fixtures::load($files, $om);
558
    }
559
560
    /**
561
     * Callback function to be executed after Schema creation.
562
     * Use this to execute acl:init or other things necessary.
563
     */
564
    protected function postFixtureSetup()
565
    {
566
    }
567
568
    /**
569
     * Callback function to be executed after Schema restore.
570
     *
571
     * @return WebTestCase
572
     */
573
    protected function postFixtureRestore()
574
    {
575
    }
576
577
    /**
578
     * Callback function to be executed before Schema restore.
579
     *
580
     * @param ObjectManager            $manager             The object manager
581
     * @param ProxyReferenceRepository $referenceRepository The reference repository
582
     *
583
     * @return WebTestCase
584
     */
585
    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...
586
    {
587
    }
588
589
    /**
590
     * Callback function to be executed after save of references.
591
     *
592
     * @param ObjectManager    $manager        The object manager
593
     * @param AbstractExecutor $executor       Executor of the data fixtures
594
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
595
     *
596
     * @return WebTestCase
597
     */
598
    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...
599
    {
600
    }
601
602
    /**
603
     * Callback function to be executed before save of references.
604
     *
605
     * @param ObjectManager    $manager        The object manager
606
     * @param AbstractExecutor $executor       Executor of the data fixtures
607
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
608
     *
609
     * @return WebTestCase
610
     */
611
    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...
612
    {
613
    }
614
615
    /**
616
     * Retrieve Doctrine DataFixtures loader.
617
     *
618
     * @param ContainerInterface $container
619
     * @param array              $classNames
620
     *
621
     * @return Loader
622
     */
623
    protected function getFixtureLoader(ContainerInterface $container, array $classNames)
624
    {
625
        $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
626
            ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
627
            : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
628
                // This class is not available during tests.
629
                // @codeCoverageIgnoreStart
630
                ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
631
                // @codeCoverageIgnoreEnd
632
                : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
633
634
        $loader = new $loaderClass($container);
635
636
        foreach ($classNames as $className) {
637
            $this->loadFixtureClass($loader, $className);
638
        }
639
640
        return $loader;
641
    }
642
643
    /**
644
     * Load a data fixture class.
645
     *
646
     * @param Loader $loader
647
     * @param string $className
648
     */
649
    protected function loadFixtureClass($loader, $className)
650
    {
651
        $fixture = new $className();
652
653
        if ($loader->hasFixture($fixture)) {
654
            unset($fixture);
655
656
            return;
657
        }
658
659
        $loader->addFixture($fixture);
660
661
        if ($fixture instanceof DependentFixtureInterface) {
662
            foreach ($fixture->getDependencies() as $dependency) {
663
                $this->loadFixtureClass($loader, $dependency);
664
            }
665
        }
666
    }
667
668
    /**
669
     * Creates an instance of a lightweight Http client.
670
     *
671
     * If $authentication is set to 'true' it will use the content of
672
     * 'liip_functional_test.authentication' to log in.
673
     *
674
     * $params can be used to pass headers to the client, note that they have
675
     * to follow the naming format used in $_SERVER.
676
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
677
     *
678
     * @param bool|array $authentication
679
     * @param array      $params
680
     *
681
     * @return Client
682
     */
683
    protected function makeClient($authentication = false, array $params = array())
684
    {
685
        if ($authentication) {
686
            if ($authentication === true) {
687
                $authentication = array(
688
                    'username' => $this->getContainer()
689
                        ->getParameter('liip_functional_test.authentication.username'),
690
                    'password' => $this->getContainer()
691
                        ->getParameter('liip_functional_test.authentication.password'),
692
                );
693
            }
694
695
            $params = array_merge($params, array(
696
                'PHP_AUTH_USER' => $authentication['username'],
697
                'PHP_AUTH_PW' => $authentication['password'],
698
            ));
699
        }
700
701
        $client = static::createClient(array('environment' => $this->environment), $params);
702
703
        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...
704
            // has to be set otherwise "hasPreviousSession" in Request returns false.
705
            $options = $client->getContainer()->getParameter('session.storage.options');
706
707
            if (!$options || !isset($options['name'])) {
708
                throw new \InvalidArgumentException('Missing session.storage.options#name');
709
            }
710
711
            $session = $client->getContainer()->get('session');
712
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
713
            if ($session instanceof Session) {
714
                $session->setId(uniqid());
715
            }
716
717
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
718
719
            /** @var $user UserInterface */
720
            foreach ($this->firewallLogins as $firewallName => $user) {
721
                $token = $this->createUserToken($user, $firewallName);
722
723
                // BC: security.token_storage is available on Symfony 2.6+
724
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
725
                if ($client->getContainer()->has('security.token_storage')) {
726
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
727
                } else {
728
                    // This block will never be reached with Symfony 2.6+
729
                    // @codeCoverageIgnoreStart
730
                    $tokenStorage = $client->getContainer()->get('security.context');
731
                    // @codeCoverageIgnoreEnd
732
                }
733
734
                $tokenStorage->setToken($token);
735
                $session->set('_security_'.$firewallName, serialize($token));
736
            }
737
738
            $session->save();
739
        }
740
741
        return $client;
742
    }
743
744
    /**
745
     * Create User Token.
746
     *
747
     * Factory method for creating a User Token object for the firewall based on
748
     * the user object provided. By default it will be a Username/Password
749
     * Token based on the user's credentials, but may be overridden for custom
750
     * tokens in your applications.
751
     *
752
     * @param UserInterface $user         The user object to base the token off of
753
     * @param string        $firewallName name of the firewall provider to use
754
     *
755
     * @return TokenInterface The token to be used in the security context
756
     */
757
    protected function createUserToken(UserInterface $user, $firewallName)
758
    {
759
        return new UsernamePasswordToken(
760
            $user,
761
            null,
762
            $firewallName,
763
            $user->getRoles()
764
        );
765
    }
766
767
    /**
768
     * Extracts the location from the given route.
769
     *
770
     * @param string $route    The name of the route
771
     * @param array  $params   Set of parameters
772
     * @param int    $absolute
773
     *
774
     * @return string
775
     */
776
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
777
    {
778
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
779
    }
780
781
    /**
782
     * Checks the success state of a response.
783
     *
784
     * @param Response $response Response object
785
     * @param bool     $success  to define whether the response is expected to be successful
786
     * @param string   $type
787
     */
788
    public function isSuccessful(Response $response, $success = true, $type = 'text/html')
789
    {
790
        try {
791
            $crawler = new Crawler();
792
            $crawler->addContent($response->getContent(), $type);
793
            if (!count($crawler->filter('title'))) {
794
                $title = '['.$response->getStatusCode().'] - '.$response->getContent();
795
            } else {
796
                $title = $crawler->filter('title')->text();
797
            }
798
        } catch (\Exception $e) {
799
            $title = $e->getMessage();
800
        }
801
802
        if ($success) {
803
            $this->assertTrue($response->isSuccessful(), 'The Response was not successful: '.$title);
804
        } else {
805
            $this->assertFalse($response->isSuccessful(), 'The Response was successful: '.$title);
806
        }
807
    }
808
809
    /**
810
     * Executes a request on the given url and returns the response contents.
811
     *
812
     * This method also asserts the request was successful.
813
     *
814
     * @param string $path           path of the requested page
815
     * @param string $method         The HTTP method to use, defaults to GET
816
     * @param bool   $authentication Whether to use authentication, defaults to false
817
     * @param bool   $success        to define whether the response is expected to be successful
818
     *
819
     * @return string
820
     */
821
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
822
    {
823
        $client = $this->makeClient($authentication);
824
        $client->request($method, $path);
825
826
        $content = $client->getResponse()->getContent();
827
        if (is_bool($success)) {
828
            $this->isSuccessful($client->getResponse(), $success);
829
        }
830
831
        return $content;
832
    }
833
834
    /**
835
     * Executes a request on the given url and returns a Crawler object.
836
     *
837
     * This method also asserts the request was successful.
838
     *
839
     * @param string $path           path of the requested page
840
     * @param string $method         The HTTP method to use, defaults to GET
841
     * @param bool   $authentication Whether to use authentication, defaults to false
842
     * @param bool   $success        Whether the response is expected to be successful
843
     *
844
     * @return Crawler
845
     */
846
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
847
    {
848
        $client = $this->makeClient($authentication);
849
        $crawler = $client->request($method, $path);
850
851
        $this->isSuccessful($client->getResponse(), $success);
852
853
        return $crawler;
854
    }
855
856
    /**
857
     * @param UserInterface $user
858
     * @param string        $firewallName
859
     *
860
     * @return WebTestCase
861
     */
862
    public function loginAs(UserInterface $user, $firewallName)
863
    {
864
        $this->firewallLogins[$firewallName] = $user;
865
866
        return $this;
867
    }
868
869
    /**
870
     * Asserts that the HTTP response code of the last request performed by
871
     * $client matches the expected code. If not, raises an error with more
872
     * information.
873
     *
874
     * @param $expectedStatusCode
875
     * @param Client $client
876
     */
877
    public function assertStatusCode($expectedStatusCode, Client $client)
878
    {
879
        $helpfulErrorMessage = null;
880
881
        if ($expectedStatusCode !== $client->getResponse()->getStatusCode()) {
882
            // Get a more useful error message, if available
883
            if ($exception = $client->getContainer()->get('liip_functional_test.exception_listener')->getLastException()) {
884
                $helpfulErrorMessage = $exception->getMessage();
885
            } elseif (count($validationErrors = $client->getContainer()->get('liip_functional_test.validator')->getLastErrors())) {
886
                $helpfulErrorMessage = "Unexpected validation errors:\n";
887
888
                foreach ($validationErrors as $error) {
889
                    $helpfulErrorMessage .= sprintf("+ %s: %s\n", $error->getPropertyPath(), $error->getMessage());
890
                }
891
            } else {
892
                $helpfulErrorMessage = substr($client->getResponse(), 0, 200);
893
            }
894
        }
895
896
        self::assertEquals($expectedStatusCode, $client->getResponse()->getStatusCode(), $helpfulErrorMessage);
897
    }
898
899
    /**
900
     * Assert that the last validation errors within $container match the
901
     * expected keys.
902
     *
903
     * @param array              $expected  A flat array of field names
904
     * @param ContainerInterface $container
905
     */
906
    public function assertValidationErrors(array $expected, ContainerInterface $container)
907
    {
908
        self::assertThat(
909
            $container->get('liip_functional_test.validator')->getLastErrors(),
910
            new ValidationErrorsConstraint($expected),
911
            'Validation errors should match.'
912
        );
913
    }
914
}
915