Passed
Pull Request — master (#330)
by
unknown
24:48
created

WebTestCase::createApplication()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
443 24
                $schemaTool->dropDatabase();
444 24
                if (!empty($metadatas)) {
445 24
                    $schemaTool->createSchema($metadatas);
446 24
                }
447 24
                $this->postFixtureSetup();
448
449 24
                $executor = new $executorClass($om);
450 24
                $executor->setReferenceRepository($referenceRepository);
451 24
            }
452 29
        }
453
454 30
        if (empty($executor)) {
455 6
            $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
456 6
            if ('PHPCR' === $type) {
457 1
                $purger = new $purgerClass($om);
458 1
                $initManager = $container->has('doctrine_phpcr.initializer_manager')
459 1
                    ? $container->get('doctrine_phpcr.initializer_manager')
460 1
                    : null;
461
462 1
                $executor = new $executorClass($om, $purger, $initManager);
463 1
            } else {
464 5
                if ('ORM' === $type) {
465 5
                    $purger = new $purgerClass(null, $this->excludedDoctrineTables);
466 5
                } else {
467
                    $purger = new $purgerClass();
468
                }
469
470 5
                if (null !== $purgeMode) {
471 2
                    $purger->setPurgeMode($purgeMode);
472 2
                }
473
474 5
                $executor = new $executorClass($om, $purger);
475
            }
476
477 6
            $executor->setReferenceRepository($referenceRepository);
478 6
            $executor->purge();
479 6
        }
480
481 30
        $loader = $this->getFixtureLoader($container, $classNames);
482
483 30
        $executor->execute($loader->getFixtures(), true);
484
485 30
        if (isset($name) && isset($backup)) {
486 3
            $this->preReferenceSave($om, $executor, $backup);
487
488 3
            $executor->getReferenceRepository()->save($backup);
489 3
            copy($name, $backup);
490
491 3
            $this->postReferenceSave($om, $executor, $backup);
492 3
        }
493
494 30
        return $executor;
495
    }
496
497
    /**
498
     * Clean database.
499
     *
500
     * @param ManagerRegistry $registry
501
     * @param EntityManager   $om
502
     * @param null            $omName
503
     * @param string          $registryName
504
     * @param int             $purgeMode
505
     */
506 9
    private function cleanDatabase(ManagerRegistry $registry, EntityManager $om, $omName = null, $registryName = 'doctrine', $purgeMode = null)
507
    {
508 9
        $connection = $om->getConnection();
509
510 9
        $mysql = ($registry->getName() === 'ORM'
511 9
            && $connection->getDatabasePlatform() instanceof MySqlPlatform);
512
513 9
        if ($mysql) {
514 1
            $connection->query('SET FOREIGN_KEY_CHECKS=0');
515 1
        }
516
517 9
        $this->loadFixtures(array(), $omName, $registryName, $purgeMode);
518
519 9
        if ($mysql) {
520 1
            $connection->query('SET FOREIGN_KEY_CHECKS=1');
521 1
        }
522 9
    }
523
524
    /**
525
     * Locate fixture files.
526
     *
527
     * @param array $paths
528
     *
529
     * @return array $files
530
     *
531
     * @throws \InvalidArgumentException if a wrong path is given outside a bundle
532
     */
533 10
    private function locateResources($paths)
534
    {
535 10
        $files = array();
536
537 10
        $kernel = $this->getContainer()->get('kernel');
538
539 10
        foreach ($paths as $path) {
540 10
            if ($path[0] !== '@') {
541 3
                if (!file_exists($path)) {
542 1
                    throw new \InvalidArgumentException(sprintf('Unable to find file "%s".', $path));
543
                }
544 2
                $files[] = $path;
545 2
                continue;
546
            }
547
548 7
            $files[] = $kernel->locateResource($path);
549 8
        }
550
551 8
        return $files;
552
    }
553
554
    /**
555
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
556
     * @param bool   $append
557
     * @param null   $omName
558
     * @param string $registryName
559
     * @param int    $purgeMode
560
     *
561
     * @return array
562
     *
563
     * @throws \BadMethodCallException
564
     */
565 10
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine', $purgeMode = null)
566
    {
567 10
        if (!class_exists('Nelmio\Alice\Fixtures')) {
568
            // This class is available during tests, no exception will be thrown.
569
            // @codeCoverageIgnoreStart
570
            throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
571
            // @codeCoverageIgnoreEnd
572
        }
573
574
        /** @var ContainerInterface $container */
575 10
        $container = $this->getContainer();
576
577
        /** @var ManagerRegistry $registry */
578 10
        $registry = $container->get($registryName);
579
580
        /** @var EntityManager $om */
581 10
        $om = $registry->getManager($omName);
582
583 10
        if ($append === false) {
584 9
            $this->cleanDatabase($registry, $om, $omName, $registryName, $purgeMode);
585 9
        }
586
587 10
        $files = $this->locateResources($paths);
588
589
        // Check if the Hautelook AliceBundle is registered and if yes, use it instead of Nelmio Alice
590 8
        $hautelookLoaderServiceName = 'hautelook_alice.fixtures.loader';
591 8
        if ($container->has($hautelookLoaderServiceName)) {
592 3
            $loaderService = $container->get($hautelookLoaderServiceName);
593 3
            $persisterClass = class_exists('Nelmio\Alice\ORM\Doctrine') ?
594 3
                'Nelmio\Alice\ORM\Doctrine' :
595 3
                'Nelmio\Alice\Persister\Doctrine';
596
597 3
            return $loaderService->load(new $persisterClass($om), $files);
598
        }
599
600 5
        return Fixtures::load($files, $om);
601
    }
602
603
    /**
604
     * Callback function to be executed after Schema creation.
605
     * Use this to execute acl:init or other things necessary.
606
     */
607 24
    protected function postFixtureSetup()
608
    {
609 24
    }
610
611
    /**
612
     * Callback function to be executed after Schema restore.
613
     *
614
     * @return WebTestCase
615
     *
616
     * @deprecated since version 1.8, to be removed in 2.0. Use postFixtureBackupRestore method instead.
617
     */
618 7
    protected function postFixtureRestore()
619
    {
620 7
    }
621
622
    /**
623
     * Callback function to be executed before Schema restore.
624
     *
625
     * @param ObjectManager            $manager             The object manager
626
     * @param ProxyReferenceRepository $referenceRepository The reference repository
627
     *
628
     * @return WebTestCase
629
     *
630
     * @deprecated since version 1.8, to be removed in 2.0. Use preFixtureBackupRestore method instead.
631
     */
632 7
    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...
633
    {
634 7
    }
635
636
    /**
637
     * Callback function to be executed after Schema restore.
638
     *
639
     * @param string $backupFilePath Path of file used to backup the references of the data fixtures
640
     *
641
     * @return WebTestCase
642
     */
643 7
    protected function postFixtureBackupRestore($backupFilePath)
0 ignored issues
show
Unused Code introduced by
The parameter $backupFilePath is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
644
    {
645 7
        $this->postFixtureRestore();
0 ignored issues
show
Deprecated Code introduced by
The method Liip\FunctionalTestBundl...e::postFixtureRestore() has been deprecated with message: since version 1.8, to be removed in 2.0. Use postFixtureBackupRestore method instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
646
647 7
        return $this;
648
    }
649
650
    /**
651
     * Callback function to be executed before Schema restore.
652
     *
653
     * @param ObjectManager            $manager             The object manager
654
     * @param ProxyReferenceRepository $referenceRepository The reference repository
655
     * @param string                   $backupFilePath      Path of file used to backup the references of the data fixtures
656
     *
657
     * @return WebTestCase
658
     */
659 7
    protected function preFixtureBackupRestore(
660
        ObjectManager $manager,
661
        ProxyReferenceRepository $referenceRepository,
662
        $backupFilePath
0 ignored issues
show
Unused Code introduced by
The parameter $backupFilePath is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
663
    ) {
664 7
        $this->preFixtureRestore($manager, $referenceRepository);
0 ignored issues
show
Deprecated Code introduced by
The method Liip\FunctionalTestBundl...se::preFixtureRestore() has been deprecated with message: since version 1.8, to be removed in 2.0. Use preFixtureBackupRestore method instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
665
666 7
        return $this;
667
    }
668
669
    /**
670
     * Callback function to be executed after save of references.
671
     *
672
     * @param ObjectManager    $manager        The object manager
673
     * @param AbstractExecutor $executor       Executor of the data fixtures
674
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
675
     *
676
     * @return WebTestCase
677
     */
678 3
    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...
679
    {
680 3
    }
681
682
    /**
683
     * Callback function to be executed before save of references.
684
     *
685
     * @param ObjectManager    $manager        The object manager
686
     * @param AbstractExecutor $executor       Executor of the data fixtures
687
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
688
     *
689
     * @return WebTestCase
690
     */
691 3
    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...
692
    {
693 3
    }
694
695
    /**
696
     * Retrieve Doctrine DataFixtures loader.
697
     *
698
     * @param ContainerInterface $container
699
     * @param array              $classNames
700
     *
701
     * @return Loader
702
     */
703 36
    protected function getFixtureLoader(ContainerInterface $container, array $classNames)
704
    {
705 36
        $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
706 36
            ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
707 36
            : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
708
                // This class is not available during tests.
709
                // @codeCoverageIgnoreStart
710
                ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
711
                // @codeCoverageIgnoreEnd
712 36
                : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
713
714 36
        $loader = new $loaderClass($container);
715
716 36
        foreach ($classNames as $className) {
717 11
            $this->loadFixtureClass($loader, $className);
718 36
        }
719
720 36
        return $loader;
721
    }
722
723
    /**
724
     * Load a data fixture class.
725
     *
726
     * @param Loader $loader
727
     * @param string $className
728
     */
729 11
    protected function loadFixtureClass($loader, $className)
730
    {
731 11
        $fixture = new $className();
732
733 11
        if ($loader->hasFixture($fixture)) {
734 2
            unset($fixture);
735
736 2
            return;
737
        }
738
739 11
        $loader->addFixture($fixture);
740
741 11
        if ($fixture instanceof DependentFixtureInterface) {
742 2
            foreach ($fixture->getDependencies() as $dependency) {
743 2
                $this->loadFixtureClass($loader, $dependency);
744 2
            }
745 2
        }
746 11
    }
747
748
    /**
749
     * Creates an instance of a lightweight Http client.
750
     *
751
     * If $authentication is set to 'true' it will use the content of
752
     * 'liip_functional_test.authentication' to log in.
753
     *
754
     * $params can be used to pass headers to the client, note that they have
755
     * to follow the naming format used in $_SERVER.
756
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
757
     *
758
     * @param bool|array $authentication
759
     * @param array      $params
760
     *
761
     * @return Client
762
     */
763 55
    protected function makeClient($authentication = false, array $params = array())
764
    {
765 55
        if ($authentication) {
766 2
            if ($authentication === true) {
767
                $authentication = array(
768 1
                    'username' => $this->getContainer()
769 1
                        ->getParameter('liip_functional_test.authentication.username'),
770 1
                    'password' => $this->getContainer()
771 1
                        ->getParameter('liip_functional_test.authentication.password'),
772 1
                );
773 1
            }
774
775 2
            $params = array_merge($params, array(
776 2
                'PHP_AUTH_USER' => $authentication['username'],
777 2
                'PHP_AUTH_PW' => $authentication['password'],
778 2
            ));
779 2
        }
780
781 55
        $client = static::createClient(array('environment' => $this->environment), $params);
782
783 55
        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...
784
            // has to be set otherwise "hasPreviousSession" in Request returns false.
785 2
            $options = $client->getContainer()->getParameter('session.storage.options');
786
787 2
            if (!$options || !isset($options['name'])) {
788
                throw new \InvalidArgumentException('Missing session.storage.options#name');
789
            }
790
791 2
            $session = $client->getContainer()->get('session');
792
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
793 2
            if ($session instanceof Session) {
794 2
                $session->setId(uniqid());
795 2
            }
796
797 2
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
798
799
            /** @var $user UserInterface */
800 2
            foreach ($this->firewallLogins as $firewallName => $user) {
801 2
                $token = $this->createUserToken($user, $firewallName);
802
803
                // BC: security.token_storage is available on Symfony 2.6+
804
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
805 2
                if ($client->getContainer()->has('security.token_storage')) {
806 2
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
807 2
                } else {
808
                    // This block will never be reached with Symfony 2.6+
809
                    // @codeCoverageIgnoreStart
810
                    $tokenStorage = $client->getContainer()->get('security.context');
811
                    // @codeCoverageIgnoreEnd
812
                }
813
814 2
                $tokenStorage->setToken($token);
815 2
                $session->set('_security_'.$firewallName, serialize($token));
816 2
            }
817
818 2
            $session->save();
819 2
        }
820
821 55
        return $client;
822
    }
823
824
    /**
825
     * Create User Token.
826
     *
827
     * Factory method for creating a User Token object for the firewall based on
828
     * the user object provided. By default it will be a Username/Password
829
     * Token based on the user's credentials, but may be overridden for custom
830
     * tokens in your applications.
831
     *
832
     * @param UserInterface $user         The user object to base the token off of
833
     * @param string        $firewallName name of the firewall provider to use
834
     *
835
     * @return TokenInterface The token to be used in the security context
836
     */
837 2
    protected function createUserToken(UserInterface $user, $firewallName)
838
    {
839 2
        return new UsernamePasswordToken(
840 2
            $user,
841 2
            null,
842 2
            $firewallName,
843 2
            $user->getRoles()
0 ignored issues
show
Documentation introduced by
$user->getRoles() is of type array<integer,object<Sym...Core\Role\Role>|string>, but the function expects a array<integer,object<Sym...\RoleInterface>|string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
844 2
        );
845
    }
846
847
    /**
848
     * Extracts the location from the given route.
849
     *
850
     * @param string $route    The name of the route
851
     * @param array  $params   Set of parameters
852
     * @param int    $absolute
853
     *
854
     * @return string
855
     */
856 1
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
857
    {
858 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
859
    }
860
861
    /**
862
     * Checks the success state of a response.
863
     *
864
     * @param Response $response Response object
865
     * @param bool     $success  to define whether the response is expected to be successful
866
     * @param string   $type
867
     */
868 6
    public function isSuccessful(Response $response, $success = true, $type = 'text/html')
869
    {
870 6
        HttpAssertions::isSuccessful($response, $success, $type);
871 5
    }
872
873
    /**
874
     * Executes a request on the given url and returns the response contents.
875
     *
876
     * This method also asserts the request was successful.
877
     *
878
     * @param string $path           path of the requested page
879
     * @param string $method         The HTTP method to use, defaults to GET
880
     * @param bool   $authentication Whether to use authentication, defaults to false
881
     * @param bool   $success        to define whether the response is expected to be successful
882
     *
883
     * @return string
884
     */
885 1
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
886
    {
887 1
        $client = $this->makeClient($authentication);
888 1
        $client->request($method, $path);
889
890 1
        $content = $client->getResponse()->getContent();
891 1
        if (is_bool($success)) {
892 1
            $this->isSuccessful($client->getResponse(), $success);
0 ignored issues
show
Bug introduced by
It seems like $client->getResponse() can be null; however, isSuccessful() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
893 1
        }
894
895 1
        return $content;
896
    }
897
898
    /**
899
     * Executes a request on the given url and returns a Crawler object.
900
     *
901
     * This method also asserts the request was successful.
902
     *
903
     * @param string $path           path of the requested page
904
     * @param string $method         The HTTP method to use, defaults to GET
905
     * @param bool   $authentication Whether to use authentication, defaults to false
906
     * @param bool   $success        Whether the response is expected to be successful
907
     *
908
     * @return Crawler
909
     */
910 1
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
911
    {
912 1
        $client = $this->makeClient($authentication);
913 1
        $crawler = $client->request($method, $path);
914
915 1
        $this->isSuccessful($client->getResponse(), $success);
0 ignored issues
show
Bug introduced by
It seems like $client->getResponse() can be null; however, isSuccessful() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
916
917 1
        return $crawler;
918
    }
919
920
    /**
921
     * @param UserInterface $user
922
     * @param string        $firewallName
923
     *
924
     * @return WebTestCase
925
     */
926 2
    public function loginAs(UserInterface $user, $firewallName)
927
    {
928 2
        $this->firewallLogins[$firewallName] = $user;
929
930 2
        return $this;
931
    }
932
933
    /**
934
     * Asserts that the HTTP response code of the last request performed by
935
     * $client matches the expected code. If not, raises an error with more
936
     * information.
937
     *
938
     * @param $expectedStatusCode
939
     * @param Client $client
940
     */
941 12
    public function assertStatusCode($expectedStatusCode, Client $client)
942
    {
943 12
        HttpAssertions::assertStatusCode($expectedStatusCode, $client);
944 9
    }
945
946
    /**
947
     * Assert that the last validation errors within $container match the
948
     * expected keys.
949
     *
950
     * @param array              $expected  A flat array of field names
951
     * @param ContainerInterface $container
952
     */
953 3
    public function assertValidationErrors(array $expected, ContainerInterface $container)
954
    {
955 3
        HttpAssertions::assertValidationErrors($expected, $container);
956 1
    }
957
958
    /**
959
     * @param array $excludedDoctrineTables
960
     */
961 1
    public function setExcludedDoctrineTables($excludedDoctrineTables)
962
    {
963 1
        $this->excludedDoctrineTables = $excludedDoctrineTables;
964 1
    }
965
}
966