Completed
Push — refactor-loadFixtureFiles ( 1ab1ed...440b7e )
by Alexis
09:06 queued 01:28
created

WebTestCase::locateResources()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 17
ccs 10
cts 10
cp 1
rs 9.2
cc 4
eloc 9
nc 3
nop 1
crap 4
1
<?php
2
3
/*
4
 * This file is part of the Liip/FunctionalTestBundle
5
 *
6
 * (c) Lukas Kahwe Smith <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Liip\FunctionalTestBundle\Test;
13
14
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
15
use Symfony\Bundle\FrameworkBundle\Console\Application;
16
use Symfony\Bundle\FrameworkBundle\Client;
17
use Symfony\Component\Console\Input\ArrayInput;
18
use Symfony\Component\Console\Output\OutputInterface;
19
use Symfony\Component\Console\Output\StreamOutput;
20
use Symfony\Component\DomCrawler\Crawler;
21
use Symfony\Component\BrowserKit\Cookie;
22
use Symfony\Component\HttpKernel\Kernel;
23
use Symfony\Component\HttpFoundation\Response;
24
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
25
use Symfony\Component\Security\Core\User\UserInterface;
26
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
27
use Symfony\Component\DependencyInjection\ContainerInterface;
28
use Symfony\Component\HttpFoundation\Session\Session;
29
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
30
use Symfony\Bridge\Doctrine\ManagerRegistry;
31
use Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader;
32
use Doctrine\Common\Persistence\ObjectManager;
33
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
34
use Doctrine\Common\DataFixtures\Executor\AbstractExecutor;
35
use Doctrine\Common\DataFixtures\ProxyReferenceRepository;
36
use Doctrine\DBAL\Driver\PDOSqlite\Driver as SqliteDriver;
37
use Doctrine\ORM\EntityManager;
38
use Doctrine\DBAL\Platforms\MySqlPlatform;
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 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...
70
    {
71 1
        $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : self::getPhpUnitXmlDir();
72
73 1
        list($appname) = explode('\\', get_called_class());
74
75 1
        $class = $appname.'Kernel';
76 1
        $file = $dir.'/'.strtolower($appname).'/'.$class.'.php';
77 1
        if (!file_exists($file)) {
78 1
            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 2
    protected function getServiceMockBuilder($id)
93
    {
94
        $service = $this->getContainer()->get($id);
95 2
        $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 12
    protected function runCommand($name, array $params = array(), $reuseKernel = false)
110
    {
111 12
        array_unshift($params, $name);
112
113 12
        if (!$reuseKernel) {
114 12
            if (null !== static::$kernel) {
115 9
                static::$kernel->shutdown();
116 9
            }
117
118 12
            $kernel = static::$kernel = $this->createKernel(array('environment' => $this->environment));
119 12
            $kernel->boot();
120 12
        } else {
121 2
            $kernel = $this->getContainer()->get('kernel');
122
        }
123
124 12
        $application = new Application($kernel);
125 12
        $application->setAutoExit(false);
126
127
        // @codeCoverageIgnoreStart
128
        if ('20301' === Kernel::VERSION_ID) {
129
            $params = $this->configureVerbosityForSymfony20301($params);
130
        }
131
        // @codeCoverageIgnoreEnd
132
133 12
        $input = new ArrayInput($params);
134 12
        $input->setInteractive(false);
135
136 12
        $fp = fopen('php://temp/maxmemory:'.$this->maxMemory, 'r+');
137 12
        $output = new StreamOutput($fp, $this->getVerbosityLevel(), $this->getDecorated());
138
139 11
        $application->run($input, $output);
140
141 11
        rewind($fp);
142
143 11
        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 12
    protected function getVerbosityLevel()
156
    {
157
        // If `null`, is not yet set
158 12
        if (null === $this->verbosityLevel) {
159
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
160 6
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
161 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
162
163 6
            $this->verbosityLevel = constant($verbosity);
164 6
        }
165
166
        // If string, it is set by the developer, so check that the value is an accepted one
167 12
        if (is_string($this->verbosityLevel)) {
168 6
            $level = strtoupper($this->verbosityLevel);
169 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
170
171 6
            if (!defined($verbosity)) {
172 1
                throw new \OutOfBoundsException(
173 1
                    sprintf('The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".', $level)
174 1
                    );
175
            }
176
177 5
            $this->verbosityLevel = constant($verbosity);
178 5
        }
179
180 11
        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 6
    public function setVerbosityLevel($level)
219
    {
220 6
        $this->verbosityLevel = $level;
221 6
    }
222
223
    /**
224
     * Retrieves the flag indicating if the output should be decorated or not.
225
     *
226
     * @return bool
227
     */
228 11
    protected function getDecorated()
229
    {
230 11
        if (null === $this->decorated) {
231
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
232 5
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
233 5
        }
234
235
        // Check the local decorated flag
236 11
        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 11
        return $this->decorated;
243
    }
244
245 6
    public function isDecorated($decorated)
246
    {
247 6
        $this->decorated = $decorated;
248 6
    }
249
250
    /**
251
     * Get an instance of the dependency injection container.
252
     * (this creates a kernel *without* parameters).
253
     *
254
     * @return ContainerInterface
255
     */
256 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...
257
    {
258 34
        if (!empty($this->kernelDir)) {
259
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
260
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
261
        }
262
263 34
        $cacheKey = $this->kernelDir.'|'.$this->environment;
264 34
        if (empty($this->containers[$cacheKey])) {
265
            $options = array(
266 34
                'environment' => $this->environment,
267 34
            );
268 34
            $kernel = $this->createKernel($options);
269 34
            $kernel->boot();
270
271 34
            $this->containers[$cacheKey] = $kernel->getContainer();
272 34
        }
273
274 34
        if (isset($tmpKernelDir)) {
275
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
276
        }
277
278 34
        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 2
    protected function getFixtureLastModified($class)
292
    {
293 2
        $lastModifiedDateTime = null;
294
295 2
        $reflClass = new \ReflectionClass($class);
296 2
        $classFileName = $reflClass->getFileName();
297
298 2
        if (file_exists($classFileName)) {
299 2
            $lastModifiedDateTime = new \DateTime();
300 2
            $lastModifiedDateTime->setTimestamp(filemtime($classFileName));
301 2
        }
302
303 2
        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 3
    protected function isBackupUpToDate(array $classNames, $backup)
317
    {
318 3
        $backupLastModifiedDateTime = new \DateTime();
319 3
        $backupLastModifiedDateTime->setTimestamp(filemtime($backup));
320
321 3
        foreach ($classNames as &$className) {
322 2
            $fixtureLastModifiedDateTime = $this->getFixtureLastModified($className);
323 2
            if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) {
324
                return false;
325
            }
326 3
        }
327
328 3
        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 26
    protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
355
    {
356 26
        $container = $this->getContainer();
357
        /** @var ManagerRegistry $registry */
358 26
        $registry = $container->get($registryName);
359 26
        $om = $registry->getManager($omName);
360 26
        $type = $registry->getName();
361
362 26
        $executorClass = 'PHPCR' === $type && class_exists('Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor')
363 26
            ? 'Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor'
364 26
            : 'Doctrine\\Common\\DataFixtures\\Executor\\'.$type.'Executor';
365 26
        $referenceRepository = new ProxyReferenceRepository($om);
366 26
        $cacheDriver = $om->getMetadataFactory()->getCacheDriver();
367
368 26
        if ($cacheDriver) {
369 26
            $cacheDriver->deleteAll();
370 26
        }
371
372 26
        if ('ORM' === $type) {
373 25
            $connection = $om->getConnection();
374 25
            if ($connection->getDriver() instanceof SqliteDriver) {
375 21
                $params = $connection->getParams();
376 21
                if (isset($params['master'])) {
377
                    $params = $params['master'];
378
                }
379
380 21
                $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
381 21
                if (!$name) {
382
                    throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
383
                }
384
385 21
                if (!isset(self::$cachedMetadatas[$omName])) {
386 6
                    self::$cachedMetadatas[$omName] = $om->getMetadataFactory()->getAllMetadata();
387
                    usort(self::$cachedMetadatas[$omName], function ($a, $b) { return strcmp($a->name, $b->name); });
388 6
                }
389 21
                $metadatas = self::$cachedMetadatas[$omName];
390
391 21
                if ($container->getParameter('liip_functional_test.cache_sqlite_db')) {
392 5
                    $backup = $container->getParameter('kernel.cache_dir').'/test_'.md5(serialize($metadatas).serialize($classNames)).'.db';
393 5
                    if (file_exists($backup) && file_exists($backup.'.ser') && $this->isBackupUpToDate($classNames, $backup)) {
394 3
                        $om->flush();
395 3
                        $om->clear();
396
397 3
                        $this->preFixtureRestore($om, $referenceRepository);
398
399 3
                        copy($backup, $name);
400
401 3
                        $executor = new $executorClass($om);
402 3
                        $executor->setReferenceRepository($referenceRepository);
403 3
                        $executor->getReferenceRepository()->load($backup);
404
405 3
                        $this->postFixtureRestore();
406
407 3
                        return $executor;
408
                    }
409 2
                }
410
411
                // TODO: handle case when using persistent connections. Fail loudly?
412 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...
413 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...
414 18
                if (!empty($metadatas)) {
415 18
                    $schemaTool->createSchema($metadatas);
416 18
                }
417 18
                $this->postFixtureSetup();
418
419 18
                $executor = new $executorClass($om);
420 18
                $executor->setReferenceRepository($referenceRepository);
421 18
            }
422 22
        }
423
424 23
        if (empty($executor)) {
425 5
            $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
426 5
            if ('PHPCR' === $type) {
427 1
                $purger = new $purgerClass($om);
428 1
                $initManager = $container->has('doctrine_phpcr.initializer_manager')
429 1
                    ? $container->get('doctrine_phpcr.initializer_manager')
430 1
                    : null;
431
432 1
                $executor = new $executorClass($om, $purger, $initManager);
433 1
            } else {
434 4
                $purger = new $purgerClass();
435 4
                if (null !== $purgeMode) {
436 1
                    $purger->setPurgeMode($purgeMode);
437 1
                }
438
439 4
                $executor = new $executorClass($om, $purger);
440
            }
441
442 5
            $executor->setReferenceRepository($referenceRepository);
443 5
            $executor->purge();
444 5
        }
445
446 23
        $loader = $this->getFixtureLoader($container, $classNames);
447
448 23
        $executor->execute($loader->getFixtures(), true);
449
450 23
        if (isset($name) && isset($backup)) {
451 2
            $this->preReferenceSave($om, $executor, $backup);
452
453 2
            $executor->getReferenceRepository()->save($backup);
454 2
            copy($name, $backup);
455
456 2
            $this->postReferenceSave($om, $executor, $backup);
457 2
        }
458
459 23
        return $executor;
460
    }
461
462
    /**
463
     * Clean database.
464
     *
465
     * @param ManagerRegistry $registry
466
     * @param EntityManager   $om
467
     */
468 3
    private function cleanDatabase(ManagerRegistry $registry, EntityManager $om)
469
    {
470 3
        $connection = $om->getConnection();
471
472 3
        $mysql = ($registry->getName() === 'ORM'
473 3
            && $connection->getDatabasePlatform() instanceof MySqlPlatform);
474
475 3
        if ($mysql) {
476 1
            $connection->query('SET FOREIGN_KEY_CHECKS=0');
477 1
        }
478
479 3
        $this->loadFixtures(array());
480
481 3
        if ($mysql) {
482 1
            $connection->query('SET FOREIGN_KEY_CHECKS=1');
483 1
        }
484 3
    }
485
486
    /**
487
     * Locate fixture files.
488
     *
489
     * @param array $paths
490
     *
491
     * @return array $files
492
     */
493 5
    private function locateResources($paths)
494
    {
495 3
        $files = array();
496
497 3
        $kernel = $this->getContainer()->get('kernel');
498
499 3
        foreach ($paths as $path) {
500 3
            if ($path[0] !== '@' && file_exists($path) === true) {
501 1
                $files[] = $path;
502 1
                continue;
503
            }
504
505 5
            $files[] = $kernel->locateResource($path);
506 3
        }
507
508 3
        return $files;
509
    }
510
511
    /**
512
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
513
     * @param bool   $append
514
     * @param null   $omName
515
     * @param string $registryName
516
     *
517
     * @return array
518
     *
519
     * @throws \BadMethodCallException
520
     */
521 3
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine')
522
    {
523 3
        if (!class_exists('Nelmio\Alice\Fixtures')) {
524
            throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
525
        }
526
527
        /** @var ManagerRegistry $registry */
528 3
        $registry = $this->getContainer()->get($registryName);
529 3
        $om = $registry->getManager($omName);
530
531 3
        if ($append === false) {
532 3
            $this->cleanDatabase($registry, $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\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...
533 3
        }
534
535 3
        $files = $this->locateResources($paths);
536
537 3
        return Fixtures::load($files, $om);
538
    }
539
540
    /**
541
     * Callback function to be executed after Schema creation.
542
     * Use this to execute acl:init or other things necessary.
543
     */
544 18
    protected function postFixtureSetup()
545
    {
546 18
    }
547
548
    /**
549
     * Callback function to be executed after Schema restore.
550
     *
551
     * @return WebTestCase
552
     */
553 3
    protected function postFixtureRestore()
554
    {
555 3
    }
556
557
    /**
558
     * Callback function to be executed before Schema restore.
559
     *
560
     * @param ObjectManager            $manager             The object manager
561
     * @param ProxyReferenceRepository $referenceRepository The reference repository
562
     *
563
     * @return WebTestCase
564
     */
565 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...
566
    {
567 3
    }
568
569
    /**
570
     * Callback function to be executed after save of references.
571
     *
572
     * @param ObjectManager    $manager        The object manager
573
     * @param AbstractExecutor $executor       Executor of the data fixtures
574
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
575
     *
576
     * @return WebTestCase
577
     */
578 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...
579
    {
580 2
    }
581
582
    /**
583
     * Callback function to be executed before save of references.
584
     *
585
     * @param ObjectManager    $manager        The object manager
586
     * @param AbstractExecutor $executor       Executor of the data fixtures
587
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
588
     *
589
     * @return WebTestCase
590
     */
591 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...
592
    {
593 2
    }
594
595
    /**
596
     * Retrieve Doctrine DataFixtures loader.
597
     *
598
     * @param ContainerInterface $container
599
     * @param array              $classNames
600
     *
601
     * @return Loader
602
     */
603 23
    protected function getFixtureLoader(ContainerInterface $container, array $classNames)
604
    {
605 23
        $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
606 23
            ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
607 23
            : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
608
                ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
609 23
                : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
610
611 23
        $loader = new $loaderClass($container);
612
613 23
        foreach ($classNames as $className) {
614 6
            $this->loadFixtureClass($loader, $className);
615 23
        }
616
617 23
        return $loader;
618
    }
619
620
    /**
621
     * Load a data fixture class.
622
     *
623
     * @param Loader $loader
624
     * @param string $className
625
     */
626 6
    protected function loadFixtureClass($loader, $className)
627
    {
628 6
        $fixture = new $className();
629
630 6
        if ($loader->hasFixture($fixture)) {
631
            unset($fixture);
632
633
            return;
634
        }
635
636 6
        $loader->addFixture($fixture);
637
638 6
        if ($fixture instanceof DependentFixtureInterface) {
639
            foreach ($fixture->getDependencies() as $dependency) {
640
                $this->loadFixtureClass($loader, $dependency);
641
            }
642
        }
643 6
    }
644
645
    /**
646
     * Creates an instance of a lightweight Http client.
647
     *
648
     * If $authentication is set to 'true' it will use the content of
649
     * 'liip_functional_test.authentication' to log in.
650
     *
651
     * $params can be used to pass headers to the client, note that they have
652
     * to follow the naming format used in $_SERVER.
653
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
654
     *
655
     * @param bool|array $authentication
656
     * @param array      $params
657
     *
658
     * @return Client
659
     */
660 46
    protected function makeClient($authentication = false, array $params = array())
661
    {
662 46
        if ($authentication) {
663 2
            if ($authentication === true) {
664
                $authentication = array(
665 1
                    'username' => $this->getContainer()
666 1
                        ->getParameter('liip_functional_test.authentication.username'),
667 1
                    'password' => $this->getContainer()
668 1
                        ->getParameter('liip_functional_test.authentication.password'),
669 1
                );
670 1
            }
671
672 2
            $params = array_merge($params, array(
673 2
                'PHP_AUTH_USER' => $authentication['username'],
674 2
                'PHP_AUTH_PW' => $authentication['password'],
675 2
            ));
676 2
        }
677
678 46
        $client = static::createClient(array('environment' => $this->environment), $params);
679
680 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...
681
            // has to be set otherwise "hasPreviousSession" in Request returns false.
682 2
            $options = $client->getContainer()->getParameter('session.storage.options');
683
684 2
            if (!$options || !isset($options['name'])) {
685
                throw new \InvalidArgumentException('Missing session.storage.options#name');
686
            }
687
688 2
            $session = $client->getContainer()->get('session');
689
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
690 2
            if ($session instanceof Session) {
691 2
                $session->setId(uniqid());
692 2
            }
693
694 2
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
695
696
            /** @var $user UserInterface */
697 2
            foreach ($this->firewallLogins as $firewallName => $user) {
698 2
                $token = $this->createUserToken($user, $firewallName);
699
700
                // BC: security.token_storage is available on Symfony 2.6+
701
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
702 2
                if ($client->getContainer()->has('security.token_storage')) {
703 2
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
704 2
                } else {
705
                    // This block will never be reached with Symfony 2.6+
706
                    // @codeCoverageIgnoreStart
707
                    $tokenStorage = $client->getContainer()->get('security.context');
708
                    // @codeCoverageIgnoreEnd
709
                }
710
711 2
                $tokenStorage->setToken($token);
712 2
                $session->set('_security_'.$firewallName, serialize($token));
713 2
            }
714
715 2
            $session->save();
716 2
        }
717
718 46
        return $client;
719
    }
720
721
    /**
722
     * Create User Token.
723
     *
724
     * Factory method for creating a User Token object for the firewall based on
725
     * the user object provided. By default it will be a Username/Password
726
     * Token based on the user's credentials, but may be overridden for custom
727
     * tokens in your applications.
728
     *
729
     * @param UserInterface $user         The user object to base the token off of
730
     * @param string        $firewallName name of the firewall provider to use
731
     *
732
     * @return TokenInterface The token to be used in the security context
733
     */
734 2
    protected function createUserToken(UserInterface $user, $firewallName)
735
    {
736 2
        return new UsernamePasswordToken(
737 2
            $user,
738 2
            null,
739 2
            $firewallName,
740 2
            $user->getRoles()
741 2
        );
742
    }
743
744
    /**
745
     * Extracts the location from the given route.
746
     *
747
     * @param string $route    The name of the route
748
     * @param array  $params   Set of parameters
749
     * @param bool   $absolute
750
     *
751
     * @return string
752
     */
753 1
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
754
    {
755 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
756
    }
757
758
    /**
759
     * Checks the success state of a response.
760
     *
761
     * @param Response $response Response object
762
     * @param bool     $success  to define whether the response is expected to be successful
763
     * @param string   $type
764
     */
765 5
    public function isSuccessful(Response $response, $success = true, $type = 'text/html')
766
    {
767
        try {
768 5
            $crawler = new Crawler();
769 5
            $crawler->addContent($response->getContent(), $type);
770 5
            if (!count($crawler->filter('title'))) {
771 1
                $title = '['.$response->getStatusCode().'] - '.$response->getContent();
772 1
            } else {
773 4
                $title = $crawler->filter('title')->text();
774
            }
775 5
        } catch (\Exception $e) {
776
            $title = $e->getMessage();
777
        }
778
779 5
        if ($success) {
780 4
            $this->assertTrue($response->isSuccessful(), 'The Response was not successful: '.$title);
781 4
        } else {
782 1
            $this->assertFalse($response->isSuccessful(), 'The Response was successful: '.$title);
783
        }
784 5
    }
785
786
    /**
787
     * Executes a request on the given url and returns the response contents.
788
     *
789
     * This method also asserts the request was successful.
790
     *
791
     * @param string $path           path of the requested page
792
     * @param string $method         The HTTP method to use, defaults to GET
793
     * @param bool   $authentication Whether to use authentication, defaults to false
794
     * @param bool   $success        to define whether the response is expected to be successful
795
     *
796
     * @return string
797
     */
798 1
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
799
    {
800 1
        $client = $this->makeClient($authentication);
801 1
        $client->request($method, $path);
802
803 1
        $content = $client->getResponse()->getContent();
804 1
        if (is_bool($success)) {
805 1
            $this->isSuccessful($client->getResponse(), $success);
806 1
        }
807
808 1
        return $content;
809
    }
810
811
    /**
812
     * Executes a request on the given url and returns a Crawler object.
813
     *
814
     * This method also asserts the request was successful.
815
     *
816
     * @param string $path           path of the requested page
817
     * @param string $method         The HTTP method to use, defaults to GET
818
     * @param bool   $authentication Whether to use authentication, defaults to false
819
     * @param bool   $success        Whether the response is expected to be successful
820
     *
821
     * @return Crawler
822
     */
823 1
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
824
    {
825 1
        $client = $this->makeClient($authentication);
826 1
        $crawler = $client->request($method, $path);
827
828 1
        $this->isSuccessful($client->getResponse(), $success);
829
830 1
        return $crawler;
831
    }
832
833
    /**
834
     * @param UserInterface $user
835
     *
836
     * @return WebTestCase
837
     */
838 2
    public function loginAs(UserInterface $user, $firewallName)
839
    {
840 2
        $this->firewallLogins[$firewallName] = $user;
841
842 2
        return $this;
843
    }
844
845
    /**
846
     * Asserts that the HTTP response code of the last request performed by
847
     * $client matches the expected code. If not, raises an error with more
848
     * information.
849
     *
850
     * @param $expectedStatusCode
851
     * @param Client $client
852
     */
853 9
    public function assertStatusCode($expectedStatusCode, Client $client)
854
    {
855 9
        $helpfulErrorMessage = null;
856
857 9
        if ($expectedStatusCode !== $client->getResponse()->getStatusCode()) {
858
            // Get a more useful error message, if available
859
            if ($exception = $client->getContainer()->get('liip_functional_test.exception_listener')->getLastException()) {
860
                $helpfulErrorMessage = $exception->getMessage();
861
            } elseif (count($validationErrors = $client->getContainer()->get('liip_functional_test.validator')->getLastErrors())) {
862
                $helpfulErrorMessage = "Unexpected validation errors:\n";
863
864
                foreach ($validationErrors as $error) {
865
                    $helpfulErrorMessage .= sprintf("+ %s: %s\n", $error->getPropertyPath(), $error->getMessage());
866
                }
867
            } else {
868
                $helpfulErrorMessage = substr($client->getResponse(), 0, 200);
869
            }
870
        }
871
872 9
        self::assertEquals($expectedStatusCode, $client->getResponse()->getStatusCode(), $helpfulErrorMessage);
873 9
    }
874
875
    /**
876
     * Assert that the last validation errors within $container match the
877
     * expected keys.
878
     *
879
     * @param array              $expected  A flat array of field names
880
     * @param ContainerInterface $container
881
     */
882 2
    public function assertValidationErrors(array $expected, ContainerInterface $container)
883
    {
884 2
        self::assertThat(
885 2
            $container->get('liip_functional_test.validator')->getLastErrors(),
886 2
            new ValidationErrorsConstraint($expected),
887
            'Validation errors should match.'
888 2
        );
889 1
    }
890
}
891