diff --git a/application/Espo/ORM/DB/BaseMapper.php b/application/Espo/ORM/DB/BaseMapper.php new file mode 100644 index 0000000000..999e1d6bc9 --- /dev/null +++ b/application/Espo/ORM/DB/BaseMapper.php @@ -0,0 +1,1215 @@ +pdo = $pdo; + $this->query = $query; + $this->entityFactory = $entityFactory; + $this->metadata = $metadata; + } + + public function selectById(Entity $entity, $id, ?array $params = null) : ?Entity + { + $params = $params ?? []; + + if (!array_key_exists('whereClause', $params)) { + $params['whereClause'] = []; + } + + $params['whereClause']['id'] = $id; + + $sql = $this->query->createSelectQuery($entity->getEntityType(), $params); + + $ps = $this->pdo->query($sql); + + if ($ps) { + foreach ($ps as $row) { + $entity = $this->fromRow($entity, $row); + $entity->setAsFetched(); + return $entity; + } + } + return null; + } + + public function count(Entity $entity, ?array $params = null) : int + { + return (int) $this->aggregate($entity, $params, 'COUNT', 'id'); + } + + public function max(Entity $entity, ?array $params, string $attribute) + { + return $this->aggregate($entity, $params, 'MAX', $attribute); + } + + public function min(Entity $entity, ?array $params, string $attribute) + { + return $this->aggregate($entity, $params, 'MIN', $attribute); + } + + public function sum(Entity $entity, ?array $params, string $attribute) + { + return $this->aggregate($entity, $params, 'SUM', $attribute); + } + + public function select(Entity $entity, ?array $params = null) : Collection + { + $sql = $this->query->createSelectQuery($entity->getEntityType(), $params); + + return $this->selectByQuery($entity, $sql, $params); + } + + public function selectByQuery(Entity $entity, $sql, ?array $params = null) : Collection + { + $params = $params ?? []; + + if ($params['returnSthCollection'] ?? false) { + $collection = $this->createSthCollection($entity->getEntityType()); + $collection->setQuery($sql); + $collection->setAsFetched(); + + return $collection; + } + + $dataList = []; + $ps = $this->pdo->query($sql); + if ($ps) { + $dataList = $ps->fetchAll(); + } + + $collection = $this->createCollection($entity->getEntityType(), $dataList); + $collection->setAsFetched(); + + return $collection; + } + + protected function createCollection(string $entityType, ?array $dataList = []) + { + return new $this->collectionClass($dataList, $entityType, $this->entityFactory); + } + + protected function createSthCollection(string $entityType) + { + return new $this->sthCollectionClass($entityType, $this->entityFactory, $this->query, $this->pdo);; + } + + public function aggregate(Entity $entity, ?array $params, string $aggregation, string $aggregationBy) + { + if (empty($aggregation) || !$entity->hasAttribute($aggregationBy)) { + return null; + } + + $params = $params ?? []; + + $params['aggregation'] = $aggregation; + $params['aggregationBy'] = $aggregationBy; + + $sql = $this->query->createSelectQuery($entity->getEntityType(), $params); + + $ps = $this->pdo->query($sql); + + if ($ps) { + foreach ($ps as $row) { + return $row['AggregateValue']; + } + } + return null; + } + + public function selectRelated(Entity $entity, string $relationName, array $params = [], bool $returnTotalCount = false) + { + $relDefs = $entity->relations[$relationName]; + + if (!isset($relDefs['type'])) { + throw new \LogicException( + "Missing 'type' in definition for relationship {$relationName} in " . $entity->getEntityType() . " entity" + ); + } + + if ($relDefs['type'] !== Entity::BELONGS_TO_PARENT) { + if (!isset($relDefs['entity'])) { + throw new \LogicException( + "Missing 'entity' in definition for relationship {$relationName} in " . $entity->getEntityType() . " entity" + ); + } + + $relEntityName = (!empty($relDefs['class'])) ? $relDefs['class'] : $relDefs['entity']; + $relEntity = $this->entityFactory->create($relEntityName); + + if (!$relEntity) { + return null; + } + } + + if ($returnTotalCount) { + $params['aggregation'] = 'COUNT'; + $params['aggregationBy'] = 'id'; + } + + if (empty($params['whereClause'])) { + $params['whereClause'] = []; + } + + $relType = $relDefs['type']; + + $keySet = $this->query->getKeys($entity, $relationName); + + $key = $keySet['key']; + $foreignKey = $keySet['foreignKey']; + + switch ($relType) { + case Entity::BELONGS_TO: + $params['whereClause'][$foreignKey] = $entity->get($key); + $params['offset'] = 0; + $params['limit'] = 1; + + $sql = $this->query->createSelectQuery($relEntity->getEntityType(), $params); + + $ps = $this->pdo->query($sql); + + if ($ps) { + foreach ($ps as $row) { + if (!$returnTotalCount) { + $relEntity = $this->fromRow($relEntity, $row); + $relEntity->setAsFetched(); + return $relEntity; + } else { + return $row['AggregateValue']; + } + } + } + return null; + + case Entity::HAS_MANY: + case Entity::HAS_CHILDREN: + case Entity::HAS_ONE: + $params['whereClause'][$foreignKey] = $entity->get($key); + + if ($relType == Entity::HAS_CHILDREN) { + $foreignType = $keySet['foreignType']; + $params['whereClause'][$foreignType] = $entity->getEntityType(); + } + + if ($relType == Entity::HAS_ONE) { + $params['offset'] = 0; + $params['limit'] = 1; + } + + if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { + $params['whereClause'][] = $relDefs['conditions']; + } + + $resultDataList = []; + + $sql = $this->query->createSelectQuery($relEntity->getEntityType(), $params); + + if (!$returnTotalCount) { + if (!empty($params['returnSthCollection']) && $relType !== Entity::HAS_ONE) { + $collection = $this->createSthCollection($relEntity->getEntityType()); + $collection->setQuery($sql); + $collection->setAsFetched(); + return $collection; + } + } + + $ps = $this->pdo->query($sql); + if ($ps) { + if (!$returnTotalCount) { + $resultDataList = $ps->fetchAll(); + } else { + foreach ($ps as $row) { + return $row['AggregateValue']; + } + } + } + + if ($relType == Entity::HAS_ONE) { + if (count($resultDataList)) { + $relEntity = $this->fromRow($relEntity, $resultDataList[0]); + $relEntity->setAsFetched(); + + return $relEntity; + } + return null; + } else { + $collection = $this->createCollection($relEntity->getEntityType(), $resultDataList); + $collection->setAsFetched(); + + return $collection; + } + + case Entity::MANY_MANY: + $additionalColumnsConditions = null; + if (!empty($params['additionalColumnsConditions'])) { + $additionalColumnsConditions = $params['additionalColumnsConditions']; + } + + $MMJoinPart = $this->getMMJoin($entity, $relationName, $keySet, $additionalColumnsConditions); + + if (empty($params['customJoin'])) { + $params['customJoin'] = ''; + } else { + $params['customJoin'] .= ' '; + } + $params['customJoin'] .= $MMJoinPart; + + $params['relationName'] = $relDefs['relationName']; + + $sql = $this->query->createSelectQuery($relEntity->getEntityType(), $params); + + $resultDataList = []; + + if (!$returnTotalCount) { + if (!empty($params['returnSthCollection'])) { + $collection = $this->createSthCollection($relEntity->getEntityType()); + $collection->setQuery($sql); + $collection->setAsFetched(); + return $collection; + } + } + + $ps = $this->pdo->query($sql); + + if ($ps) { + if (!$returnTotalCount) { + $resultDataList = $ps->fetchAll(); + } else { + foreach ($ps as $row) { + return $row['AggregateValue']; + } + } + } + + $collection = $this->createCollection($relEntity->getEntityType(), $resultDataList); + $collection->setAsFetched(); + + return $collection; + + case Entity::BELONGS_TO_PARENT: + $foreignEntityType = $entity->get($keySet['typeKey']); + $foreignEntityId = $entity->get($key); + if (!$foreignEntityType || !$foreignEntityId) { + return null; + } + $params['whereClause'][$foreignKey] = $foreignEntityId; + $params['offset'] = 0; + $params['limit'] = 1; + + $relEntity = $this->entityFactory->create($foreignEntityType); + + $sql = $this->query->createSelectQuery($foreignEntityType, $params); + + $ps = $this->pdo->query($sql); + + if ($ps) { + foreach ($ps as $row) { + if (!$returnTotalCount) { + $relEntity = $this->fromRow($relEntity, $row); + return $relEntity; + } else { + return $row['AggregateValue']; + } + } + } + return null; + } + + return false; + } + + public function countRelated(Entity $entity, string $relationName, array $params) : int + { + return (int) $this->selectRelated($entity, $relationName, $params, true); + } + + public function relate(Entity $entityFrom, string $relationName, Entity $entityTo, ?array $data = null) + { + return $this->addRelation($entityFrom, $relationName, null, $entityTo, $data); + } + + public function unrelate(Entity $entityFrom, string $relationName, Entity $entityTo) + { + return $this->removeRelation($entityFrom, $relationName, null, false, $entityTo); + } + + public function updateRelation(Entity $entity, string $relationName, ?string $id = null, array $columnData) : bool + { + if (empty($id) || empty($relationName)) { + return false; + } + + if (empty($columnData)) return false; + + $relDefs = $entity->relations[$relationName]; + $keySet = $this->query->getKeys($entity, $relationName); + + $relType = $relDefs['type']; + + switch ($relType) { + case Entity::MANY_MANY: + $relTable = $this->toDb($relDefs['relationName']); + $key = $keySet['key']; + $foreignKey = $keySet['foreignKey']; + $nearKey = $keySet['nearKey']; + $distantKey = $keySet['distantKey']; + + $setArr = []; + foreach ($columnData as $column => $value) { + $setArr[] = "`".$this->toDb($column) . "` = " . $this->quote($value); + } + if (empty($setArr)) { + return true; + } + $setPart = implode(', ', $setArr); + + $wherePart = + $this->toDb($nearKey) . " = " . $this->pdo->quote($entity->id) . " + AND " . $this->toDb($distantKey) . " = " . $this->pdo->quote($id) . " AND deleted = 0 + "; + + if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { + foreach ($relDefs['conditions'] as $f => $v) { + $wherePart .= " AND " . $this->toDb($f) . " = " . $this->pdo->quote($v); + } + } + + $sql = $this->composeUpdateQuery($relTable, $setPart, $wherePart); + + if ($this->pdo->query($sql)) { + return true; + } + } + + return false; + } + + public function getRelationColumn(Entity $entity, string $relationName, string $id, string $column) + { + $type = $entity->getRelationType($entityType, $relationName); + + if (!$type === Entity::MANY_MANY) return null; + + $relDefs = $entity->relations[$relationName]; + + $relTable = $this->toDb($relDefs['relationName']); + + $keySet = $this->query->getKeys($entity, $relationName); + $key = $keySet['key']; + $foreignKey = $keySet['foreignKey']; + $nearKey = $keySet['nearKey']; + $distantKey = $keySet['distantKey']; + + $additionalColumns = $entity->getRelationParam($relationName, 'additionalColumns') ?? []; + + if (!isset($additionalColumns[$column])) return null; + + $columnType = $additionalColumns[$column]['type'] ?? 'string'; + + $columnAlias = $this->query->sanitizeSelectAlias($column); + + $sql = + "SELECT " . $this->toDb($this->query->sanitize($column)) . " AS `{$columnAlias}` FROM `{$relTable}` " . + "WHERE "; + + $wherePart = + $this->toDb($nearKey) . " = " . $this->pdo->quote($entity->id) . " ". + "AND " . $this->toDb($distantKey) . " = " . $this->pdo->quote($id) . " AND deleted = 0"; + + $sql .= $wherePart; + + $ps = $this->pdo->query($sql); + if ($ps) { + foreach ($ps as $row) { + $value = $row[$columnAlias]; + if ($columnType == 'bool') { + $value = boolval($value); + } else if ($columnType == 'int') { + $value = intval($value); + } else if ($columnType == 'float') { + $value = floatval($value); + } + + return $value; + } + } + + return null; + } + + public function massRelate(Entity $entity, $relationName, array $params = []) + { + if (!$entity) { + return false; + } + $id = $entity->id; + + if (empty($id) || empty($relationName)) { + return false; + } + + $relDefs = $entity->relations[$relationName]; + + if (!isset($relDefs['entity']) || !isset($relDefs['type'])) { + throw new \LogicException("Not appropriate definition for relationship {$relationName} in " . $entity->getEntityType() . " entity"); + } + + $relType = $relDefs['type']; + + $className = (!empty($relDefs['class'])) ? $relDefs['class'] : $relDefs['entity']; + $relEntity = $this->entityFactory->create($className); + $foreignEntityType = $relEntity->getEntityType(); + + $keySet = $this->query->getKeys($entity, $relationName); + + switch ($relType) { + case Entity::MANY_MANY: + $key = $keySet['key']; + $foreignKey = $keySet['foreignKey']; + $nearKey = $keySet['nearKey']; + $distantKey = $keySet['distantKey']; + + $relTable = $this->toDb($relDefs['relationName']); + + $fieldsPart = $this->toDb($nearKey); + $valuesPart = $this->pdo->quote($entity->id); + + $valueList = []; + $valueList[] = $entity->id; + + if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { + foreach ($relDefs['conditions'] as $f => $v) { + $fieldsPart .= ", " . $this->toDb($f); + $valuesPart .= ", " . $this->pdo->quote($v); + $valueList[] = $v; + } + } + $fieldsPart .= ", " . $this->toDb($distantKey); + + $params['select'] = []; + foreach ($valueList as $value) { + $params['select'][] = ['VALUE:' . $value, $value]; + } + + $params['select'][] = 'id'; + + unset($params['orderBy']); + unset($params['order']); + + $subSql = $this->query->createSelectQuery($foreignEntityType, $params); + + $sql = "INSERT INTO `".$relTable."` (".$fieldsPart.") (".$subSql.") ON DUPLICATE KEY UPDATE deleted = '0'"; + + if ($this->runQuery($sql, true)) { + return true; + } + + break; + } + } + + public function runQuery($query, $rerunIfDeadlock = false) + { + try { + return $this->pdo->query($query); + } catch (\Exception $e) { + if ($rerunIfDeadlock) { + if ($e->errorInfo[0] == 40001 && $e->errorInfo[1] == 1213) { + return $this->pdo->query($query); + } else { + throw $e; + } + } + } + } + + public function addRelation + (Entity $entity, string $relationName, ?string $id = null, ?Entity $relEntity = null, ?array $data = null) + { + if (!is_null($relEntity)) { + $id = $relEntity->id; + } + + if (empty($id) || empty($relationName)) { + return false; + } + + if (!$entity->hasRelation($relationName)) return false; + + $relDefs = $entity->relations[$relationName]; + + $relType = $entity->getRelationType($relationName); + $foreignEntityType = $entity->getRelationParam($relationName, 'entity'); + + if (!$relType || !$foreignEntityType && $relType !== Entity::BELONGS_TO_PARENT) { + throw new \LogicException("Not appropriate definition for relationship {$relationName} in " . $entity->getEntityType() . " entity"); + } + + $className = (!empty($relDefs['class'])) ? $relDefs['class'] : $foreignEntityType; + + if (is_null($relEntity)) { + $relEntity = $this->entityFactory->create($className); + if (!$relEntity) { + return false; + } + $relEntity->id = $id; + } + + $keySet = $this->query->getKeys($entity, $relationName); + + switch ($relType) { + case Entity::BELONGS_TO: + $key = $relationName . 'Id'; + + $foreignRelationName = $entity->getRelationParam($relationName, 'foreign'); + if ($foreignRelationName) { + if ($relEntity->getRelationParam($foreignRelationName, 'type') === Entity::HAS_ONE) { + $setPart = $this->toDb($key) . " = " . $this->quote(null); + $wherePart = $this->query->getWhere($entity, ['id!=' => $entity->id, $key => $id, 'deleted' => 0]); + $sql = $this->composeUpdateQuery( + $this->toDb($entity->getEntityType()), + $setPart, + $wherePart + ); + $this->pdo->query($sql); + } + } + + $setPart = $this->toDb($key) . " = " . $this->pdo->quote($relEntity->id); + $wherePart = $this->query->getWhere($entity, ['id' => $entity->id, 'deleted' => 0]); + + $entity->set([ + $key => $relEntity->id + ]); + + $sql = $this->composeUpdateQuery( + $this->toDb($entity->getEntityType()), + $setPart, + $wherePart + ); + + if ($this->pdo->query($sql)) { + return true; + } + return false; + + case Entity::BELONGS_TO_PARENT: + $key = $relationName . 'Id'; + $typeKey = $relationName . 'Type'; + + $entity->set([ + $key => $relEntity->id, + $typeKey => $relEntity->getEntityType() + ]); + + $setPart = + $this->toDb($key) . " = " . $this->pdo->quote($relEntity->id) . ', ' . + $this->toDb($typeKey) . " = " . $this->pdo->quote($relEntity->getEntityType()); + $wherePart = $this->query->getWhere($entity, ['id' => $id, 'deleted' => 0]); + + $sql = $this->composeUpdateQuery( + $this->toDb($entity->getEntityType()), + $setPart, + $wherePart + ); + + if ($this->pdo->query($sql)) { + return true; + } + return false; + + case Entity::HAS_ONE: + $foreignKey = $keySet['foreignKey']; + + if ($this->count($relEntity, ['whereClause' => ['id' => $id]]) === 0) return false; + + $setPart = $this->toDb($foreignKey) . " = " . $this->quote(null); + $wherePart = $this->query->getWhere($relEntity, [$foreignKey => $entity->id, 'deleted' => 0]); + $sql = $this->composeUpdateQuery($this->toDb($foreignEntityType), $setPart, $wherePart); + $this->pdo->query($sql); + + $setPart = $this->toDb($foreignKey) . " = " . $this->pdo->quote($entity->id); + $wherePart = $this->query->getWhere($relEntity, ['id' => $id, 'deleted' => 0]); + $sql = $this->composeUpdateQuery($this->toDb($foreignEntityType), $setPart, $wherePart); + + if ($this->pdo->query($sql)) { + return true; + } + + return false; + + case Entity::HAS_CHILDREN: + case Entity::HAS_MANY: + $key = $keySet['key']; + $foreignKey = $keySet['foreignKey']; + + if ($this->count($relEntity, ['whereClause' => ['id' => $id]]) > 0) { + + $setPart = $this->toDb($foreignKey) . " = " . $this->pdo->quote($entity->get($key)); + + if ($relType == Entity::HAS_CHILDREN) { + $foreignType = $keySet['foreignType']; + $setPart .= ", " . $this->toDb($foreignType) . " = " . $this->pdo->quote($entity->getEntityType()); + } + + $wherePart = $this->query->getWhere($relEntity, ['id' => $id, 'deleted' => 0]); + $sql = $this->composeUpdateQuery($this->toDb($relEntity->getEntityType()), $setPart, $wherePart); + + if ($this->pdo->query($sql)) { + return true; + } + return false; + } else { + return false; + } + break; + + case Entity::MANY_MANY: + $key = $keySet['key']; + $foreignKey = $keySet['foreignKey']; + $nearKey = $keySet['nearKey']; + $distantKey = $keySet['distantKey']; + + if ($this->count($relEntity, ['whereClause' => ['id' => $id]]) > 0) { + $relTable = $this->toDb($relDefs['relationName']); + + $wherePart = + $this->toDb($nearKey) . " = " . $this->pdo->quote($entity->id) . " ". + "AND " . $this->toDb($distantKey) . " = " . $this->pdo->quote($relEntity->id); + if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { + foreach ($relDefs['conditions'] as $f => $v) { + $wherePart .= " AND " . $this->toDb($f) . " = " . $this->pdo->quote($v); + } + } + + $sql = $this->query->composeSelectQuery($relTable, '*', '', $wherePart); + + $ps = $this->pdo->query($sql); + + if ($ps->rowCount() == 0) { + $fieldsPart = $this->toDb($nearKey) . ", " . $this->toDb($distantKey); + $valuesPart = $this->pdo->quote($entity->id) . ", " . $this->pdo->quote($relEntity->id); + + if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { + foreach ($relDefs['conditions'] as $f => $v) { + $fieldsPart .= ", " . $this->toDb($f); + $valuesPart .= ", " . $this->quote($v); + } + } + + if (!empty($data) && is_array($data)) { + foreach ($data as $column => $columnValue) { + $fieldsPart .= ", " . $this->toDb($column); + $valuesPart .= ", " . $this->quote($columnValue); + } + } + + $sql = $this->composeInsertQuery($relTable, $fieldsPart, $valuesPart); + + $sql .= " ON DUPLICATE KEY UPDATE deleted = '0'"; + + if (!empty($data) && is_array($data)) { + $setArr = []; + foreach ($data as $column => $value) { + $setArr[] = $this->toDb($column) . " = " . $this->quote($value); + } + $sql .= ', ' . implode(', ', $setArr); + } + + if ($this->runQuery($sql, true)) { + return true; + } + + } else { + $setPart = 'deleted = 0'; + + if (!empty($data) && is_array($data)) { + $setArr = []; + foreach ($data as $column => $value) { + $setArr[] = $this->toDb($column) . " = " . $this->quote($value); + } + $setPart .= ', ' . implode(', ', $setArr); + } + + $wherePart = + $this->toDb($nearKey) . " = " . $this->pdo->quote($entity->id) . " + AND " . $this->toDb($distantKey) . " = " . $this->pdo->quote($relEntity->id) . " + "; + + if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { + foreach ($relDefs['conditions'] as $f => $v) { + $wherePart .= " AND " . $this->toDb($f) . " = " . $this->pdo->quote($v); + } + } + + $sql = $this->composeUpdateQuery($relTable, $setPart, $wherePart); + if ($this->pdo->query($sql)) { + return true; + } + } + } else { + return false; + } + break; + } + + return false; + } + + public function removeRelation( + Entity $entity, string $relationName, ?string $id = null, bool $all = false, ?Entity $relEntity = null + ) : bool { + if (!is_null($relEntity)) { + $id = $relEntity->id; + } + + if (empty($id) && empty($all) || empty($relationName)) { + return false; + } + + if (!$entity->hasRelation($relationName)) return false; + + $relDefs = $entity->relations[$relationName]; + + $relType = $entity->getRelationType($relationName); + $foreignEntityType = $entity->getRelationParam($relationName, 'entity'); + + if (!$relType || !$foreignEntityType && $relType !== Entity::BELONGS_TO_PARENT) { + throw new \LogicException( + "Not appropriate definition for relationship {$relationName} in " . $entity->getEntityType() . " entity" + ); + } + + $className = (!empty($relDefs['class'])) ? $relDefs['class'] : $foreignEntityType; + + if (is_null($relEntity)) { + $relEntity = $this->entityFactory->create($className); + if (!$relEntity) { + return false; + } + $relEntity->id = $id; + } + + $keySet = $this->query->getKeys($entity, $relationName); + + switch ($relType) { + case Entity::BELONGS_TO: + $key = $relationName . 'Id'; + $setPart = $this->toDb($key) . " = " . $this->quote(null); + $wherePart = $this->query->getWhere($entity, ['id' => $entity->id, 'deleted' => 0]); + + $entity->set([ + $key => null + ]); + + $sql = $this->composeUpdateQuery( + $this->toDb($entity->getEntityType()), + $setPart, + $wherePart + ); + + if ($this->pdo->query($sql)) { + return true; + } + return false; + + case Entity::BELONGS_TO_PARENT: + $key = $relationName . 'Id'; + $typeKey = $relationName . 'Type'; + + $entity->set([ + $key => null, + $typeKey => null + ]); + + $setPart = + $this->toDb($key) . " = " . $this->quote(null) . ', ' . + $this->toDb($typeKey) . " = " . $this->quote(null); + $wherePart = $this->query->getWhere($entity, ['id' => $entity->id, 'deleted' => 0]); + + $sql = $this->composeUpdateQuery( + $this->toDb($entity->getEntityType()), + $setPart, + $wherePart + ); + + if ($this->pdo->query($sql)) { + return true; + } + return false; + + case Entity::HAS_ONE: + case Entity::HAS_MANY: + case Entity::HAS_CHILDREN: + $key = $keySet['key']; + $foreignKey = $keySet['foreignKey']; + + $setPart = $this->toDb($foreignKey) . " = " . "NULL"; + + $whereClause = ['deleted' => 0]; + if (empty($all) && $relType != Entity::HAS_ONE) { + $whereClause['id'] = $id; + } else { + $whereClause[$foreignKey] = $entity->id; + } + + if ($relType == Entity::HAS_CHILDREN) { + $foreignType = $keySet['foreignType']; + $whereClause[$foreignType] = $entity->getEntityType(); + } + + $wherePart = $this->query->getWhere($relEntity, $whereClause); + $sql = $this->composeUpdateQuery($this->toDb($relEntity->getEntityType()), $setPart, $wherePart); + if ($this->pdo->query($sql)) { + return true; + } + break; + + case Entity::MANY_MANY: + $key = $keySet['key']; + $foreignKey = $keySet['foreignKey']; + $nearKey = $keySet['nearKey']; + $distantKey = $keySet['distantKey']; + + $relTable = $this->toDb($relDefs['relationName']); + + $setPart = 'deleted = 1'; + $wherePart = $this->toDb($nearKey) . " = " . $this->pdo->quote($entity->id); + + + if (empty($all)) { + $wherePart .= " AND " . $this->toDb($distantKey) . " = " . $this->pdo->quote($id) . ""; + } + + if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { + foreach ($relDefs['conditions'] as $f => $v) { + $wherePart .= " AND " . $this->toDb($f) . " = " . $this->pdo->quote($v); + } + } + + $sql = $this->composeUpdateQuery($relTable, $setPart, $wherePart); + + if ($this->pdo->query($sql)) { + return true; + } + break; + } + return false; + } + + public function removeAllRelations(Entity $entity, string $relationName) : bool + { + $this->removeRelation($entity, $relationName, null, true); + } + + protected function quote($value) + { + if (is_null($value)) { + return 'NULL'; + } else if (is_bool($value)) { + return $value ? '1' : '0'; + } else { + return $this->pdo->quote($value); + } + } + + public function insert(Entity $entity) : ?string + { + $dataList = $this->toValueMap($entity); + + $fieldArr = []; + $valArr = []; + foreach ($dataList as $field => $value) { + $fieldArr[] = $this->toDb($field); + + $type = $entity->fields[$field]['type']; + + $value = $this->prepareValueForInsert($type, $value); + + $valArr[] = $this->quote($value); + } + $fieldsPart = "`" . implode("`, `", $fieldArr) . "`"; + $valuesPart = implode(", ", $valArr); + + $sql = $this->composeInsertQuery($this->toDb($entity->getEntityType()), $fieldsPart, $valuesPart); + + if ($this->pdo->query($sql)) { + return $entity->id; + } + + return null; + } + + public function update(Entity $entity) : ?string + { + $valueMap = $this->toValueMap($entity); + + $setArr = []; + + foreach ($valueMap as $attribute => $value) { + if ($attribute == 'id') { + continue; + } + $type = $entity->getAttributeType($attribute); + + if ($type == Entity::FOREIGN) { + continue; + } + + if (!$entity->isAttributeChanged($attribute)) { + continue; + } + + $value = $this->prepareValueForInsert($type, $value); + + $setArr[] = "`" . $this->toDb($attribute) . "` = " . $this->quote($value); + } + + if (count($setArr) == 0) { + return $entity->id; + } + + $setPart = implode(', ', $setArr); + $wherePart = $this->query->getWhere($entity, ['id' => $entity->id, 'deleted' => 0]); + + $sql = $this->composeUpdateQuery($this->toDb($entity->getEntityType()), $setPart, $wherePart); + + if ($this->pdo->query($sql)) { + return $entity->id; + } + + return null; + } + + protected function prepareValueForInsert($type, $value) + { + if ($type == Entity::JSON_ARRAY && is_array($value)) { + $value = json_encode($value, \JSON_UNESCAPED_UNICODE); + } else if ($type == Entity::JSON_OBJECT && (is_array($value) || $value instanceof \stdClass)) { + $value = json_encode($value, \JSON_UNESCAPED_UNICODE); + } else { + if (is_array($value) || is_object($value)) { + return null; + } + } + + if (is_bool($value)) { + $value = (int) $value; + } + return $value; + } + + public function deleteFromDb(string $entityType, string $id, bool $onlyDeleted = false) : bool + { + if (empty($entityType) || empty($id)) return false; + + $table = $this->toDb($entityType); + $sql = "DELETE FROM `{$table}` WHERE id = " . $this->quote($id); + if ($onlyDeleted) { + $sql .= " AND deleted = 1"; + } + if ($this->pdo->query($sql)) { + return true; + } + + return false; + } + + /** + * Mass delete from database by specified whereClause. + * + * @return Number of deleted records or null if failure. + */ + public function massDeleteFromDb(string $entityType, array $whereClause) : ?int + { + $table = $this->toDb($entityType); + + $sql = "DELETE FROM `{$table}`"; + + $entity = $this->entityFactory->create($entityType); + if (!$entity) return null; + + $wherePart = $this->query->getWhere($entity, $whereClause); + if ($wherePart) { + $sql .= ' WHERE ' . $wherePart; + } + + $sth = $this->pdo->prepare($sql); + + if ($sth->execute()) { + return $sth->rowCount(); + } + + return null; + } + + public function restoreDeleted(string $entityType, string $id) + { + if (empty($entityType) || empty($id)) return false; + + $table = $this->toDb($entityType); + $sql = "UPDATE `{$table}` SET `deleted` = 0 WHERE id = " . $this->quote($id); + if ($this->pdo->query($sql)) { + return true; + } + + return false; + } + + public function delete(Entity $entity) : bool + { + $entity->set('deleted', true); + return (booL) $this->update($entity); + } + + protected function toValueMap(Entity $entity, bool $onlyStorable = true) + { + $data = []; + foreach ($entity->getAttributes() as $attribute => $defs) { + if ($entity->has($attribute)) { + if ($onlyStorable) { + if ( + !empty($defs['notStorable']) + || + !empty($defs['autoincrement']) + || + isset($defs['source']) && $defs['source'] != 'db' + ) continue; + if ($defs['type'] == Entity::FOREIGN) continue; + } + $data[$attribute] = $entity->get($attribute); + } + } + return $data; + } + + protected function fromRow(Entity $entity, $data) + { + $entity->set($data); + return $entity; + } + + protected function getMMJoin(Entity $entity, $relationName, $keySet = false, $conditions = []) + { + $relDefs = $entity->relations[$relationName]; + + if (empty($keySet)) { + $keySet = $this->query->getKeys($entity, $relationName); + } + + $key = $keySet['key']; + $foreignKey = $keySet['foreignKey']; + $nearKey = $keySet['nearKey']; + $distantKey = $keySet['distantKey']; + + $relTable = $this->toDb($relDefs['relationName']); + $distantTable = $this->toDb($relDefs['entity']); + + $join = + "JOIN `{$relTable}` ON {$distantTable}." . $this->toDb($foreignKey) . " = {$relTable}." . $this->toDb($distantKey) + . " AND " + . "{$relTable}." . $this->toDb($nearKey) . " = " . $this->pdo->quote($entity->get($key)) + . " AND " + . "{$relTable}.deleted = " . $this->pdo->quote(0) . ""; + + $conditions = $conditions ?? []; + if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { + $conditions = array_merge($conditions, $relDefs['conditions']); + } + + if (!empty($conditions)) { + $conditionsSql = $this->query->buildJoinConditionsStatement($entity, $relTable, $conditions); + $join .= " AND " . $conditionsSql; + } + + return $join; + } + + + protected function composeInsertQuery($table, $fields, $values) + { + $sql = "INSERT INTO `{$table}`"; + $sql .= " ({$fields})"; + if (!is_array($values)) { + $sql .= " VALUES ({$values})"; + } else { + $sql .= " VALUES (" . implode("), (", $values) . ")"; + } + + return $sql; + } + + protected function composeUpdateQuery($table, $set, $where) + { + $sql = "UPDATE `{$table}` SET {$set} WHERE {$where}"; + + return $sql; + } + + abstract protected function toDb(string $attribute); + + public function setCollectionClass(string $collectionClass) + { + $this->collectionClass = $collectionClass; + } +} diff --git a/application/Espo/ORM/DB/IMapper.php b/application/Espo/ORM/DB/IMapper.php deleted file mode 100644 index 3caa7bbb57..0000000000 --- a/application/Espo/ORM/DB/IMapper.php +++ /dev/null @@ -1,152 +0,0 @@ -pdo = $pdo; - $this->query = $query; - $this->entityFactory = $entityFactory; - $this->metadata = $metadata; - } - - public function selectById(IEntity $entity, $id, ?array $params = null) : ?IEntity - { - $params = $params ?? []; - - if (!array_key_exists('whereClause', $params)) { - $params['whereClause'] = []; - } - - $params['whereClause']['id'] = $id; - - $sql = $this->query->createSelectQuery($entity->getEntityType(), $params); - - $ps = $this->pdo->query($sql); - - if ($ps) { - foreach ($ps as $row) { - $entity = $this->fromRow($entity, $row); - $entity->setAsFetched(); - return $entity; - } - } - return null; - } - - public function count(IEntity $entity, ?array $params = null) - { - return $this->aggregate($entity, $params, 'COUNT', 'id'); - } - - public function max(IEntity $entity, ?array $params, string $attribute) - { - return $this->aggregate($entity, $params, 'MAX', $attribute); - } - - public function min(IEntity $entity, ?array $params, string $attribute) - { - return $this->aggregate($entity, $params, 'MIN', $attribute); - } - - public function sum(IEntity $entity, ?array $params, string $attribute) - { - return $this->aggregate($entity, $params, 'SUM', $attribute); - } - - public function select(IEntity $entity, ?array $params = null) - { - $sql = $this->query->createSelectQuery($entity->getEntityType(), $params); - - return $this->selectByQuery($entity, $sql, $params); - } - - public function selectByQuery(IEntity $entity, $sql, ?array $params = null) - { - $params = $params ?? []; - - if ($params['returnSthCollection'] ?? false) { - $collection = $this->createSthCollection($entity->getEntityType()); - $collection->setQuery($sql); - return $collection; - } - - $dataArr = []; - $ps = $this->pdo->query($sql); - if ($ps) { - $dataArr = $ps->fetchAll(); - } - - if ($this->returnCollection) { - $collectionClass = $this->collectionClass; - $collection = new $collectionClass($dataArr, $entity->getEntityType(), $this->entityFactory); - $collection->setAsFetched(); - return $collection; - } else { - return $dataArr; - } - } - - protected function createSthCollection(string $entityType) - { - return new $this->sthColletctionClass($entityType, $this->entityFactory, $this->query, $this->pdo);; - } - - public function aggregate(IEntity $entity, ?array $params, string $aggregation, string $aggregationBy) - { - if (empty($aggregation) || !$entity->hasAttribute($aggregationBy)) { - return false; - } - - $params = $params ?? []; - - $params['aggregation'] = $aggregation; - $params['aggregationBy'] = $aggregationBy; - - $sql = $this->query->createSelectQuery($entity->getEntityType(), $params); - - $ps = $this->pdo->query($sql); - - if ($ps) { - foreach ($ps as $row) { - return $row['AggregateValue']; - } - } - return false; - } - - public function selectRelated(IEntity $entity, $relationName, $params = [], $returnTotalCount = false) - { - $relDefs = $entity->relations[$relationName]; - - if (!isset($relDefs['type'])) { - throw new \LogicException("Missing 'type' in definition for relationship {$relationName} in " . $entity->getEntityType() . " entity"); - } - - if ($relDefs['type'] !== IEntity::BELONGS_TO_PARENT) { - if (!isset($relDefs['entity'])) { - throw new \LogicException("Missing 'entity' in definition for relationship {$relationName} in " . $entity->getEntityType() . " entity"); - } - - $relEntityName = (!empty($relDefs['class'])) ? $relDefs['class'] : $relDefs['entity']; - $relEntity = $this->entityFactory->create($relEntityName); - - if (!$relEntity) { - return null; - } - } - - if ($returnTotalCount) { - $params['aggregation'] = 'COUNT'; - $params['aggregationBy'] = 'id'; - } - - if (empty($params['whereClause'])) { - $params['whereClause'] = []; - } - - $relType = $relDefs['type']; - - $keySet = $this->query->getKeys($entity, $relationName); - - $key = $keySet['key']; - $foreignKey = $keySet['foreignKey']; - - switch ($relType) { - case IEntity::BELONGS_TO: - $params['whereClause'][$foreignKey] = $entity->get($key); - $params['offset'] = 0; - $params['limit'] = 1; - - $sql = $this->query->createSelectQuery($relEntity->getEntityType(), $params); - - $ps = $this->pdo->query($sql); - - if ($ps) { - foreach ($ps as $row) { - if (!$returnTotalCount) { - $relEntity = $this->fromRow($relEntity, $row); - $relEntity->setAsFetched(); - return $relEntity; - } else { - return $row['AggregateValue']; - } - } - } - return null; - - case IEntity::HAS_MANY: - case IEntity::HAS_CHILDREN: - case IEntity::HAS_ONE: - $params['whereClause'][$foreignKey] = $entity->get($key); - - if ($relType == IEntity::HAS_CHILDREN) { - $foreignType = $keySet['foreignType']; - $params['whereClause'][$foreignType] = $entity->getEntityType(); - } - - if ($relType == IEntity::HAS_ONE) { - $params['offset'] = 0; - $params['limit'] = 1; - } - - if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { - $params['whereClause'][] = $relDefs['conditions']; - } - - $resultDataList = []; - - $sql = $this->query->createSelectQuery($relEntity->getEntityType(), $params); - - if (!$returnTotalCount) { - if (!empty($params['returnSthCollection']) && $relType !== IEntity::HAS_ONE) { - $collection = $this->createSthCollection($relEntity->getEntityType()); - $collection->setQuery($sql); - return $collection; - } - } - - $ps = $this->pdo->query($sql); - if ($ps) { - if (!$returnTotalCount) { - $resultDataList = $ps->fetchAll(); - } else { - foreach ($ps as $row) { - return $row['AggregateValue']; - } - } - } - - if ($relType == IEntity::HAS_ONE) { - if (count($resultDataList)) { - $relEntity = $this->fromRow($relEntity, $resultDataList[0]); - $relEntity->setAsFetched(); - return $relEntity; - } - return null; - } else { - if ($this->returnCollection) { - $collectionClass = $this->collectionClass; - $collection = new $collectionClass($resultDataList, $relEntity->getEntityType(), $this->entityFactory); - $collection->setAsFetched(); - return $collection; - } else { - return $resultDataList; - } - } - - case IEntity::MANY_MANY: - $additionalColumnsConditions = null; - if (!empty($params['additionalColumnsConditions'])) { - $additionalColumnsConditions = $params['additionalColumnsConditions']; - } - - $MMJoinPart = $this->getMMJoin($entity, $relationName, $keySet, $additionalColumnsConditions); - - if (empty($params['customJoin'])) { - $params['customJoin'] = ''; - } else { - $params['customJoin'] .= ' '; - } - $params['customJoin'] .= $MMJoinPart; - - $params['relationName'] = $relDefs['relationName']; - - $sql = $this->query->createSelectQuery($relEntity->getEntityType(), $params); - - $resultDataList = []; - - if (!$returnTotalCount) { - if (!empty($params['returnSthCollection'])) { - $collection = $this->createSthCollection($relEntity->getEntityType()); - $collection->setQuery($sql); - return $collection; - } - } - - $ps = $this->pdo->query($sql); - - if ($ps) { - if (!$returnTotalCount) { - $resultDataList = $ps->fetchAll(); - } else { - foreach ($ps as $row) { - return $row['AggregateValue']; - } - } - } - if ($this->returnCollection) { - $collectionClass = $this->collectionClass; - $collection = new $collectionClass($resultDataList, $relEntity->getEntityType(), $this->entityFactory); - $collection->setAsFetched(); - return $collection; - } else { - return $resultDataList; - } - case IEntity::BELONGS_TO_PARENT: - $foreignEntityType = $entity->get($keySet['typeKey']); - $foreignEntityId = $entity->get($key); - if (!$foreignEntityType || !$foreignEntityId) { - return null; - } - $params['whereClause'][$foreignKey] = $foreignEntityId; - $params['offset'] = 0; - $params['limit'] = 1; - - $relEntity = $this->entityFactory->create($foreignEntityType); - - $sql = $this->query->createSelectQuery($foreignEntityType, $params); - - $ps = $this->pdo->query($sql); - - if ($ps) { - foreach ($ps as $row) { - if (!$returnTotalCount) { - $relEntity = $this->fromRow($relEntity, $row); - return $relEntity; - } else { - return $row['AggregateValue']; - } - } - } - return null; - } - - return false; - } - - - public function countRelated(IEntity $entity, $relationName, $params = []) - { - return $this->selectRelated($entity, $relationName, $params, true); - } - - public function relate(IEntity $entityFrom, string $relationName, IEntity $entityTo, ?array $data = null) - { - return $this->addRelation($entityFrom, $relationName, null, $entityTo, $data); - } - - public function unrelate(IEntity $entityFrom, string $relationName, IEntity $entityTo) - { - return $this->removeRelation($entityFrom, $relationName, null, false, $entityTo); - } - - public function updateRelation(IEntity $entity, string $relationName, ?string $id = null, array $columnData) - { - if (empty($id) || empty($relationName)) { - return false; - } - - if (empty($columnData)) return; - - $relDefs = $entity->relations[$relationName]; - $keySet = $this->query->getKeys($entity, $relationName); - - $relType = $relDefs['type']; - - switch ($relType) { - case IEntity::MANY_MANY: - $relTable = $this->toDb($relDefs['relationName']); - $key = $keySet['key']; - $foreignKey = $keySet['foreignKey']; - $nearKey = $keySet['nearKey']; - $distantKey = $keySet['distantKey']; - - $setArr = []; - foreach ($columnData as $column => $value) { - $setArr[] = "`".$this->toDb($column) . "` = " . $this->quote($value); - } - if (empty($setArr)) { - return true; - } - $setPart = implode(', ', $setArr); - - $wherePart = - $this->toDb($nearKey) . " = " . $this->pdo->quote($entity->id) . " - AND " . $this->toDb($distantKey) . " = " . $this->pdo->quote($id) . " AND deleted = 0 - "; - - if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { - foreach ($relDefs['conditions'] as $f => $v) { - $wherePart .= " AND " . $this->toDb($f) . " = " . $this->pdo->quote($v); - } - } - - $sql = $this->composeUpdateQuery($relTable, $setPart, $wherePart); - - if ($this->pdo->query($sql)) { - return true; - } - } - } - - public function getRelationColumn(IEntity $entity, string $relationName, string $id, string $column) - { - $type = $entity->getRelationType($entityType, $relationName); - - if (!$type === IEntity::MANY_MANY) return null; - - $relDefs = $entity->relations[$relationName]; - - $relTable = $this->toDb($relDefs['relationName']); - - $keySet = $this->query->getKeys($entity, $relationName); - $key = $keySet['key']; - $foreignKey = $keySet['foreignKey']; - $nearKey = $keySet['nearKey']; - $distantKey = $keySet['distantKey']; - - $additionalColumns = $entity->getRelationParam($relationName, 'additionalColumns') ?? []; - - if (!isset($additionalColumns[$column])) return null; - - $columnType = $additionalColumns[$column]['type'] ?? 'string'; - - $columnAlias = $this->query->sanitizeSelectAlias($column); - - $sql = - "SELECT " . $this->toDb($this->query->sanitize($column)) . " AS `{$columnAlias}` FROM `{$relTable}` " . - "WHERE "; - - $wherePart = - $this->toDb($nearKey) . " = " . $this->pdo->quote($entity->id) . " ". - "AND " . $this->toDb($distantKey) . " = " . $this->pdo->quote($id) . " AND deleted = 0"; - - $sql .= $wherePart; - - $ps = $this->pdo->query($sql); - if ($ps) { - foreach ($ps as $row) { - $value = $row[$columnAlias]; - if ($columnType == 'bool') { - $value = boolval($value); - } else if ($columnType == 'int') { - $value = intval($value); - } else if ($columnType == 'float') { - $value = floatval($value); - } - - return $value; - } - } - - return null; - } - - public function massRelate(IEntity $entity, $relationName, array $params = []) - { - if (!$entity) { - return false; - } - $id = $entity->id; - - if (empty($id) || empty($relationName)) { - return false; - } - - $relDefs = $entity->relations[$relationName]; - - if (!isset($relDefs['entity']) || !isset($relDefs['type'])) { - throw new \LogicException("Not appropriate definition for relationship {$relationName} in " . $entity->getEntityType() . " entity"); - } - - $relType = $relDefs['type']; - - $className = (!empty($relDefs['class'])) ? $relDefs['class'] : $relDefs['entity']; - $relEntity = $this->entityFactory->create($className); - $foreignEntityType = $relEntity->getEntityType(); - - $keySet = $this->query->getKeys($entity, $relationName); - - switch ($relType) { - case IEntity::MANY_MANY: - $key = $keySet['key']; - $foreignKey = $keySet['foreignKey']; - $nearKey = $keySet['nearKey']; - $distantKey = $keySet['distantKey']; - - $relTable = $this->toDb($relDefs['relationName']); - - $fieldsPart = $this->toDb($nearKey); - $valuesPart = $this->pdo->quote($entity->id); - - $valueList = []; - $valueList[] = $entity->id; - - if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { - foreach ($relDefs['conditions'] as $f => $v) { - $fieldsPart .= ", " . $this->toDb($f); - $valuesPart .= ", " . $this->pdo->quote($v); - $valueList[] = $v; - } - } - $fieldsPart .= ", " . $this->toDb($distantKey); - - $params['select'] = []; - foreach ($valueList as $value) { - $params['select'][] = ['VALUE:' . $value, $value]; - } - - $params['select'][] = 'id'; - - unset($params['orderBy']); - unset($params['order']); - - $subSql = $this->query->createSelectQuery($foreignEntityType, $params); - - $sql = "INSERT INTO `".$relTable."` (".$fieldsPart.") (".$subSql.") ON DUPLICATE KEY UPDATE deleted = '0'"; - - if ($this->runQuery($sql, true)) { - return true; - } - - break; - } - } - - public function runQuery($query, $rerunIfDeadlock = false) - { - try { - return $this->pdo->query($query); - } catch (\Exception $e) { - if ($rerunIfDeadlock) { - if ($e->errorInfo[0] == 40001 && $e->errorInfo[1] == 1213) { - return $this->pdo->query($query); - } else { - throw $e; - } - } - } - } - - public function addRelation - (IEntity $entity, string $relationName, ?string $id = null, ?IEntity $relEntity = null, ?array $data = null) - { - if (!is_null($relEntity)) { - $id = $relEntity->id; - } - - if (empty($id) || empty($relationName)) { - return false; - } - - if (!$entity->hasRelation($relationName)) return false; - - $relDefs = $entity->relations[$relationName]; - - $relType = $entity->getRelationType($relationName); - $foreignEntityType = $entity->getRelationParam($relationName, 'entity'); - - if (!$relType || !$foreignEntityType && $relType !== IEntity::BELONGS_TO_PARENT) { - throw new \LogicException("Not appropriate definition for relationship {$relationName} in " . $entity->getEntityType() . " entity"); - } - - $className = (!empty($relDefs['class'])) ? $relDefs['class'] : $foreignEntityType; - - if (is_null($relEntity)) { - $relEntity = $this->entityFactory->create($className); - if (!$relEntity) { - return false; - } - $relEntity->id = $id; - } - - $keySet = $this->query->getKeys($entity, $relationName); - - switch ($relType) { - case IEntity::BELONGS_TO: - $key = $relationName . 'Id'; - - $foreignRelationName = $entity->getRelationParam($relationName, 'foreign'); - if ($foreignRelationName) { - if ($relEntity->getRelationParam($foreignRelationName, 'type') === IEntity::HAS_ONE) { - $setPart = $this->toDb($key) . " = " . $this->quote(null); - $wherePart = $this->query->getWhere($entity, ['id!=' => $entity->id, $key => $id, 'deleted' => 0]); - $sql = $this->composeUpdateQuery( - $this->toDb($entity->getEntityType()), - $setPart, - $wherePart - ); - $this->pdo->query($sql); - } - } - - $setPart = $this->toDb($key) . " = " . $this->pdo->quote($relEntity->id); - $wherePart = $this->query->getWhere($entity, ['id' => $entity->id, 'deleted' => 0]); - - $entity->set([ - $key => $relEntity->id - ]); - - $sql = $this->composeUpdateQuery( - $this->toDb($entity->getEntityType()), - $setPart, - $wherePart - ); - - if ($this->pdo->query($sql)) { - return true; - } - return false; - - case IEntity::BELONGS_TO_PARENT: - $key = $relationName . 'Id'; - $typeKey = $relationName . 'Type'; - - $entity->set([ - $key => $relEntity->id, - $typeKey => $relEntity->getEntityType() - ]); - - $setPart = - $this->toDb($key) . " = " . $this->pdo->quote($relEntity->id) . ', ' . - $this->toDb($typeKey) . " = " . $this->pdo->quote($relEntity->getEntityType()); - $wherePart = $this->query->getWhere($entity, ['id' => $id, 'deleted' => 0]); - - $sql = $this->composeUpdateQuery( - $this->toDb($entity->getEntityType()), - $setPart, - $wherePart - ); - - if ($this->pdo->query($sql)) { - return true; - } - return false; - - case IEntity::HAS_ONE: - $foreignKey = $keySet['foreignKey']; - - if ($this->count($relEntity, ['whereClause' => ['id' => $id]]) === 0) return false; - - $setPart = $this->toDb($foreignKey) . " = " . $this->quote(null); - $wherePart = $this->query->getWhere($relEntity, [$foreignKey => $entity->id, 'deleted' => 0]); - $sql = $this->composeUpdateQuery($this->toDb($foreignEntityType), $setPart, $wherePart); - $this->pdo->query($sql); - - $setPart = $this->toDb($foreignKey) . " = " . $this->pdo->quote($entity->id); - $wherePart = $this->query->getWhere($relEntity, ['id' => $id, 'deleted' => 0]); - $sql = $this->composeUpdateQuery($this->toDb($foreignEntityType), $setPart, $wherePart); - - if ($this->pdo->query($sql)) { - return true; - } - - return false; - - case IEntity::HAS_CHILDREN: - case IEntity::HAS_MANY: - $key = $keySet['key']; - $foreignKey = $keySet['foreignKey']; - - if ($this->count($relEntity, ['whereClause' => ['id' => $id]]) > 0) { - - $setPart = $this->toDb($foreignKey) . " = " . $this->pdo->quote($entity->get($key)); - - if ($relType == IEntity::HAS_CHILDREN) { - $foreignType = $keySet['foreignType']; - $setPart .= ", " . $this->toDb($foreignType) . " = " . $this->pdo->quote($entity->getEntityType()); - } - - $wherePart = $this->query->getWhere($relEntity, ['id' => $id, 'deleted' => 0]); - $sql = $this->composeUpdateQuery($this->toDb($relEntity->getEntityType()), $setPart, $wherePart); - - if ($this->pdo->query($sql)) { - return true; - } - return false; - } else { - return false; - } - break; - - case IEntity::MANY_MANY: - $key = $keySet['key']; - $foreignKey = $keySet['foreignKey']; - $nearKey = $keySet['nearKey']; - $distantKey = $keySet['distantKey']; - - if ($this->count($relEntity, ['whereClause' => ['id' => $id]]) > 0) { - $relTable = $this->toDb($relDefs['relationName']); - - $wherePart = - $this->toDb($nearKey) . " = " . $this->pdo->quote($entity->id) . " ". - "AND " . $this->toDb($distantKey) . " = " . $this->pdo->quote($relEntity->id); - if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { - foreach ($relDefs['conditions'] as $f => $v) { - $wherePart .= " AND " . $this->toDb($f) . " = " . $this->pdo->quote($v); - } - } - - $sql = $this->query->composeSelectQuery($relTable, '*', '', $wherePart); - - $ps = $this->pdo->query($sql); - - if ($ps->rowCount() == 0) { - $fieldsPart = $this->toDb($nearKey) . ", " . $this->toDb($distantKey); - $valuesPart = $this->pdo->quote($entity->id) . ", " . $this->pdo->quote($relEntity->id); - - if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { - foreach ($relDefs['conditions'] as $f => $v) { - $fieldsPart .= ", " . $this->toDb($f); - $valuesPart .= ", " . $this->quote($v); - } - } - - if (!empty($data) && is_array($data)) { - foreach ($data as $column => $columnValue) { - $fieldsPart .= ", " . $this->toDb($column); - $valuesPart .= ", " . $this->quote($columnValue); - } - } - - $sql = $this->composeInsertQuery($relTable, $fieldsPart, $valuesPart); - - $sql .= " ON DUPLICATE KEY UPDATE deleted = '0'"; - - if (!empty($data) && is_array($data)) { - $setArr = []; - foreach ($data as $column => $value) { - $setArr[] = $this->toDb($column) . " = " . $this->quote($value); - } - $sql .= ', ' . implode(', ', $setArr); - } - - if ($this->runQuery($sql, true)) { - return true; - } - - } else { - $setPart = 'deleted = 0'; - - if (!empty($data) && is_array($data)) { - $setArr = []; - foreach ($data as $column => $value) { - $setArr[] = $this->toDb($column) . " = " . $this->quote($value); - } - $setPart .= ', ' . implode(', ', $setArr); - } - - $wherePart = - $this->toDb($nearKey) . " = " . $this->pdo->quote($entity->id) . " - AND " . $this->toDb($distantKey) . " = " . $this->pdo->quote($relEntity->id) . " - "; - - if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { - foreach ($relDefs['conditions'] as $f => $v) { - $wherePart .= " AND " . $this->toDb($f) . " = " . $this->pdo->quote($v); - } - } - - $sql = $this->composeUpdateQuery($relTable, $setPart, $wherePart); - if ($this->pdo->query($sql)) { - return true; - } - } - } else { - return false; - } - break; - } - - return false; - } - - public function removeRelation - (IEntity $entity, string $relationName, ?string $id = null, bool $all = false, IEntity $relEntity = null) - { - if (!is_null($relEntity)) { - $id = $relEntity->id; - } - - if (empty($id) && empty($all) || empty($relationName)) { - return false; - } - - if (!$entity->hasRelation($relationName)) return false; - - $relDefs = $entity->relations[$relationName]; - - $relType = $entity->getRelationType($relationName); - $foreignEntityType = $entity->getRelationParam($relationName, 'entity'); - - if (!$relType || !$foreignEntityType && $relType !== IEntity::BELONGS_TO_PARENT) { - throw new \LogicException("Not appropriate definition for relationship {$relationName} in " . $entity->getEntityType() . " entity"); - } - - $className = (!empty($relDefs['class'])) ? $relDefs['class'] : $foreignEntityType; - - if (is_null($relEntity)) { - $relEntity = $this->entityFactory->create($className); - if (!$relEntity) { - return null; - } - $relEntity->id = $id; - } - - $keySet = $this->query->getKeys($entity, $relationName); - - switch ($relType) { - case IEntity::BELONGS_TO: - $key = $relationName . 'Id'; - $setPart = $this->toDb($key) . " = " . $this->quote(null); - $wherePart = $this->query->getWhere($entity, ['id' => $entity->id, 'deleted' => 0]); - - $entity->set([ - $key => null - ]); - - $sql = $this->composeUpdateQuery( - $this->toDb($entity->getEntityType()), - $setPart, - $wherePart - ); - - if ($this->pdo->query($sql)) { - return true; - } - return false; - - case IEntity::BELONGS_TO_PARENT: - $key = $relationName . 'Id'; - $typeKey = $relationName . 'Type'; - - $entity->set([ - $key => null, - $typeKey => null - ]); - - $setPart = - $this->toDb($key) . " = " . $this->quote(null) . ', ' . - $this->toDb($typeKey) . " = " . $this->quote(null); - $wherePart = $this->query->getWhere($entity, ['id' => $entity->id, 'deleted' => 0]); - - $sql = $this->composeUpdateQuery( - $this->toDb($entity->getEntityType()), - $setPart, - $wherePart - ); - - if ($this->pdo->query($sql)) { - return true; - } - return false; - - case IEntity::HAS_ONE: - case IEntity::HAS_MANY: - case IEntity::HAS_CHILDREN: - $key = $keySet['key']; - $foreignKey = $keySet['foreignKey']; - - $setPart = $this->toDb($foreignKey) . " = " . "NULL"; - - $whereClause = ['deleted' => 0]; - if (empty($all) && $relType != IEntity::HAS_ONE) { - $whereClause['id'] = $id; - } else { - $whereClause[$foreignKey] = $entity->id; - } - - if ($relType == IEntity::HAS_CHILDREN) { - $foreignType = $keySet['foreignType']; - $whereClause[$foreignType] = $entity->getEntityType(); - } - - $wherePart = $this->query->getWhere($relEntity, $whereClause); - $sql = $this->composeUpdateQuery($this->toDb($relEntity->getEntityType()), $setPart, $wherePart); - if ($this->pdo->query($sql)) { - return true; - } - break; - - case IEntity::MANY_MANY: - $key = $keySet['key']; - $foreignKey = $keySet['foreignKey']; - $nearKey = $keySet['nearKey']; - $distantKey = $keySet['distantKey']; - - $relTable = $this->toDb($relDefs['relationName']); - - $setPart = 'deleted = 1'; - $wherePart = $this->toDb($nearKey) . " = " . $this->pdo->quote($entity->id); - - - if (empty($all)) { - $wherePart .= " AND " . $this->toDb($distantKey) . " = " . $this->pdo->quote($id) . ""; - } - - if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { - foreach ($relDefs['conditions'] as $f => $v) { - $wherePart .= " AND " . $this->toDb($f) . " = " . $this->pdo->quote($v); - } - } - - $sql = $this->composeUpdateQuery($relTable, $setPart, $wherePart); - - if ($this->pdo->query($sql)) { - return true; - } - break; - } - } - - public function removeAllRelations(IEntity $entity, string $relationName) - { - $this->removeRelation($entity, $relationName, null, true); - } - - protected function quote($value) - { - if (is_null($value)) { - return 'NULL'; - } else if (is_bool($value)) { - return $value ? '1' : '0'; - } else { - return $this->pdo->quote($value); - } - } - - public function insert(IEntity $entity) - { - $dataArr = $this->toValueMap($entity); - - $fieldArr = []; - $valArr = []; - foreach ($dataArr as $field => $value) { - $fieldArr[] = $this->toDb($field); - - $type = $entity->fields[$field]['type']; - - $value = $this->prepareValueForInsert($type, $value); - - $valArr[] = $this->quote($value); - } - $fieldsPart = "`" . implode("`, `", $fieldArr) . "`"; - $valuesPart = implode(", ", $valArr); - - $sql = $this->composeInsertQuery($this->toDb($entity->getEntityType()), $fieldsPart, $valuesPart); - - if ($this->pdo->query($sql)) { - return $entity->id; - } - - return false; - } - - public function update(IEntity $entity) - { - $valueMap = $this->toValueMap($entity); - - $setArr = []; - - foreach ($valueMap as $attribute => $value) { - if ($attribute == 'id') { - continue; - } - $type = $entity->getAttributeType($attribute); - - if ($type == IEntity::FOREIGN) { - continue; - } - - if (!$entity->isAttributeChanged($attribute)) { - continue; - } - - $value = $this->prepareValueForInsert($type, $value); - - $setArr[] = "`" . $this->toDb($attribute) . "` = " . $this->quote($value); - } - - if (count($setArr) == 0) { - return $entity->id; - } - - $setPart = implode(', ', $setArr); - $wherePart = $this->query->getWhere($entity, ['id' => $entity->id, 'deleted' => 0]); - - $sql = $this->composeUpdateQuery($this->toDb($entity->getEntityType()), $setPart, $wherePart); - - if ($this->pdo->query($sql)) { - return $entity->id; - } - - return false; - } - - protected function prepareValueForInsert($type, $value) { - if ($type == IEntity::JSON_ARRAY && is_array($value)) { - $value = json_encode($value, \JSON_UNESCAPED_UNICODE); - } else if ($type == IEntity::JSON_OBJECT && (is_array($value) || $value instanceof \stdClass)) { - $value = json_encode($value, \JSON_UNESCAPED_UNICODE); - } else { - if (is_array($value) || is_object($value)) { - return null; - } - } - - if (is_bool($value)) { - $value = (int) $value; - } - return $value; - } - - public function deleteFromDb(string $entityType, $id, $onlyDeleted = false) - { - if (empty($entityType) || empty($id)) return false; - - $table = $this->toDb($entityType); - $sql = "DELETE FROM `{$table}` WHERE id = " . $this->quote($id); - if ($onlyDeleted) { - $sql .= " AND deleted = 1"; - } - if ($this->pdo->query($sql)) { - return true; - } - - return false; - } + /** + * Select an entity by ID. + */ + public function selectById(Entity $entity, $id, ?array $params = null) : ?Entity; /** - * Mass delete from database by specified whereClause. - * @return Number of deleted records or null if failure. + * Select a list of entities according to given parameters. */ - public function massDeleteFromDb(string $entityType, array $whereClause) : ?int - { - $table = $this->toDb($entityType); + public function select(Entity $entity, ?array $params = null) : Collection; - $sql = "DELETE FROM `{$table}`"; + /** + * Invoke an aggregate function and return a result value. + * + * @return mixed Result of an aggregation. + */ + public function aggregate(Entity $entity, ?array $params, string $aggregation, string $aggregationBy); - $entity = $this->entityFactory->create($entityType); - if (!$entity) return null; + /** + * Returns count of records according to given parameters. + * + * @return Record count. + */ + public function count(Entity $entity, ?array $params = null) : int; - $wherePart = $this->query->getWhere($entity, $whereClause); - if ($wherePart) { - $sql .= ' WHERE ' . $wherePart; - } + /** + * Returns max value of the attribute in the select according to given parameters. + * + * @return mixed Max value. + */ + public function max(Entity $entity, ?array $params, string $attribute); - $sth = $this->pdo->prepare($sql); + /** + * Returns a min value of the attribute in the select according to given parameters. + * + * @return mixed Min value. + */ + public function min(Entity $entity, ?array $params, string $attribute); - if ($sth->execute()) { - return $sth->rowCount(); - } + /** + * Returns a sum value of the attribute in the select according to given parameters. + * + * @return mixed Sum value. + */ + function sum(Entity $entity, ?array $params, string $attribute); - return null; - } + /** + * Selects related entity or list of entities. + * + * @return List of entities or total count if $totalCount was passed as true. + */ + public function selectRelated(Entity $entity, string $relationName, array $params = [], bool $returnTotalCount = false); - public function restoreDeleted(string $entityType, $id) - { - if (empty($entityType) || empty($id)) return false; + /** + * Returns count of related records according to given parameters. + * + * @return int Count of records. + */ + public function countRelated(Entity $entity, string $relationName, array $params) : int; - $table = $this->toDb($entityType); - $sql = "UPDATE `{$table}` SET `deleted` = 0 WHERE id = " . $this->quote($id); - if ($this->pdo->query($sql)) { - return true; - } + /** + * Links entity with another one. + * + * @return bool True if success + */ + public function addRelation( + Entity $entity, string $relationName, ?string $id = null, ?Entity $relEntity = null, ?array $data = null + ); - return false; - } + /** + * Remove relation of entity with certain record. + * + * @return bool True if success. + */ + public function removeRelation( + Entity $entity, string $relationName, ?string $id = null, bool $all = false, ?Entity $relEntity = null + ) : bool; - public function delete(IEntity $entity) - { - $entity->set('deleted', true); - return $this->update($entity); - } + /** + * Remove all relations of entity of specified relation name. + * + * @return True if success. + */ + public function removeAllRelations(Entity $entity, string $relationName); - protected function toValueMap(IEntity $entity, bool $onlyStorable = true) - { - $data = []; - foreach ($entity->getAttributes() as $attribute => $defs) { - if ($entity->has($attribute)) { - if ($onlyStorable) { - if ( - !empty($defs['notStorable']) - || - !empty($defs['autoincrement']) - || - isset($defs['source']) && $defs['source'] != 'db' - ) continue; - if ($defs['type'] == IEntity::FOREIGN) continue; - } - $data[$attribute] = $entity->get($attribute); - } - } - return $data; - } + /** + * Insert an entity into DB. + * + * @return Record ID if success. + */ + public function insert(Entity $entity); - protected function fromRow(IEntity $entity, $data) - { - $entity->set($data); - return $entity; - } - - protected function getMMJoin(IEntity $entity, $relationName, $keySet = false, $conditions = []) - { - $relDefs = $entity->relations[$relationName]; - - if (empty($keySet)) { - $keySet = $this->query->getKeys($entity, $relationName); - } - - $key = $keySet['key']; - $foreignKey = $keySet['foreignKey']; - $nearKey = $keySet['nearKey']; - $distantKey = $keySet['distantKey']; - - $relTable = $this->toDb($relDefs['relationName']); - $distantTable = $this->toDb($relDefs['entity']); - - $join = - "JOIN `{$relTable}` ON {$distantTable}." . $this->toDb($foreignKey) . " = {$relTable}." . $this->toDb($distantKey) - . " AND " - . "{$relTable}." . $this->toDb($nearKey) . " = " . $this->pdo->quote($entity->get($key)) - . " AND " - . "{$relTable}.deleted = " . $this->pdo->quote(0) . ""; - - $conditions = $conditions ?? []; - if (!empty($relDefs['conditions']) && is_array($relDefs['conditions'])) { - $conditions = array_merge($conditions, $relDefs['conditions']); - } - - if (!empty($conditions)) { - $conditionsSql = $this->query->buildJoinConditionsStatement($entity, $relTable, $conditions); - $join .= " AND " . $conditionsSql; - } - - return $join; - } + /** + * Update an entity in DB. + * + * @return Recotd ID if success. + */ + public function update(Entity $entity) : ?string; - protected function composeInsertQuery($table, $fields, $values) - { - $sql = "INSERT INTO `{$table}`"; - $sql .= " ({$fields})"; - if (!is_array($values)) { - $sql .= " VALUES ({$values})"; - } else { - $sql .= " VALUES (" . implode("), (", $values) . ")"; - } + /** + * Delete an entity (mark as deleted). + * + * @return TRUE if success. + */ + public function delete(Entity $entity) : bool; - return $sql; - } + /** + * Set a class name of a a model collection that will be returned by operations such as select. + */ + public function setCollectionClass(string $collectionClass); - protected function composeUpdateQuery($table, $set, $where) - { - $sql = "UPDATE `{$table}` SET {$set} WHERE {$where}"; - - return $sql; - } - - abstract protected function toDb($attribute); - - public function setReturnCollection($returnCollection) - { - $this->returnCollection = $returnCollection; - } - - public function setCollectionClass(string $collectionClass) - { - $this->collectionClass = $collectionClass; - } + /** + * Delete a record from DB. + */ + public function deleteFromDb(string $entityType, string $id, bool $onlyDeleted = false) : bool; } diff --git a/application/Espo/ORM/DB/MysqlMapper.php b/application/Espo/ORM/DB/MysqlMapper.php index ad08396dff..fc2f9336d3 100644 --- a/application/Espo/ORM/DB/MysqlMapper.php +++ b/application/Espo/ORM/DB/MysqlMapper.php @@ -28,18 +28,10 @@ ************************************************************************/ namespace Espo\ORM\DB; -use Espo\ORM\Entity; -use Espo\ORM\Classes\EntityCollection; -use PDO; -/** - * Abstraction for MySQL DB. - * Mapping of Entity to DB. - * Should be used internally only. - */ -class MysqlMapper extends Mapper +class MysqlMapper extends BaseMapper { - protected function toDb($attribute) + protected function toDb(string $attribute) { return $this->query->toDb($attribute); } diff --git a/application/Espo/ORM/EntityManager.php b/application/Espo/ORM/EntityManager.php index 215c087ab7..eb50421566 100644 --- a/application/Espo/ORM/EntityManager.php +++ b/application/Espo/ORM/EntityManager.php @@ -32,7 +32,7 @@ namespace Espo\ORM; use Espo\Core\Exceptions\Error; use Espo\ORM\DB\{ - IMapper, + Mapper, Query\Base as Query, }; @@ -119,7 +119,7 @@ class EntityManager break; } - if (!class_exists($className)) { + if (!$className || !class_exists($className)) { throw new Error("Mapper {$name} does not exist."); } @@ -129,20 +129,18 @@ class EntityManager /** * Get Mapper. */ - public function getMapper(?string $name = null) : IMapper + public function getMapper(?string $name = null) : Mapper { $name = $name ?? $this->defaultMapperName; - if ($name{0} == '\\') { - $className = $name; - } else { - $className = $this->getMapperClassName($name); - } + $className = $this->getMapperClassName($name); if (empty($this->mappers[$className])) { $this->mappers[$className] = new $className( - $this->getPDO(), $this->entityFactory, $this->getQuery(), $this->metadata); + $this->getPDO(), $this->entityFactory, $this->getQuery(), $this->metadata + ); } + return $this->mappers[$className]; } diff --git a/application/Espo/ORM/Repositories/Findable.php b/application/Espo/ORM/Repositories/Findable.php index 5f8d9b3b0d..e0ff0635e6 100644 --- a/application/Espo/ORM/Repositories/Findable.php +++ b/application/Espo/ORM/Repositories/Findable.php @@ -29,13 +29,16 @@ namespace Espo\ORM\Repositories; -use Espo\ORM\Entity; +use Espo\ORM\{ + Entity, + ICollection as Collection, +}; interface Findable { public function count(array $params) : int; - public function find(array $params) : \Traversable; + public function find(array $params) : Collection; public function findOne(array $params) : ?Entity; } diff --git a/application/Espo/ORM/Repositories/RDB.php b/application/Espo/ORM/Repositories/RDB.php index 7c421b2d5e..b346bc425c 100644 --- a/application/Espo/ORM/Repositories/RDB.php +++ b/application/Espo/ORM/Repositories/RDB.php @@ -29,20 +29,17 @@ namespace Espo\ORM\Repositories; -use Espo\ORM\EntityManager; -use Espo\ORM\EntityFactory; -use Espo\ORM\EntityCollection; -use Espo\ORM\Entity; -use Espo\ORM\IEntity; -use Espo\ORM\Repository; - -use Espo\ORM\DB\IMapper as Mapper; +use Espo\ORM\{ + EntityManager, + EntityFactory, + ICollection as Collection, + Entity, + Repository, + DB\Mapper, +}; class RDB extends Repository implements Findable, Relatable, Removable { - /** - * @var Object Mapper. - */ protected $mapper; /** @@ -85,6 +82,9 @@ class RDB extends Repository implements Findable, Relatable, Removable { } + /** + * Reset select parameters. + */ public function reset() { $this->whereClause = []; @@ -92,6 +92,9 @@ class RDB extends Repository implements Findable, Relatable, Removable $this->listParams = []; } + /** + * Get a new entity. + */ public function getNew() : ?Entity { $entity = $this->entityFactory->create($this->entityType); @@ -190,7 +193,7 @@ class RDB extends Repository implements Findable, Relatable, Removable return $this->getMapper()->deleteFromDb($this->entityType, $id, $onlyDeleted); } - public function find(array $params = []) : \Traversable + public function find(array $params = []) : Collection { $params = $this->getSelectParams($params); @@ -198,16 +201,7 @@ class RDB extends Repository implements Findable, Relatable, Removable $this->handleSelectParams($params); } - $selectResult = $this->getMapper()->select($this->seed, $params); - - if (!empty($params['returnSthCollection'])) { - $collection = $selectResult; - } else { - $dataList = $selectResult; - $collection = new EntityCollection($dataList, $this->entityType, $this->entityFactory); - } - - $collection->setAsFetched(); + $collection = $this->getMapper()->select($this->seed, $params); $this->reset(); @@ -225,11 +219,9 @@ class RDB extends Repository implements Findable, Relatable, Removable public function findByQuery(string $sql, ?string $collectionType = null) { - $dataArr = $this->getMapper()->selectByQuery($this->seed, $sql); - if (!$collectionType) { - $collection = new EntityCollection($dataArr, $this->entityType, $this->entityFactory); - } else if ($collectionType === \Espo\ORM\EntityManager::STH_COLLECTION) { + $collection = $this->getMapper()->selectByQuery($this->seed, $sql); + } else if ($collectionType === EntityManager::STH_COLLECTION) { $collection = $this->getEntityManager()->createSthCollection($this->entityType); $collection->setQuery($sql); } @@ -242,7 +234,7 @@ class RDB extends Repository implements Findable, Relatable, Removable public function findRelated(Entity $entity, string $relationName, array $params = []) { if (!$entity->id) { - return []; + return null; } if ($entity->getRelationType($relationName) === Entity::BELONGS_TO_PARENT) { @@ -257,19 +249,7 @@ class RDB extends Repository implements Findable, Relatable, Removable $result = $this->getMapper()->selectRelated($entity, $relationName, $params); - if (is_array($result)) { - $collection = new EntityCollection($result, $entityType, $this->entityFactory); - $collection->setAsFetched(); - return $collection; - } else if ($result instanceof EntityCollection) { - $collection = $result; - return $collection; - } else if ($result instanceof Entity) { - $entity = $result; - return $entity; - } else { - return $result; - } + return $result; } public function countRelated(Entity $entity, string $relationName, array $params = []) : int @@ -373,7 +353,6 @@ class RDB extends Repository implements Findable, Relatable, Removable return $result; } - public function unrelate(Entity $entity, string $relationName, $foreign, array $options = []) { if (!$entity->id) { @@ -587,7 +566,7 @@ class RDB extends Repository implements Findable, Relatable, Removable return $this; } - public function order($field = 'id', $direction = "ASC") + public function order($field = 'id', $direction = 'ASC') { $this->listParams['orderBy'] = $field; $this->listParams['order'] = $direction; diff --git a/tests/unit/Espo/ORM/DB/MapperTest.php b/tests/unit/Espo/ORM/DB/MapperTest.php index 64d7951897..4fed7e9cd3 100644 --- a/tests/unit/Espo/ORM/DB/MapperTest.php +++ b/tests/unit/Espo/ORM/DB/MapperTest.php @@ -61,17 +61,17 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase return "'" . $args[0] . "'"; })); - $metadata = $this->getMockBuilder('\\Espo\\ORM\\Metadata')->disableOriginalConstructor()->getMock(); + $metadata = $this->getMockBuilder('Espo\\ORM\\Metadata')->disableOriginalConstructor()->getMock(); $metadata ->method('get') ->will($this->returnValue(false)); - $entityManager = $this->getMockBuilder('\\Espo\\ORM\\EntityManager')->disableOriginalConstructor()->getMock(); + $entityManager = $this->getMockBuilder('Espo\\ORM\\EntityManager')->disableOriginalConstructor()->getMock(); $entityManager ->method('getMetadata') ->will($this->returnValue($metadata)); - $this->entityFactory = $this->getMockBuilder('\\Espo\\ORM\\EntityFactory')->disableOriginalConstructor()->getMock(); + $this->entityFactory = $this->getMockBuilder('Espo\\ORM\\EntityFactory')->disableOriginalConstructor()->getMock(); $this->entityFactory ->expects($this->any()) ->method('create') @@ -81,12 +81,11 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase return new $className([], $entityManager); })); - $this->metadata = $this->getMockBuilder('\\Espo\\ORM\\Metadata')->disableOriginalConstructor()->getMock(); + $this->metadata = $this->getMockBuilder('Espo\\ORM\\Metadata')->disableOriginalConstructor()->getMock(); $this->query = new Query($this->pdo, $this->entityFactory, $this->metadata); $this->db = new MysqlMapper($this->pdo, $this->entityFactory, $this->query, $this->metadata); - $this->db->setReturnCollection(true); $this->post = new \Espo\Entities\Post([], $entityManager); $this->comment = new \Espo\Entities\Comment([], $entityManager); @@ -95,7 +94,6 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase $this->contact = new \Espo\Entities\Contact([], $entityManager); $this->account = new \Espo\Entities\Account([], $entityManager); - } protected function tearDown() : void