These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 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\Utils; |
||
| 13 | |||
| 14 | use Symfony\Component\DependencyInjection\ContainerInterface; |
||
| 15 | use Symfony\Bridge\Doctrine\ManagerRegistry; |
||
| 16 | use Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader; |
||
| 17 | use Doctrine\Common\Persistence\ObjectManager; |
||
| 18 | use Doctrine\Common\DataFixtures\DependentFixtureInterface; |
||
| 19 | use Doctrine\Common\DataFixtures\Executor\AbstractExecutor; |
||
| 20 | use Doctrine\Common\DataFixtures\ProxyReferenceRepository; |
||
| 21 | use Doctrine\DBAL\Connection; |
||
| 22 | use Doctrine\DBAL\Driver\PDOSqlite\Driver as SqliteDriver; |
||
| 23 | use Doctrine\DBAL\Platforms\MySqlPlatform; |
||
| 24 | use Doctrine\ORM\EntityManager; |
||
| 25 | use Doctrine\ORM\Tools\SchemaTool; |
||
| 26 | use Nelmio\Alice\Fixtures; |
||
| 27 | |||
| 28 | class FixturesLoader |
||
| 29 | { |
||
| 30 | /** @var \Symfony\Component\DependencyInjection\ContainerInterface $container */ |
||
| 31 | private $container; |
||
| 32 | |||
| 33 | /** |
||
| 34 | * @var array |
||
| 35 | */ |
||
| 36 | private static $cachedMetadatas = array(); |
||
| 37 | |||
| 38 | public function __construct(ContainerInterface $container) |
||
| 39 | { |
||
| 40 | $this->container = $container; |
||
| 41 | } |
||
| 42 | |||
| 43 | /** |
||
| 44 | * @param string $type |
||
| 45 | * |
||
| 46 | * @return string |
||
| 47 | */ |
||
| 48 | private function getExecutorClass($type) |
||
| 49 | { |
||
| 50 | return 'PHPCR' === $type && class_exists('Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor') |
||
| 51 | ? 'Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor' |
||
| 52 | : 'Doctrine\\Common\\DataFixtures\\Executor\\'.$type.'Executor'; |
||
| 53 | } |
||
| 54 | |||
| 55 | /** |
||
| 56 | * Get file path of the SQLite database. |
||
| 57 | * |
||
| 58 | * @param Connection $connection |
||
| 59 | * |
||
| 60 | * @return string $name |
||
| 61 | */ |
||
| 62 | private function getNameParameter(Connection $connection) |
||
| 63 | { |
||
| 64 | $params = $connection->getParams(); |
||
| 65 | |||
| 66 | if (isset($params['master'])) { |
||
| 67 | $params = $params['master']; |
||
| 68 | } |
||
| 69 | |||
| 70 | $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false); |
||
| 71 | |||
| 72 | if (!$name) { |
||
| 73 | throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped."); |
||
| 74 | } |
||
| 75 | |||
| 76 | return $name; |
||
| 77 | } |
||
| 78 | |||
| 79 | /** |
||
| 80 | * Purge SQLite database. |
||
| 81 | * |
||
| 82 | * @param ObjectManager $om |
||
| 83 | * @param string $omName The name of object manager to use |
||
| 84 | */ |
||
| 85 | private function getCachedMetadatas(ObjectManager $om, $omName) |
||
| 86 | { |
||
| 87 | if (!isset(self::$cachedMetadatas[$omName])) { |
||
| 88 | self::$cachedMetadatas[$omName] = $om->getMetadataFactory()->getAllMetadata(); |
||
| 89 | usort(self::$cachedMetadatas[$omName], function ($a, $b) { return strcmp($a->name, $b->name); }); |
||
| 90 | } |
||
| 91 | |||
| 92 | return self::$cachedMetadatas[$omName]; |
||
| 93 | } |
||
| 94 | |||
| 95 | /** |
||
| 96 | * This function finds the time when the data blocks of a class definition |
||
| 97 | * file were being written to, that is, the time when the content of the |
||
| 98 | * file was changed. |
||
| 99 | * |
||
| 100 | * @param string $class The fully qualified class name of the fixture class to |
||
| 101 | * check modification date on. |
||
| 102 | * |
||
| 103 | * @return \DateTime|null |
||
| 104 | */ |
||
| 105 | protected function getFixtureLastModified($class) |
||
| 106 | { |
||
| 107 | $lastModifiedDateTime = null; |
||
| 108 | |||
| 109 | $reflClass = new \ReflectionClass($class); |
||
| 110 | $classFileName = $reflClass->getFileName(); |
||
| 111 | |||
| 112 | if (file_exists($classFileName)) { |
||
| 113 | $lastModifiedDateTime = new \DateTime(); |
||
| 114 | $lastModifiedDateTime->setTimestamp(filemtime($classFileName)); |
||
| 115 | } |
||
| 116 | |||
| 117 | return $lastModifiedDateTime; |
||
| 118 | } |
||
| 119 | |||
| 120 | /** |
||
| 121 | * Determine if the Fixtures that define a database backup have been |
||
| 122 | * modified since the backup was made. |
||
| 123 | * |
||
| 124 | * @param array $classNames The fixture classnames to check |
||
| 125 | * @param string $backup The fixture backup SQLite database file path |
||
| 126 | * |
||
| 127 | * @return bool TRUE if the backup was made since the modifications to the |
||
| 128 | * fixtures; FALSE otherwise |
||
| 129 | */ |
||
| 130 | protected function isBackupUpToDate(array $classNames, $backup) |
||
| 131 | { |
||
| 132 | $backupLastModifiedDateTime = new \DateTime(); |
||
| 133 | $backupLastModifiedDateTime->setTimestamp(filemtime($backup)); |
||
| 134 | |||
| 135 | foreach ($classNames as &$className) { |
||
| 136 | $fixtureLastModifiedDateTime = $this->getFixtureLastModified($className); |
||
| 137 | if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) { |
||
| 138 | return false; |
||
| 139 | } |
||
| 140 | } |
||
| 141 | |||
| 142 | return true; |
||
| 143 | } |
||
| 144 | |||
| 145 | /** |
||
| 146 | * Copy SQLite backup file. |
||
| 147 | * |
||
| 148 | * @param ObjectManager $om |
||
| 149 | * @param string $executorClass |
||
| 150 | * @param ProxyReferenceRepository $referenceRepository |
||
| 151 | * @param string $backup Path of the source file. |
||
| 152 | * @param string $name Path of the destination file. |
||
| 153 | */ |
||
| 154 | private function copySqliteBackup($om, $executorClass, |
||
| 155 | $referenceRepository, $backup, $name) |
||
| 156 | { |
||
| 157 | $om->flush(); |
||
| 158 | $om->clear(); |
||
| 159 | |||
| 160 | $this->preFixtureRestore($om, $referenceRepository); |
||
| 161 | |||
| 162 | copy($backup, $name); |
||
| 163 | |||
| 164 | $executor = new $executorClass($om); |
||
| 165 | $executor->setReferenceRepository($referenceRepository); |
||
| 166 | $executor->getReferenceRepository()->load($backup); |
||
| 167 | |||
| 168 | $this->postFixtureRestore(); |
||
| 169 | |||
| 170 | return $executor; |
||
| 171 | } |
||
| 172 | |||
| 173 | /** |
||
| 174 | * Purge database. |
||
| 175 | * |
||
| 176 | * @param ObjectManager $om |
||
| 177 | * @param string $type |
||
| 178 | * @param int $purgeMode |
||
| 179 | * @param string $executorClass |
||
| 180 | * @param ProxyReferenceRepository $referenceRepository |
||
| 181 | */ |
||
| 182 | private function purgeDatabase(ObjectManager $om, $type, $purgeMode, |
||
| 183 | $executorClass, |
||
| 184 | ProxyReferenceRepository $referenceRepository) |
||
| 185 | { |
||
| 186 | $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger'; |
||
| 187 | if ('PHPCR' === $type) { |
||
| 188 | $purger = new $purgerClass($om); |
||
| 189 | $initManager = $this->container->has('doctrine_phpcr.initializer_manager') |
||
| 190 | ? $this->container->get('doctrine_phpcr.initializer_manager') |
||
| 191 | : null; |
||
| 192 | |||
| 193 | $executor = new $executorClass($om, $purger, $initManager); |
||
| 194 | } else { |
||
| 195 | $purger = new $purgerClass(); |
||
| 196 | if (null !== $purgeMode) { |
||
| 197 | $purger->setPurgeMode($purgeMode); |
||
| 198 | } |
||
| 199 | |||
| 200 | $executor = new $executorClass($om, $purger); |
||
| 201 | } |
||
| 202 | |||
| 203 | $executor->setReferenceRepository($referenceRepository); |
||
| 204 | $executor->purge(); |
||
| 205 | |||
| 206 | return $executor; |
||
| 207 | } |
||
| 208 | |||
| 209 | /** |
||
| 210 | * Purge database. |
||
| 211 | * |
||
| 212 | * @param ObjectManager $om |
||
| 213 | * @param array $metadatas |
||
| 214 | * @param string $executorClass |
||
| 215 | * @param ProxyReferenceRepository $referenceRepository |
||
| 216 | */ |
||
| 217 | private function createSqliteSchema(ObjectManager $om, |
||
| 218 | $metadatas, $executorClass, |
||
| 219 | ProxyReferenceRepository $referenceRepository) |
||
| 220 | { |
||
| 221 | // TODO: handle case when using persistent connections. Fail loudly? |
||
| 222 | $schemaTool = new SchemaTool($om); |
||
|
0 ignored issues
–
show
|
|||
| 223 | $schemaTool->dropDatabase(); |
||
| 224 | if (!empty($metadatas)) { |
||
| 225 | $schemaTool->createSchema($metadatas); |
||
| 226 | } |
||
| 227 | $this->postFixtureSetup(); |
||
| 228 | |||
| 229 | $executor = new $executorClass($om); |
||
| 230 | $executor->setReferenceRepository($referenceRepository); |
||
| 231 | } |
||
| 232 | |||
| 233 | /** |
||
| 234 | * Set the database to the provided fixtures. |
||
| 235 | * |
||
| 236 | * Drops the current database and then loads fixtures using the specified |
||
| 237 | * classes. The parameter is a list of fully qualified class names of |
||
| 238 | * classes that implement Doctrine\Common\DataFixtures\FixtureInterface |
||
| 239 | * so that they can be loaded by the DataFixtures Loader::addFixture |
||
| 240 | * |
||
| 241 | * When using SQLite this method will automatically make a copy of the |
||
| 242 | * loaded schema and fixtures which will be restored automatically in |
||
| 243 | * case the same fixture classes are to be loaded again. Caveat: changes |
||
| 244 | * to references and/or identities may go undetected. |
||
| 245 | * |
||
| 246 | * Depends on the doctrine data-fixtures library being available in the |
||
| 247 | * class path. |
||
| 248 | * |
||
| 249 | * @param array $classNames List of fully qualified class names of fixtures to load |
||
| 250 | * @param string $omName The name of object manager to use |
||
| 251 | * @param string $registryName The service id of manager registry to use |
||
| 252 | * @param int $purgeMode Sets the ORM purge mode |
||
| 253 | * |
||
| 254 | * @return null|AbstractExecutor |
||
| 255 | */ |
||
| 256 | public function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null) |
||
| 257 | { |
||
| 258 | /** @var ManagerRegistry $registry */ |
||
| 259 | $registry = $this->container->get($registryName); |
||
| 260 | $om = $registry->getManager($omName); |
||
| 261 | $type = $registry->getName(); |
||
| 262 | |||
| 263 | $executorClass = $this->getExecutorClass($type); |
||
| 264 | $referenceRepository = new ProxyReferenceRepository($om); |
||
| 265 | |||
| 266 | $cacheDriver = $om->getMetadataFactory()->getCacheDriver(); |
||
| 267 | |||
| 268 | if ($cacheDriver) { |
||
| 269 | $cacheDriver->deleteAll(); |
||
| 270 | } |
||
| 271 | |||
| 272 | if ('ORM' === $type) { |
||
| 273 | $connection = $om->getConnection(); |
||
| 274 | if ($connection->getDriver() instanceof SqliteDriver) { |
||
| 275 | $name = $this->getNameParameter($connection); |
||
| 276 | $metadatas = $this->getCachedMetadatas($om, $omName); |
||
| 277 | |||
| 278 | if ($this->container->getParameter('liip_functional_test.cache_sqlite_db')) { |
||
| 279 | $backup = $this->container->getParameter('kernel.cache_dir').'/test_'.md5(serialize($metadatas).serialize($classNames)).'.db'; |
||
| 280 | if (file_exists($backup) && file_exists($backup.'.ser') && $this->isBackupUpToDate($classNames, $backup)) { |
||
| 281 | $executor = $this->copySqliteBackup($om, |
||
| 282 | $executorClass, $referenceRepository, |
||
| 283 | $backup, $name); |
||
| 284 | |||
| 285 | return $executor; |
||
| 286 | } |
||
| 287 | } |
||
| 288 | |||
| 289 | $this->createSqliteSchema($om, $metadatas, |
||
| 290 | $executorClass, $referenceRepository); |
||
| 291 | } |
||
| 292 | } |
||
| 293 | |||
| 294 | if (empty($executor)) { |
||
|
0 ignored issues
–
show
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 This is likely the result of code being shifted around. Consider removing these calls. Loading history...
|
|||
| 295 | $executor = $this->purgeDatabase($om, $type, $purgeMode, |
||
| 296 | $executorClass, $referenceRepository); |
||
| 297 | } |
||
| 298 | |||
| 299 | $loader = $this->getFixtureLoader($classNames); |
||
| 300 | |||
| 301 | $executor->execute($loader->getFixtures(), true); |
||
| 302 | |||
| 303 | if (isset($name) && isset($backup)) { |
||
| 304 | $this->preReferenceSave($om, $executor, $backup); |
||
| 305 | |||
| 306 | $executor->getReferenceRepository()->save($backup); |
||
| 307 | copy($name, $backup); |
||
| 308 | |||
| 309 | $this->postReferenceSave($om, $executor, $backup); |
||
| 310 | } |
||
| 311 | |||
| 312 | return $executor; |
||
| 313 | } |
||
| 314 | |||
| 315 | /** |
||
| 316 | * Clean database. |
||
| 317 | * |
||
| 318 | * @param ManagerRegistry $registry |
||
| 319 | * @param EntityManager $om |
||
| 320 | */ |
||
| 321 | private function cleanDatabase(ManagerRegistry $registry, EntityManager $om) |
||
| 322 | { |
||
| 323 | $connection = $om->getConnection(); |
||
| 324 | |||
| 325 | $mysql = ($registry->getName() === 'ORM' |
||
| 326 | && $connection->getDatabasePlatform() instanceof MySqlPlatform); |
||
| 327 | |||
| 328 | if ($mysql) { |
||
| 329 | $connection->query('SET FOREIGN_KEY_CHECKS=0'); |
||
| 330 | } |
||
| 331 | |||
| 332 | $this->loadFixtures(array()); |
||
| 333 | |||
| 334 | if ($mysql) { |
||
| 335 | $connection->query('SET FOREIGN_KEY_CHECKS=1'); |
||
| 336 | } |
||
| 337 | } |
||
| 338 | |||
| 339 | /** |
||
| 340 | * Locate fixture files. |
||
| 341 | * |
||
| 342 | * @param array $paths |
||
| 343 | * |
||
| 344 | * @return array $files |
||
| 345 | */ |
||
| 346 | private function locateResources($paths) |
||
| 347 | { |
||
| 348 | $files = array(); |
||
| 349 | |||
| 350 | $kernel = $this->container->get('kernel'); |
||
| 351 | |||
| 352 | foreach ($paths as $path) { |
||
| 353 | if ($path[0] !== '@' && file_exists($path) === true) { |
||
| 354 | $files[] = $path; |
||
| 355 | continue; |
||
| 356 | } |
||
| 357 | |||
| 358 | $files[] = $kernel->locateResource($path); |
||
| 359 | } |
||
| 360 | |||
| 361 | return $files; |
||
| 362 | } |
||
| 363 | |||
| 364 | /** |
||
| 365 | * @param array $paths Either symfony resource locators (@ BundleName/etc) or actual file paths |
||
| 366 | * @param bool $append |
||
| 367 | * @param null $omName |
||
| 368 | * @param string $registryName |
||
| 369 | * |
||
| 370 | * @return array |
||
| 371 | * |
||
| 372 | * @throws \BadMethodCallException |
||
| 373 | */ |
||
| 374 | public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine') |
||
| 375 | { |
||
| 376 | if (!class_exists('Nelmio\Alice\Fixtures')) { |
||
| 377 | throw new \BadMethodCallException('nelmio/alice should be installed to use this method.'); |
||
| 378 | } |
||
| 379 | |||
| 380 | /** @var ManagerRegistry $registry */ |
||
| 381 | $registry = $this->container->get($registryName); |
||
| 382 | /** @var EntityManager $om */ |
||
| 383 | $om = $registry->getManager($omName); |
||
| 384 | |||
| 385 | if ($append === false) { |
||
| 386 | $this->cleanDatabase($registry, $om); |
||
| 387 | } |
||
| 388 | |||
| 389 | $files = $this->locateResources($paths); |
||
| 390 | |||
| 391 | return Fixtures::load($files, $om); |
||
| 392 | } |
||
| 393 | |||
| 394 | /** |
||
| 395 | * Retrieve Doctrine DataFixtures loader. |
||
| 396 | * |
||
| 397 | * @param array $classNames |
||
| 398 | * |
||
| 399 | * @return Loader |
||
| 400 | */ |
||
| 401 | protected function getFixtureLoader(array $classNames) |
||
| 402 | { |
||
| 403 | $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader') |
||
| 404 | ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader' |
||
| 405 | : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader') |
||
| 406 | ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader' |
||
| 407 | : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader'); |
||
| 408 | |||
| 409 | $loader = new $loaderClass($this->container); |
||
| 410 | |||
| 411 | foreach ($classNames as $className) { |
||
| 412 | $this->loadFixtureClass($loader, $className); |
||
| 413 | } |
||
| 414 | |||
| 415 | return $loader; |
||
| 416 | } |
||
| 417 | |||
| 418 | /** |
||
| 419 | * Load a data fixture class. |
||
| 420 | * |
||
| 421 | * @param Loader $loader |
||
| 422 | * @param string $className |
||
| 423 | */ |
||
| 424 | protected function loadFixtureClass($loader, $className) |
||
| 425 | { |
||
| 426 | $fixture = new $className(); |
||
| 427 | |||
| 428 | if ($loader->hasFixture($fixture)) { |
||
| 429 | unset($fixture); |
||
| 430 | |||
| 431 | return; |
||
| 432 | } |
||
| 433 | |||
| 434 | $loader->addFixture($fixture); |
||
| 435 | |||
| 436 | if ($fixture instanceof DependentFixtureInterface) { |
||
| 437 | foreach ($fixture->getDependencies() as $dependency) { |
||
| 438 | $this->loadFixtureClass($loader, $dependency); |
||
| 439 | } |
||
| 440 | } |
||
| 441 | } |
||
| 442 | |||
| 443 | /** |
||
| 444 | * Callback function to be executed after Schema creation. |
||
| 445 | * Use this to execute acl:init or other things necessary. |
||
| 446 | */ |
||
| 447 | protected function postFixtureSetup() |
||
| 448 | { |
||
| 449 | } |
||
| 450 | |||
| 451 | /** |
||
| 452 | * Callback function to be executed after Schema restore. |
||
| 453 | * |
||
| 454 | * @return WebTestCase |
||
| 455 | */ |
||
| 456 | protected function postFixtureRestore() |
||
| 457 | { |
||
| 458 | } |
||
| 459 | |||
| 460 | /** |
||
| 461 | * Callback function to be executed before Schema restore. |
||
| 462 | * |
||
| 463 | * @param ObjectManager $manager The object manager |
||
| 464 | * @param ProxyReferenceRepository $referenceRepository The reference repository |
||
| 465 | * |
||
| 466 | * @return WebTestCase |
||
| 467 | */ |
||
| 468 | protected function preFixtureRestore(ObjectManager $manager, ProxyReferenceRepository $referenceRepository) |
||
|
0 ignored issues
–
show
|
|||
| 469 | { |
||
| 470 | } |
||
| 471 | |||
| 472 | /** |
||
| 473 | * Callback function to be executed after save of references. |
||
| 474 | * |
||
| 475 | * @param ObjectManager $manager The object manager |
||
| 476 | * @param AbstractExecutor $executor Executor of the data fixtures |
||
| 477 | * @param string $backupFilePath Path of file used to backup the references of the data fixtures |
||
| 478 | * |
||
| 479 | * @return WebTestCase |
||
| 480 | */ |
||
| 481 | protected function postReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath) |
||
|
0 ignored issues
–
show
|
|||
| 482 | { |
||
| 483 | } |
||
| 484 | |||
| 485 | /** |
||
| 486 | * Callback function to be executed before save of references. |
||
| 487 | * |
||
| 488 | * @param ObjectManager $manager The object manager |
||
| 489 | * @param AbstractExecutor $executor Executor of the data fixtures |
||
| 490 | * @param string $backupFilePath Path of file used to backup the references of the data fixtures |
||
| 491 | * |
||
| 492 | * @return WebTestCase |
||
| 493 | */ |
||
| 494 | protected function preReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath) |
||
|
0 ignored issues
–
show
|
|||
| 495 | { |
||
| 496 | } |
||
| 497 | } |
||
| 498 |
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.