/** * @test */ public function morePatternsWithNotMatchingData() { $pathInStructure = '[/\\d+/][/some-.*/][/[0-9]+/]'; $value = [0 => ['some-key' => [1 => 'value']], 'string-key' => 'value', 1 => ['some-other-key' => [1 => 'value']], 2 => ['some-key' => 'value']]; $paths = ReferenceUtils::getMatchingPaths($value, $pathInStructure); $this->assertEquals([[0, 'some-key', 1], [1, 'some-other-key', 1]], $paths); }
public function replaceForeignKeysWithReferences($entityName, $entity) { $entityInfo = $this->schemaInfo->getEntityInfo($entityName); $vpIdTable = $this->schemaInfo->getPrefixedTableName('vp_id'); foreach ($entityInfo->references as $referenceName => $targetEntity) { $targetTable = $this->schemaInfo->getEntityInfo($targetEntity)->tableName; if (isset($entity[$referenceName])) { if ($entity[$referenceName] > 0) { $referenceVpId = $this->database->get_var("SELECT HEX(vp_id) FROM {$vpIdTable} WHERE `table` = '{$targetTable}' AND id={$entity[$referenceName]}"); } else { $referenceVpId = 0; } $entity['vp_' . $referenceName] = $referenceVpId; unset($entity[$referenceName]); } } foreach ($entityInfo->valueReferences as $referenceName => $targetEntity) { list($sourceColumn, $sourceValue, $valueColumn) = array_values(ReferenceUtils::getValueReferenceDetails($referenceName)); if (isset($entity[$sourceColumn]) && $entity[$sourceColumn] == $sourceValue && isset($entity[$valueColumn])) { if ($entity[$valueColumn] == 0) { continue; } if ($targetEntity[0] === '@') { $entityNameProvider = substr($targetEntity, 1); $targetEntity = call_user_func($entityNameProvider, $entity); if (!$targetEntity) { continue; } } $targetTable = $this->schemaInfo->getEntityInfo($targetEntity)->tableName; $referenceVpId = $this->database->get_var("SELECT HEX(vp_id) FROM {$vpIdTable} WHERE `table` = '{$targetTable}' AND id=" . $entity[$valueColumn]); $entity[$valueColumn] = $referenceVpId; } } return $entity; }
private function fixMnReferences($entities) { $entityInfo = $this->dbSchema->getEntityInfo($this->entityName); $mnReferences = $entityInfo->mnReferences; $referencesToSave = $this->getExistingMnReferences($entities); $vpIdsToLoad = $this->getAllVpIdsUsedInReferences($referencesToSave); $idMap = $this->getIdsForVpIds($vpIdsToLoad); $hasAllIds = $this->idMapContainsAllVpIds($idMap, $vpIdsToLoad); if (!$hasAllIds) { return false; } foreach ($referencesToSave as $reference => $relations) { if ($entityInfo->isVirtualReference($reference)) { continue; } $referenceDetails = ReferenceUtils::getMnReferenceDetails($this->dbSchema, $this->entityName, $reference); $prefixedTable = $this->dbSchema->getPrefixedTableName($referenceDetails['junction-table']); $sourceColumn = $referenceDetails['source-column']; $targetColumn = $referenceDetails['target-column']; $valuesForInsert = array_map(function ($relation) use($idMap) { $sourceId = $idMap[$relation['vp_id']]; $targetId = $idMap[$relation['referenced_vp_id']]; return "({$sourceId}, {$targetId})"; }, $relations); $sql = sprintf("SELECT id FROM %s WHERE HEX(vp_id) IN ('%s')", $this->dbSchema->getPrefixedTableName('vp_id'), join("', '", array_map(function ($entity) { return $entity['vp_id']; }, $entities))); $processedIds = array_merge($this->database->get_col($sql), $this->deletedIds); if ($this->selectiveSynchronization) { if (count($processedIds) > 0) { $this->database->query("DELETE FROM {$prefixedTable} WHERE {$sourceColumn} IN (" . join(", ", $processedIds) . ")"); } } else { $this->database->query("TRUNCATE TABLE {$prefixedTable}"); } $valuesString = join(", ", $valuesForInsert); $insertSql = "INSERT IGNORE INTO {$prefixedTable} ({$sourceColumn}, {$targetColumn}) VALUES {$valuesString}"; $this->database->query($insertSql); } return true; }
/** * Returns true if there is any entity with reference to the passed one. * * @param $entityName * @param $entityId * @return bool */ private function existsSomeEntityWithReferenceTo($entityName, $entityId) { $entityNames = $this->dbSchemaInfo->getAllEntityNames(); foreach ($entityNames as $otherEntityName) { $otherEntityInfo = $this->dbSchemaInfo->getEntityInfo($otherEntityName); $otherEntityReferences = $otherEntityInfo->references; $otherEntityMnReferences = $otherEntityInfo->mnReferences; $otherEntityValueReferences = $otherEntityInfo->valueReferences; $allReferences = array_merge($otherEntityReferences, $otherEntityMnReferences, $otherEntityValueReferences); foreach ($allReferences as $reference => $referencedEntity) { // if the target is dynamic, check it anyway - just to be sure if ($referencedEntity !== $entityName && $referencedEntity[0] !== '@') { continue; } $otherEntityStorage = $this->storageFactory->getStorage($otherEntityName); $possiblyReferencingEntities = $otherEntityStorage->loadAll(); if (isset($otherEntityReferences[$reference])) { // 1:N reference $vpReference = "vp_{$reference}"; foreach ($possiblyReferencingEntities as $possiblyReferencingEntity) { if (isset($possiblyReferencingEntity[$vpReference])) { $referencedVpidsString = $possiblyReferencingEntity[$vpReference]; preg_match_all(IdUtil::getRegexMatchingId(), $referencedVpidsString, $matches); if (ArrayUtils::any($matches[0], Comparators::equals($entityId))) { return true; } } } } elseif (isset($otherEntityMnReferences[$reference])) { // M:N reference $vpReference = "vp_{$otherEntityName}"; foreach ($possiblyReferencingEntities as $possiblyReferencingEntity) { if (isset($possiblyReferencingEntity[$vpReference]) && array_search($entityId, $possiblyReferencingEntity[$vpReference]) !== false) { return true; } } } elseif (isset($otherEntityValueReferences[$reference])) { // Value reference list($sourceColumn, $sourceValue, $valueColumn, $pathInStructure) = array_values(ReferenceUtils::getValueReferenceDetails($reference)); foreach ($possiblyReferencingEntities as $possiblyReferencingEntity) { if (isset($possiblyReferencingEntity[$sourceColumn]) && ($possiblyReferencingEntity[$sourceColumn] === $sourceValue || ReferenceUtils::valueMatchesWildcard($sourceValue, $possiblyReferencingEntity[$sourceColumn])) && isset($possiblyReferencingEntity[$valueColumn])) { if (is_numeric($possiblyReferencingEntity[$valueColumn]) && intval($possiblyReferencingEntity[$valueColumn]) === 0 || $possiblyReferencingEntity[$valueColumn] === '') { continue; } if ($pathInStructure) { $possiblyReferencingEntity[$valueColumn] = unserialize($possiblyReferencingEntity[$valueColumn]); $paths = ReferenceUtils::getMatchingPaths($possiblyReferencingEntity[$valueColumn], $pathInStructure); } else { $paths = [[]]; // root = the value itself } /** @var Cursor[] $cursors */ $cursors = array_map(function ($path) use(&$possiblyReferencingEntity, $valueColumn) { return new Cursor($possiblyReferencingEntity[$valueColumn], $path); }, $paths); foreach ($cursors as $cursor) { $vpidsString = $cursor->getValue(); preg_match_all(IdUtil::getRegexMatchingId(), $vpidsString, $matches); if (ArrayUtils::any($matches[0], Comparators::equals($entityId))) { return true; } } } } } } } return false; }
public function getAllMnReferences() { $mnReferences = []; foreach ($this->getAllEntityNames() as $entityName) { $entityInfo = $this->getEntityInfo($entityName); if (!$entityInfo->mnReferences) { continue; } foreach ($entityInfo->mnReferences as $reference => $targetEntity) { if ($entityInfo->isVirtualReference($reference)) { continue; } $mnReferences[] = ReferenceUtils::getMnReferenceDetails($this, $entityName, $reference); } } return $mnReferences; }
public function restoreForeignKeys($entityName, $entity) { $entityInfo = $this->schemaInfo->getEntityInfo($entityName); foreach ($entityInfo->references as $referenceName => $targetEntity) { $referenceField = "vp_{$referenceName}"; if (isset($entity[$referenceField])) { if ($this->isNullReference($entity[$referenceField])) { $referencedId = 0; } else { $referencedId = $this->restoreIdsInString($entity[$referenceField]); } if (!Strings::contains($referencedId, self::UNKNOWN_VPID_MARK)) { $entity[$referenceName] = $referencedId; unset($entity[$referenceField]); } } } foreach ($entityInfo->valueReferences as $referenceName => $targetEntity) { list($sourceColumn, $sourceValue, $valueColumn, $pathInStructure) = array_values(ReferenceUtils::getValueReferenceDetails($referenceName)); if (isset($entity[$sourceColumn]) && ($entity[$sourceColumn] === $sourceValue || ReferenceUtils::valueMatchesWildcard($sourceValue, $entity[$sourceColumn])) && isset($entity[$valueColumn])) { if ($this->isNullReference($entity[$valueColumn])) { continue; } if ($pathInStructure) { $entity[$valueColumn] = unserialize($entity[$valueColumn]); $paths = ReferenceUtils::getMatchingPaths($entity[$valueColumn], $pathInStructure); } else { $paths = [[]]; // root = the value itself } /** @var Cursor[] $cursors */ $cursors = array_map(function ($path) use(&$entity, $valueColumn) { return new Cursor($entity[$valueColumn], $path); }, $paths); foreach ($cursors as $cursor) { $vpids = $cursor->getValue(); $referenceVpId = $this->restoreIdsInString($vpids); $cursor->setValue($referenceVpId); } if ($pathInStructure) { $entity[$valueColumn] = serialize($entity[$valueColumn]); } } } return $entity; }
private function maybeRestoreReference($option) { $entityInfo = $this->dbSchema->getEntityInfo('option'); foreach ($entityInfo->valueReferences as $reference => $targetEntity) { $referenceDetails = ReferenceUtils::getValueReferenceDetails($reference); if ($option[$referenceDetails['source-column']] === $referenceDetails['source-value'] && isset($option[$referenceDetails['value-column']])) { $vpid = $option[$referenceDetails['value-column']]; $vpidTable = $this->dbSchema->getPrefixedTableName('vp_id'); $targetTable = $this->dbSchema->getTableName($targetEntity); $dbId = $this->database->get_var("SELECT id FROM {$vpidTable} WHERE `table`='{$targetTable}' AND vp_id=UNHEX('{$vpid}')"); $option[$referenceDetails['value-column']] = $dbId; } } return $option; }
/** * Returns true if there is any entity with reference to the passed one. * * @param $entityName * @param $entityId * @return bool */ private function existsSomeEntityWithReferenceTo($entityName, $entityId) { $entityNames = $this->dbSchemaInfo->getAllEntityNames(); foreach ($entityNames as $otherEntityName) { $otherEntityInfo = $this->dbSchemaInfo->getEntityInfo($otherEntityName); $otherEntityReferences = $otherEntityInfo->references; $otherEntityMnReferences = $otherEntityInfo->mnReferences; $otherEntityValueReferences = $otherEntityInfo->valueReferences; $allReferences = array_merge($otherEntityReferences, $otherEntityMnReferences, $otherEntityValueReferences); $reference = array_search($entityName, $allReferences); if ($reference === false) { // Other entity is not referencing $entityName continue; } $otherEntityStorage = $this->storageFactory->getStorage($otherEntityName); $possiblyReferencingEntities = $otherEntityStorage->loadAll(); if (isset($otherEntityReferences[$reference])) { // 1:N reference $vpReference = "vp_{$reference}"; foreach ($possiblyReferencingEntities as $possiblyReferencingEntity) { if (isset($possiblyReferencingEntity[$vpReference]) && $possiblyReferencingEntity[$vpReference] === $entityId) { return true; } } } elseif (isset($otherEntityMnReferences[$reference])) { // M:N reference $vpReference = "vp_{$otherEntityName}"; foreach ($possiblyReferencingEntities as $possiblyReferencingEntity) { if (isset($possiblyReferencingEntity[$vpReference]) && array_search($entityId, $possiblyReferencingEntity[$vpReference]) !== false) { return true; } } } elseif (isset($otherEntityValueReferences[$reference])) { // Value reference list($sourceColumn, $sourceValue, $valueColumn) = array_values(ReferenceUtils::getValueReferenceDetails($reference)); foreach ($possiblyReferencingEntities as $possiblyReferencingEntity) { if (isset($possiblyReferencingEntity[$sourceColumn]) && $possiblyReferencingEntity[$sourceColumn] == $sourceValue && isset($possiblyReferencingEntity[$valueColumn]) && $possiblyReferencingEntity[$valueColumn] === $entityId) { return true; } } } } return false; }
/** * @param $entityName */ private static function assertEntitiesEqualDatabase($entityName) { $storage = self::$storageFactory->getStorage($entityName); $entityInfo = self::$schemaInfo->getEntityInfo($entityName); $allDbEntities = self::selectAll(self::$schemaInfo->getPrefixedTableName($entityName)); $idMap = self::getVpIdMap(); $allDbEntities = self::identifyEntities($entityName, $allDbEntities, $idMap); $allDbEntities = self::replaceForeignKeys($entityName, $allDbEntities, $idMap); $dbEntities = array_filter($allDbEntities, [$storage, 'shouldBeSaved']); $urlReplacer = new AbsoluteUrlReplacer(self::$testConfig->testSite->url); $storageEntities = array_map(function ($entity) use($urlReplacer) { return $urlReplacer->restore($entity); }, $storage->loadAll()); $countOfentitiesInDb = count($dbEntities); $countOfentitiesInStorage = count($storageEntities); if ($countOfentitiesInDb !== $countOfentitiesInStorage) { if ($countOfentitiesInStorage > $countOfentitiesInDb) { $problematicEntities = self::findMissingEntities($entityName, $storageEntities, $dbEntities); } else { $problematicEntities = self::findExceedingEntities($entityName, $storageEntities, $dbEntities); } throw new \PHPUnit_Framework_AssertionFailedError("Different count of synchronized entities ({$entityName}): DB = {$countOfentitiesInDb}, " . "storage = {$countOfentitiesInStorage}\nProblematic entities: " . join(", ", $problematicEntities)); } foreach ($dbEntities as $dbEntity) { $id = $dbEntity[$entityInfo->vpidColumnName]; $storageEntity = $storageEntities[$id]; $dbEntity = self::$shortcodesReplacer->replaceShortcodesInEntity($entityName, $dbEntity); foreach ($dbEntity as $column => $value) { if ($entityInfo->idColumnName === $column || isset($entityInfo->getIgnoredColumns()[$column])) { continue; } if (!isset($storageEntity[$column])) { throw new \PHPUnit_Framework_AssertionFailedError("{$entityName}[{$column}] with value = {$value}, ID = {$id} not found in storage"); } if (is_string($storageEntity[$column])) { $storageEntity[$column] = str_replace("\r\n", "\n", $storageEntity[$column]); } if (is_string($value)) { $value = str_replace("\r\n", "\n", $value); } if ($storageEntity[$column] != $value) { throw new \PHPUnit_Framework_AssertionFailedError("Different values ({$entityName}[{$column}]: {$id}): DB = {$value}, storage = {$storageEntity[$column]}"); } } } $missingReferences = []; $exceedingReferences = []; foreach ($entityInfo->mnReferences as $reference => $targetEntity) { if ($entityInfo->isVirtualReference($reference)) { continue; } $referenceDetails = ReferenceUtils::getMnReferenceDetails(self::$schemaInfo, $entityName, $reference); $sourceColumn = $referenceDetails['source-column']; $targetColumn = $referenceDetails['target-column']; $junctionTable = $referenceDetails['junction-table']; $prefixedJunctionTable = self::$schemaInfo->getPrefixedTableName($junctionTable); $prefixedVpIdTable = self::$schemaInfo->getPrefixedTableName('vp_id'); $sourceTable = self::$schemaInfo->getTableName($referenceDetails['source-entity']); $targetTable = self::$schemaInfo->getTableName($referenceDetails['target-entity']); $junctionTableContent = self::fetchAll("SELECT HEX(s_vp_id.vp_id), HEX(t_vp_id.vp_id) FROM {$prefixedJunctionTable} j\n JOIN {$prefixedVpIdTable} s_vp_id ON j.{$sourceColumn} = s_vp_id.id AND s_vp_id.`table`='{$sourceTable}'\n JOIN {$prefixedVpIdTable} t_vp_id ON j.{$targetColumn} = t_vp_id.id AND t_vp_id.`table` = '{$targetTable}'", MYSQLI_NUM); $checkedReferences = []; $missingReferences[$junctionTable] = []; foreach ($storageEntities as $storageEntity) { if (!isset($storageEntity["vp_{$targetEntity}"])) { continue; } foreach ($storageEntity["vp_{$targetEntity}"] as $referenceVpId) { if (!ArrayUtils::any($junctionTableContent, function ($junctionRow) use($storageEntity, $referenceVpId) { return $junctionRow[0] === $storageEntity['vp_id'] && $junctionRow[1] === $referenceVpId; })) { $missingReferences[$junctionTable][] = [$sourceColumn => $storageEntity['vp_id'], $targetColumn => $referenceVpId]; } $checkedReferences[] = [$storageEntity['vp_id'], $referenceVpId]; } } $exceedingReferences[$junctionTable] = array_map(function ($pair) use($sourceColumn, $targetColumn) { return [$sourceColumn => $pair[0], $targetColumn => $pair[1]]; }, array_filter($junctionTableContent, function ($pair) use($checkedReferences) { foreach ($checkedReferences as $reference) { if ($reference[0] === $pair[0] && $reference[1] === $pair[1]) { return false; } } return true; })); } self::reportResultOfMnReferenceCheck($missingReferences, "Missing"); self::reportResultOfMnReferenceCheck($exceedingReferences, "Exceeding"); }