Completed
Push — refactor-loadFixtures ( 085b46...591d01 )
by Alexis
17:40 queued 08:31
created

WebTestCase::getDecorated()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0987

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 16
ccs 7
cts 9
cp 0.7778
rs 9.4285
cc 3
eloc 7
nc 4
nop 0
crap 3.0987
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\Connection;
37
use Doctrine\DBAL\Driver\PDOSqlite\Driver as SqliteDriver;
38
use Doctrine\DBAL\Platforms\MySqlPlatform;
39
use Doctrine\ORM\EntityManager;
40
use Doctrine\ORM\Tools\SchemaTool;
41
use Nelmio\Alice\Fixtures;
42
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
    protected function getServiceMockBuilder($id)
94
    {
95
        $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 1
            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 42
    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 42
        if (!empty($this->kernelDir)) {
260
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
261
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
262
        }
263
264 42
        $cacheKey = $this->kernelDir.'|'.$this->environment;
265 42
        if (empty($this->containers[$cacheKey])) {
266
            $options = array(
267 42
                'environment' => $this->environment,
268 42
            );
269 42
            $kernel = $this->createKernel($options);
270 42
            $kernel->boot();
271
272 42
            $this->containers[$cacheKey] = $kernel->getContainer();
273 42
        }
274
275 42
        if (isset($tmpKernelDir)) {
276
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
277
        }
278
279 42
        return $this->containers[$cacheKey];
280
    }
281
282
    /**
283
     * @param string $type
284
     *
285
     * @return string
286
     */
287 34
    private function getExecutorClass($type)
288
    {
289 34
        return 'PHPCR' === $type && class_exists('Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor')
290 34
            ? 'Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor'
291 34
            : 'Doctrine\\Common\\DataFixtures\\Executor\\'.$type.'Executor';
292
    }
293
294
    /**
295
     * Get file path of the SQLite database.
296
     *
297
     * @param Connection $connection
298
     *
299
     * @return string $name
300
     */
301 29
    private function getNameParameter(Connection $connection)
302
    {
303 29
        $params = $connection->getParams();
304
305 29
        if (isset($params['master'])) {
306
            $params = $params['master'];
307
        }
308
309 29
        $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
310
311 29
        if (!$name) {
312
            throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
313
        }
314
315 29
        return $name;
316
    }
317
318
    /**
319
     * Purge SQLite database.
320
     *
321
     * @param ObjectManager $om
322
     * @param string        $omName The name of object manager to use
323
     */
324 29
    private function getCachedMetadatas(ObjectManager $om, $omName)
325
    {
326 29
        if (!isset(self::$cachedMetadatas[$omName])) {
327 6
            self::$cachedMetadatas[$omName] = $om->getMetadataFactory()->getAllMetadata();
328
            usort(self::$cachedMetadatas[$omName], function ($a, $b) { return strcmp($a->name, $b->name); });
329 6
        }
330
331 29
        return self::$cachedMetadatas[$omName];
332
    }
333
334
    /**
335
     * This function finds the time when the data blocks of a class definition
336
     * file were being written to, that is, the time when the content of the
337
     * file was changed.
338
     *
339
     * @param string $class The fully qualified class name of the fixture class to
340
     *                      check modification date on.
341
     *
342
     * @return \DateTime|null
343
     */
344 2
    protected function getFixtureLastModified($class)
345
    {
346 2
        $lastModifiedDateTime = null;
347
348 2
        $reflClass = new \ReflectionClass($class);
349 2
        $classFileName = $reflClass->getFileName();
350
351 2
        if (file_exists($classFileName)) {
352 2
            $lastModifiedDateTime = new \DateTime();
353 2
            $lastModifiedDateTime->setTimestamp(filemtime($classFileName));
354 2
        }
355
356 2
        return $lastModifiedDateTime;
357
    }
358
359
    /**
360
     * Determine if the Fixtures that define a database backup have been
361
     * modified since the backup was made.
362
     *
363
     * @param array  $classNames The fixture classnames to check
364
     * @param string $backup     The fixture backup SQLite database file path
365
     *
366
     * @return bool TRUE if the backup was made since the modifications to the
367
     *              fixtures; FALSE otherwise
368
     */
369 3
    protected function isBackupUpToDate(array $classNames, $backup)
370
    {
371 3
        $backupLastModifiedDateTime = new \DateTime();
372 3
        $backupLastModifiedDateTime->setTimestamp(filemtime($backup));
373
374 3
        foreach ($classNames as &$className) {
375 2
            $fixtureLastModifiedDateTime = $this->getFixtureLastModified($className);
376 2
            if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) {
377
                return false;
378
            }
379 3
        }
380
381 3
        return true;
382
    }
383
384
    /**
385
     * Copy SQLite backup file.
386
     *
387
     * @param ObjectManager            $om
388
     * @param string                   $executorClass
389
     * @param ProxyReferenceRepository $referenceRepository
390
     * @param string                   $backup              Path of the source file.
391
     * @param string                   $name                Path of the destination file.
392
     */
393 3
    private function copySqliteBackup($om, $executorClass,
394
        $referenceRepository, $backup, $name)
395
    {
396 3
        $om->flush();
397 3
        $om->clear();
398
399 3
        $this->preFixtureRestore($om, $referenceRepository);
400
401 3
        copy($backup, $name);
402
403 3
        $executor = new $executorClass($om);
404 3
        $executor->setReferenceRepository($referenceRepository);
405 3
        $executor->getReferenceRepository()->load($backup);
406
407 3
        $this->postFixtureRestore();
408
409 3
        return $executor;
410
    }
411
412
    /**
413
     * Purge database.
414
     *
415
     * @param ObjectManager            $om
416
     * @param string                   $type
417
     * @param int                      $purgeMode
418
     * @param string                   $executorClass
419
     * @param ProxyReferenceRepository $referenceRepository
420
     */
421 31
    private function purgeDatabase(ObjectManager $om, $type, $purgeMode,
422
        $executorClass,
423
        ProxyReferenceRepository $referenceRepository)
424
    {
425 31
        $container = $this->getContainer();
426
427 31
        $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
428 31
        if ('PHPCR' === $type) {
429 1
            $purger = new $purgerClass($om);
430 1
            $initManager = $container->has('doctrine_phpcr.initializer_manager')
431 1
                ? $container->get('doctrine_phpcr.initializer_manager')
432 1
                : null;
433
434 1
            $executor = new $executorClass($om, $purger, $initManager);
435 1
        } else {
436 30
            $purger = new $purgerClass();
437 30
            if (null !== $purgeMode) {
438 1
                $purger->setPurgeMode($purgeMode);
439 1
            }
440
441 30
            $executor = new $executorClass($om, $purger);
442
        }
443
444 31
        $executor->setReferenceRepository($referenceRepository);
445 31
        $executor->purge();
446
447 31
        return $executor;
448
    }
449
450
    /**
451
     * Purge database.
452
     *
453
     * @param ObjectManager            $om
454
     * @param string                   $name
455
     * @param array                    $metadatas
456
     * @param string                   $executorClass
457
     * @param ProxyReferenceRepository $referenceRepository
458
     */
459 26
    private function createSqliteSchema(ObjectManager $om, $name,
460
        $metadatas, $executorClass,
461
        ProxyReferenceRepository $referenceRepository)
462
    {
463
        // TODO: handle case when using persistent connections. Fail loudly?
464 26
        $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...
465 26
        $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...
466 26
        if (!empty($metadatas)) {
467 26
            $schemaTool->createSchema($metadatas);
468 26
        }
469 26
        $this->postFixtureSetup();
470
471 26
        $executor = new $executorClass($om);
472 26
        $executor->setReferenceRepository($referenceRepository);
473 26
    }
474
475
    /**
476
     * Set the database to the provided fixtures.
477
     *
478
     * Drops the current database and then loads fixtures using the specified
479
     * classes. The parameter is a list of fully qualified class names of
480
     * classes that implement Doctrine\Common\DataFixtures\FixtureInterface
481
     * so that they can be loaded by the DataFixtures Loader::addFixture
482
     *
483
     * When using SQLite this method will automatically make a copy of the
484
     * loaded schema and fixtures which will be restored automatically in
485
     * case the same fixture classes are to be loaded again. Caveat: changes
486
     * to references and/or identities may go undetected.
487
     *
488
     * Depends on the doctrine data-fixtures library being available in the
489
     * class path.
490
     *
491
     * @param array  $classNames   List of fully qualified class names of fixtures to load
492
     * @param string $omName       The name of object manager to use
493
     * @param string $registryName The service id of manager registry to use
494
     * @param int    $purgeMode    Sets the ORM purge mode
495
     *
496
     * @return null|AbstractExecutor
497
     */
498 34
    protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
499
    {
500 34
        $container = $this->getContainer();
501
        /** @var ManagerRegistry $registry */
502 34
        $registry = $container->get($registryName);
503
        /** @var ObjectManager $om */
504 34
        $om = $registry->getManager($omName);
505 34
        $type = $registry->getName();
506
507 34
        $executorClass = $this->getExecutorClass($type);
508 34
        $referenceRepository = new ProxyReferenceRepository($om);
509
510 34
        $cacheDriver = $om->getMetadataFactory()->getCacheDriver();
511
512 34
        if ($cacheDriver) {
513 34
            $cacheDriver->deleteAll();
514 34
        }
515
516 34
        if ('ORM' === $type) {
517 33
            $connection = $om->getConnection();
518 33
            if ($connection->getDriver() instanceof SqliteDriver) {
519 29
                $name = $this->getNameParameter($connection);
520 29
                $metadatas = $this->getCachedMetadatas($om, $omName);
521
522 29
                if ($container->getParameter('liip_functional_test.cache_sqlite_db')) {
523 5
                    $backup = $container->getParameter('kernel.cache_dir').'/test_'.md5(serialize($metadatas).serialize($classNames)).'.db';
524 5
                    if (file_exists($backup) && file_exists($backup.'.ser') && $this->isBackupUpToDate($classNames, $backup)) {
525 3
                        $executor = $this->copySqliteBackup($om,
526 3
                            $executorClass, $referenceRepository,
527 3
                            $backup, $name);
528
529 3
                        return $executor;
530
                    }
531 2
                }
532
533 26
                $this->createSqliteSchema($om, $name, $metadatas,
534 26
                    $executorClass, $referenceRepository);
535 26
            }
536 30
        }
537
538 31
        if (empty($executor)) {
0 ignored issues
show
Bug introduced by
The variable $executor seems only to be defined at a later point. As such the call to empty() seems to always evaluate to true.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

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