type fix
This commit is contained in:
@@ -39,17 +39,24 @@ use RuntimeException;
|
||||
|
||||
class BaseEntity implements Entity
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
* @deprecated
|
||||
*/
|
||||
public $id = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $entityType;
|
||||
|
||||
private $isNotNew = false;
|
||||
private bool $isNotNew = false;
|
||||
|
||||
private $isSaved = false;
|
||||
private bool $isSaved = false;
|
||||
|
||||
private $isFetched = false;
|
||||
private bool $isFetched = false;
|
||||
|
||||
private $isBeingSaved = false;
|
||||
private bool $isBeingSaved = false;
|
||||
|
||||
/**
|
||||
* @todo Make private. Rename to `attributes`.
|
||||
@@ -528,7 +535,7 @@ class BaseEntity implements Entity
|
||||
$preparedValue = $value;
|
||||
|
||||
if (is_array($value)) {
|
||||
$preparedValue = json_decode(json_encode($value));
|
||||
$preparedValue = json_decode(json_encode($value, \JSON_THROW_ON_ERROR));
|
||||
|
||||
if ($preparedValue instanceof stdClass) {
|
||||
return $preparedValue;
|
||||
@@ -828,8 +835,11 @@ class BaseEntity implements Entity
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
$type = $this->getAttributeType($name);
|
||||
|
||||
return !self::areValuesEqual(
|
||||
$this->getAttributeType($name),
|
||||
$type,
|
||||
$this->get($name),
|
||||
$this->getFetched($name),
|
||||
$this->getAttributeParam($name, 'isUnordered') ?? false
|
||||
@@ -875,7 +885,7 @@ class BaseEntity implements Entity
|
||||
$a1 = get_object_vars($v1);
|
||||
$a2 = get_object_vars($v2);
|
||||
|
||||
foreach ($v1 as $key => $itemValue) {
|
||||
foreach (get_object_vars($v1) as $key => $itemValue) {
|
||||
if (is_object($a1[$key]) && is_object($a2[$key])) {
|
||||
if (!self::areValuesEqual(self::JSON_OBJECT, $a1[$key], $a2[$key])) {
|
||||
return false;
|
||||
@@ -1015,6 +1025,7 @@ class BaseEntity implements Entity
|
||||
*/
|
||||
protected function getEntityManager(): EntityManager
|
||||
{
|
||||
/** @var EntityManager */
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
@@ -1043,6 +1054,8 @@ class BaseEntity implements Entity
|
||||
|
||||
$copy = [];
|
||||
|
||||
/** @var array<int,stdClass|array|scalar|null> $value */
|
||||
|
||||
foreach ($value as $i => $item) {
|
||||
if (is_object($item)) {
|
||||
$copy[$i] = $this->cloneObject($item);
|
||||
@@ -1074,6 +1087,8 @@ class BaseEntity implements Entity
|
||||
$copy = (object) [];
|
||||
|
||||
foreach (get_object_vars($value) as $k => $item) {
|
||||
/** @var stdClass|array|scalar|null $item */
|
||||
|
||||
$key = $k;
|
||||
|
||||
if (!is_string($key)) {
|
||||
|
||||
@@ -33,16 +33,31 @@ use RuntimeException;
|
||||
|
||||
class EntityDefs
|
||||
{
|
||||
private $data;
|
||||
/**
|
||||
* @var array<string,array<string,mixed>>
|
||||
*/
|
||||
private array $data;
|
||||
|
||||
private $name;
|
||||
private string $name;
|
||||
|
||||
/**
|
||||
* @var array<string,?AttributeDefs>
|
||||
*/
|
||||
private $attributeCache = [];
|
||||
|
||||
/**
|
||||
* @var array<string,?RelationDefs>
|
||||
*/
|
||||
private $relationCache = [];
|
||||
|
||||
/**
|
||||
* @var array<string,?IndexDefs>
|
||||
*/
|
||||
private $indexCache = [];
|
||||
|
||||
/**
|
||||
* @var array<string,?FieldDefs>
|
||||
*/
|
||||
private $fieldCache = [];
|
||||
|
||||
private function __construct()
|
||||
@@ -224,6 +239,7 @@ class EntityDefs
|
||||
throw new RuntimeException("Attribute '{$name}' does not exist.");
|
||||
}
|
||||
|
||||
/** @var AttributeDefs */
|
||||
return $this->attributeCache[$name];
|
||||
}
|
||||
|
||||
@@ -239,6 +255,7 @@ class EntityDefs
|
||||
throw new RuntimeException("Relation '{$name}' does not exist.");
|
||||
}
|
||||
|
||||
/** @var RelationDefs */
|
||||
return $this->relationCache[$name];
|
||||
}
|
||||
|
||||
@@ -254,6 +271,7 @@ class EntityDefs
|
||||
throw new RuntimeException("Index '{$name}' does not exist.");
|
||||
}
|
||||
|
||||
/** @var IndexDefs */
|
||||
return $this->indexCache[$name];
|
||||
}
|
||||
|
||||
@@ -269,6 +287,7 @@ class EntityDefs
|
||||
throw new RuntimeException("Field '{$name}' does not exist.");
|
||||
}
|
||||
|
||||
/** @var FieldDefs */
|
||||
return $this->fieldCache[$name];
|
||||
}
|
||||
|
||||
|
||||
@@ -221,6 +221,8 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
|
||||
throw new RuntimeException("Can't build from array. EntityFactory was not passed to the constructor.");
|
||||
}
|
||||
|
||||
assert($this->entityType !== null);
|
||||
|
||||
$entity = $this->entityFactory->create($this->entityType);
|
||||
|
||||
$entity->set($dataArray);
|
||||
|
||||
@@ -151,9 +151,12 @@ class EntityManager
|
||||
$this->initLocker();
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Use factory.
|
||||
*/
|
||||
private function initQueryComposer(): void
|
||||
{
|
||||
$platform = $this->databaseParams->getPlatform();
|
||||
$platform = $this->databaseParams->getPlatform() ?? 'Dummy';
|
||||
|
||||
$className = 'Espo\\ORM\\QueryComposer\\' . ucfirst($platform) . 'QueryComposer';
|
||||
|
||||
@@ -161,17 +164,23 @@ class EntityManager
|
||||
throw new RuntimeException("Query composer for '{$platform}' platform does not exits.");
|
||||
}
|
||||
|
||||
$this->queryComposer = new $className(
|
||||
/** @var QueryComposer */
|
||||
$queryComposer = new $className(
|
||||
$this->pdoProvider->get(),
|
||||
$this->entityFactory,
|
||||
$this->metadata,
|
||||
$this->functionConverterFactory
|
||||
);
|
||||
|
||||
$this->queryComposer = $queryComposer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Use factory.
|
||||
*/
|
||||
private function initLocker(): void
|
||||
{
|
||||
$platform = $this->databaseParams->getPlatform();
|
||||
$platform = $this->databaseParams->getPlatform() ?? 'Dummy';
|
||||
|
||||
$className = 'Espo\\ORM\\Locker\\' . ucfirst($platform) . 'Locker';
|
||||
|
||||
@@ -179,7 +188,10 @@ class EntityManager
|
||||
$className = BaseLocker::class;
|
||||
}
|
||||
|
||||
$this->locker = new $className($this->pdoProvider->get(), $this->queryComposer, $this->transactionManager);
|
||||
/** @var Locker */
|
||||
$locker = new $className($this->pdoProvider->get(), $this->queryComposer, $this->transactionManager);
|
||||
|
||||
$this->locker = $locker;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,7 +235,8 @@ class EntityManager
|
||||
if ($name === self::RDB_MAPPER_NAME) {
|
||||
$className = $this->getRDBMapperClassName();
|
||||
|
||||
$this->mappers[$name] = new $className(
|
||||
/** @var Mapper */
|
||||
$mapper = new $className(
|
||||
$this->pdoProvider->get(),
|
||||
$this->entityFactory,
|
||||
$this->collectionFactory,
|
||||
@@ -232,6 +245,8 @@ class EntityManager
|
||||
$this->sqlExecutor
|
||||
);
|
||||
|
||||
$this->mappers[$name] = $mapper;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -244,7 +259,7 @@ class EntityManager
|
||||
|
||||
private function getRDBMapperClassName(): string
|
||||
{
|
||||
$platform = $this->databaseParams->getPlatform();
|
||||
$platform = $this->databaseParams->getPlatform() ?? 'Dummy';
|
||||
|
||||
$className = 'Espo\\ORM\\Mapper\\' . ucfirst($platform) . 'Mapper';
|
||||
|
||||
@@ -277,6 +292,7 @@ class EntityManager
|
||||
*/
|
||||
public function getNewEntity(string $entityType): Entity
|
||||
{
|
||||
/** @var Entity */
|
||||
return $this->getEntity($entityType);
|
||||
}
|
||||
|
||||
@@ -352,7 +368,7 @@ class EntityManager
|
||||
*/
|
||||
public function createEntity(string $entityType, $data = [], array $options = []): Entity
|
||||
{
|
||||
$entity = $this->getEntity($entityType);
|
||||
$entity = $this->getNewEntity($entityType);
|
||||
|
||||
$entity->set($data);
|
||||
|
||||
@@ -384,7 +400,13 @@ class EntityManager
|
||||
$this->repositoryHash[$entityType] = $this->repositoryFactory->create($entityType);
|
||||
}
|
||||
|
||||
return $this->repositoryHash[$entityType];
|
||||
$repository = $this->repositoryHash[$entityType];
|
||||
|
||||
if (!$repository instanceof RDBRepository) {
|
||||
throw new RuntimeException("Repository '{$entityType}' is not RDB.");
|
||||
}
|
||||
|
||||
return $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,46 +55,25 @@ use RuntimeException;
|
||||
*/
|
||||
class BaseMapper implements RDBMapper
|
||||
{
|
||||
const ATTRIBUTE_DELETED = 'deleted';
|
||||
protected const ATTRIBUTE_DELETED = 'deleted';
|
||||
|
||||
protected $fieldsMapCache = [];
|
||||
protected array $fieldsMapCache = [];
|
||||
|
||||
protected $aliasesCache = [];
|
||||
protected array $aliasesCache = [];
|
||||
|
||||
/**
|
||||
* @var PDO
|
||||
*/
|
||||
protected $pdo;
|
||||
protected PDO $pdo;
|
||||
|
||||
/**
|
||||
* @var EntityFactory
|
||||
*/
|
||||
protected $entityFactory;
|
||||
protected EntityFactory $entityFactory;
|
||||
|
||||
/**
|
||||
* @var CollectionFactory
|
||||
*/
|
||||
protected $collectionFactory;
|
||||
protected CollectionFactory $collectionFactory;
|
||||
|
||||
/**
|
||||
* @var QueryComposer
|
||||
*/
|
||||
protected $queryComposer;
|
||||
protected QueryComposer $queryComposer;
|
||||
|
||||
/**
|
||||
* @var Metadata
|
||||
*/
|
||||
protected $metadata;
|
||||
protected Metadata $metadata;
|
||||
|
||||
/**
|
||||
* @var SqlExecutor
|
||||
*/
|
||||
protected $sqlExecutor;
|
||||
protected SqlExecutor $sqlExecutor;
|
||||
|
||||
/**
|
||||
* @var Helper
|
||||
*/
|
||||
protected $helper;
|
||||
protected Helper $helper;
|
||||
|
||||
public function __construct(
|
||||
PDO $pdo,
|
||||
@@ -121,6 +100,10 @@ class BaseMapper implements RDBMapper
|
||||
{
|
||||
$entityType = $select->getFrom();
|
||||
|
||||
if ($entityType === null) {
|
||||
throw new RuntimeException("No entity type.");
|
||||
}
|
||||
|
||||
$entity = $this->entityFactory->create($entityType);
|
||||
|
||||
$sql = $this->queryComposer->compose($select);
|
||||
@@ -191,6 +174,10 @@ class BaseMapper implements RDBMapper
|
||||
{
|
||||
$entityType = $select->getFrom();
|
||||
|
||||
if ($entityType === null) {
|
||||
throw new RuntimeException("No entity type.");
|
||||
}
|
||||
|
||||
$sql = $this->queryComposer->compose($select);
|
||||
|
||||
return $this->selectBySqlInternal($entityType, $sql);
|
||||
@@ -213,6 +200,10 @@ class BaseMapper implements RDBMapper
|
||||
{
|
||||
$entityType = $select->getFrom();
|
||||
|
||||
if ($entityType === null) {
|
||||
throw new RuntimeException("No entity type.");
|
||||
}
|
||||
|
||||
$entity = $this->entityFactory->create($entityType);
|
||||
|
||||
if (empty($aggregation) || !$entity->hasAttribute($aggregationBy)) {
|
||||
@@ -299,6 +290,7 @@ class BaseMapper implements RDBMapper
|
||||
|
||||
switch ($relType) {
|
||||
case Entity::BELONGS_TO:
|
||||
/** @var Entity $relEntity */
|
||||
|
||||
$params['whereClause'][$foreignKey] = $entity->get($key);
|
||||
$params['offset'] = 0;
|
||||
@@ -352,6 +344,8 @@ class BaseMapper implements RDBMapper
|
||||
|
||||
$resultDataList = [];
|
||||
|
||||
/** @var Entity $relEntity */
|
||||
|
||||
$params['from'] = $relEntity->getEntityType();
|
||||
|
||||
$sql = $this->queryComposer->compose(Select::fromRaw($params));
|
||||
@@ -394,6 +388,8 @@ class BaseMapper implements RDBMapper
|
||||
$params['select'] ?? []
|
||||
);
|
||||
|
||||
/** @var Entity $relEntity */
|
||||
|
||||
$params['from'] = $relEntity->getEntityType();
|
||||
|
||||
$sql = $this->queryComposer->compose(Select::fromRaw($params));
|
||||
@@ -1155,6 +1151,8 @@ class BaseMapper implements RDBMapper
|
||||
|
||||
$where[static::ATTRIBUTE_DELETED] = false;
|
||||
|
||||
/** @var Entity $relEntity */
|
||||
|
||||
$sql = $this->queryComposer->compose(
|
||||
Update::fromRaw([
|
||||
'from' => $relEntity->getEntityType(),
|
||||
|
||||
@@ -123,9 +123,12 @@ class Metadata
|
||||
return array_keys($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[]|string|null $key
|
||||
*/
|
||||
private static function getValueByKey(array $data, $key = null, $default = null)
|
||||
{
|
||||
if (!is_string($key) && !is_array($key) && !is_null($key)) {
|
||||
if (!is_string($key) && !is_array($key) && !is_null($key)) { /** @phpstan-ignore-line */
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
@@ -139,6 +142,8 @@ class Metadata
|
||||
$path = explode('.', $key);
|
||||
}
|
||||
|
||||
/** @var string[] $path */
|
||||
|
||||
$item = $data;
|
||||
|
||||
foreach ($path as $k) {
|
||||
|
||||
@@ -54,6 +54,8 @@ class DefaultPDOProvider implements PDOProvider
|
||||
$this->intPDO();
|
||||
}
|
||||
|
||||
assert($this->pdo !== null);
|
||||
|
||||
return $this->pdo;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ use PDO;
|
||||
class Options
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
* @return array<int, mixed>
|
||||
*/
|
||||
public static function getOptionsFromDatabaseParams(DatabaseParams $databaseParams): array
|
||||
{
|
||||
|
||||
@@ -242,8 +242,8 @@ class SelectBuilder implements Builder
|
||||
* * `having(array $clause)`
|
||||
* * `having(string $key, string $value)`
|
||||
*
|
||||
* @param WhereItem|array|string $clause A key or where clause.
|
||||
* @param array|string|null $value A value. Omitted if the first argument is not string.
|
||||
* @param WhereItem|array<mixed,mixed>|string $clause A key or where clause.
|
||||
* @param mixed[]|scalar|null $value A value. Omitted if the first argument is not string.
|
||||
*/
|
||||
public function having($clause, $value = null): self
|
||||
{
|
||||
|
||||
@@ -49,8 +49,8 @@ trait SelectingBuilderTrait
|
||||
* * `where(array $clause)`
|
||||
* * `where(string $key, string $value)`
|
||||
*
|
||||
* @param WhereItem|array|string $clause A key or where clause.
|
||||
* @param array|string|null $value A value. Omitted if the first argument is not string.
|
||||
* @param WhereItem|array<mixed,mixed>|string $clause A key or where clause.
|
||||
* @param mixed[]|scalar|null $value A value. Omitted if the first argument is not string.
|
||||
*/
|
||||
public function where($clause, $value = null): self
|
||||
{
|
||||
|
||||
@@ -613,6 +613,7 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
$selectPart = $this->getSelectPart($entity, $params);
|
||||
|
||||
$additionalSelectPart = $this->getAdditionalSelect($entity, $params);
|
||||
|
||||
if ($additionalSelectPart) {
|
||||
$selectPart .= $additionalSelectPart;
|
||||
}
|
||||
@@ -630,7 +631,11 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
$params['select'] = [];
|
||||
|
||||
$selectPart = $this->getAggregationSelectPart(
|
||||
$entity, $params['aggregation'], $params['aggregationBy'], $aggregationDistinct, $params
|
||||
$entity,
|
||||
$params['aggregation'],
|
||||
$params['aggregationBy'],
|
||||
$aggregationDistinct,
|
||||
$params
|
||||
);
|
||||
}
|
||||
|
||||
@@ -639,6 +644,7 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
if ($wherePart) {
|
||||
$wherePart .= ' ';
|
||||
}
|
||||
|
||||
$wherePart .= $params['customWhere'];
|
||||
}
|
||||
|
||||
@@ -679,6 +685,9 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
$fromPart = '(' . $this->composeSelecting($fromQuery) . ')';
|
||||
}
|
||||
|
||||
/** @var string $selectPart */
|
||||
/** @var string $fromAlias */
|
||||
|
||||
if ($isAggregation) {
|
||||
$sql = $this->composeSelectQuery(
|
||||
$fromPart,
|
||||
@@ -880,13 +889,16 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
return $selectPart;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $argumentPartList
|
||||
*/
|
||||
protected function getFunctionPart(
|
||||
string $function,
|
||||
string $part,
|
||||
array $params,
|
||||
string $entityType,
|
||||
bool $distinct = false,
|
||||
?array $argumentPartList = null
|
||||
bool $distinct,
|
||||
array $argumentPartList = []
|
||||
): string {
|
||||
|
||||
$isBuiltIn = in_array($function, Functions::FUNCTION_LIST);
|
||||
@@ -1084,6 +1096,8 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
|
||||
private function getFunctionPartFromFactory(string $function, array $argumentPartList): string
|
||||
{
|
||||
assert($this->functionConverterFactory !== null);
|
||||
|
||||
$obj = $this->functionConverterFactory->create($function);
|
||||
|
||||
return $obj->convert(...$argumentPartList);
|
||||
@@ -1233,7 +1247,16 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
}
|
||||
|
||||
if ($function) {
|
||||
$part = $this->getFunctionPart($function, $part, $params, $entityType, $distinct, $argumentPartList);
|
||||
/** @var string[] $argumentPartList */
|
||||
|
||||
$part = $this->getFunctionPart(
|
||||
$function,
|
||||
$part,
|
||||
$params,
|
||||
$entityType,
|
||||
$distinct,
|
||||
$argumentPartList
|
||||
);
|
||||
}
|
||||
|
||||
return $part;
|
||||
@@ -1408,6 +1431,7 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
$modifiedOrder[] = $newItem;
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
$part = $this->getOrderExpressionPart($entity, $modifiedOrder, null, $params, true);
|
||||
|
||||
return $part;
|
||||
@@ -1527,7 +1551,7 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
if (!empty($defs['joins'])) {
|
||||
foreach ($defs['joins'] as $j) {
|
||||
$jAlias = $this->obtainJoinAlias($j);
|
||||
$jAlias = str_replace('{alias}', $alias, $jAlias);
|
||||
$jAlias = str_replace('{alias}', $alias ?? '', $jAlias);
|
||||
|
||||
if (isset($j[1])) {
|
||||
$j[1] = $jAlias;
|
||||
@@ -1567,7 +1591,7 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
$params['extraAdditionalSelect'] = $params['extraAdditionalSelect'] ?? [];
|
||||
|
||||
foreach ($defs['additionalSelect'] as $value) {
|
||||
$value = str_replace('{alias}', $alias, $value);
|
||||
$value = str_replace('{alias}', $alias ?? '', $value);
|
||||
$value = str_replace('{attribute}', $attribute, $value);
|
||||
|
||||
if (!in_array($value, $params['extraAdditionalSelect'])) {
|
||||
@@ -1725,6 +1749,9 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
return $selectPart;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|string[] $attribute
|
||||
*/
|
||||
protected function getSelectPartItemPair(?Entity $entity, array &$params, $attribute): ?array
|
||||
{
|
||||
$maxTextColumnsLength = $params['maxTextColumnsLength'] ?? null;
|
||||
@@ -1733,7 +1760,7 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
|
||||
$attributeType = null;
|
||||
|
||||
if (!is_array($attribute) && !is_string($attribute)) {
|
||||
if (!is_array($attribute) && !is_string($attribute)) { /** @phpstan-ignore-line */
|
||||
throw new RuntimeException("ORM Query: Bad select item.");
|
||||
}
|
||||
|
||||
@@ -1759,7 +1786,7 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
}
|
||||
|
||||
// @todo Make VALUE: usage deprecated.
|
||||
if (stripos($expression, 'VALUE:') === 0) {
|
||||
if (is_string($expression) && stripos($expression, 'VALUE:') === 0) {
|
||||
$part = $this->quote(
|
||||
substr($expression, 6)
|
||||
);
|
||||
@@ -1768,6 +1795,10 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
}
|
||||
|
||||
if (!$entity) {
|
||||
if (!is_string($expression)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
return [
|
||||
$this->convertComplexExpression(null, $expression, false, $params),
|
||||
$alias
|
||||
@@ -1804,6 +1835,10 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
return [$part, $alias];
|
||||
}
|
||||
|
||||
if (!is_string($attribute)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
if (!$entity->hasAttribute($attribute)) {
|
||||
$expression = $attribute;
|
||||
|
||||
@@ -2048,7 +2083,7 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
if (strpos($orderBy, 'LIST:') === 0) {
|
||||
list($l, $field, $list) = explode(':', $orderBy);
|
||||
|
||||
$fieldPath = $this->getAttributePathForOrderBy($entity, $field, $params);
|
||||
$fieldPath = $this->getAttributePathForOrderBy($entity, $field, $params ?? []);
|
||||
|
||||
$listQuoted = [];
|
||||
|
||||
@@ -2088,7 +2123,7 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
return $this->getAttributeOrderSql($entity, $orderBy, $params, $order);
|
||||
}
|
||||
|
||||
$fieldPath = $this->getAttributePathForOrderBy($entity, $orderBy, $params);
|
||||
$fieldPath = $this->getAttributePathForOrderBy($entity, $orderBy, $params ?? []);
|
||||
|
||||
if (!$fieldPath) {
|
||||
throw new LogicException("Could not handle 'order' for '".$entity->getEntityType()."'.");
|
||||
@@ -2697,7 +2732,7 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
*/
|
||||
public function sanitize(string $string): string
|
||||
{
|
||||
return preg_replace('/[^A-Za-z0-9_]+/', '', $string);
|
||||
return preg_replace('/[^A-Za-z0-9_]+/', '', $string) ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2707,7 +2742,7 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
*/
|
||||
public function sanitizeSelectAlias(string $string): string
|
||||
{
|
||||
$string = preg_replace('/[^A-Za-z\r\n0-9_:\'" .,\-\(\)]+/', '', $string);
|
||||
$string = preg_replace('/[^A-Za-z\r\n0-9_:\'" .,\-\(\)]+/', '', $string) ?? '';
|
||||
|
||||
if (strlen($string) > 256) {
|
||||
$string = substr($string, 0, 256);
|
||||
@@ -2718,7 +2753,7 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
|
||||
protected function sanitizeIndexName(string $string): string
|
||||
{
|
||||
return preg_replace('/[^A-Za-z0-9_]+/', '', $string);
|
||||
return preg_replace('/[^A-Za-z0-9_]+/', '', $string) ?? '';
|
||||
}
|
||||
|
||||
protected function getJoinsTypePart(
|
||||
|
||||
@@ -29,9 +29,8 @@
|
||||
|
||||
namespace Espo\ORM\QueryComposer;
|
||||
|
||||
use Espo\ORM\{
|
||||
Query\LockTable as LockTableQuery,
|
||||
};
|
||||
use Espo\ORM\Query\LockTable as LockTableQuery;
|
||||
|
||||
use LogicException;
|
||||
|
||||
class MysqlQueryComposer extends BaseQueryComposer
|
||||
|
||||
@@ -49,21 +49,21 @@ use RuntimeException;
|
||||
*/
|
||||
class RDBRelation
|
||||
{
|
||||
private $entityManager;
|
||||
private EntityManager $entityManager;
|
||||
|
||||
private $hookMediator;
|
||||
private HookMediator $hookMediator;
|
||||
|
||||
private $entity;
|
||||
private Entity $entity;
|
||||
|
||||
private $entityType;
|
||||
private string $entityType;
|
||||
|
||||
private $foreignEntityType = null;
|
||||
private ?string $foreignEntityType = null;
|
||||
|
||||
private $relationName;
|
||||
private string $relationName;
|
||||
|
||||
private $relationType = null;
|
||||
private ?string $relationType = null;
|
||||
|
||||
private $noBuilder = false;
|
||||
private bool $noBuilder = false;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
@@ -178,6 +178,7 @@ class RDBRelation
|
||||
public function findOne(): ?Entity
|
||||
{
|
||||
if ($this->isBelongsToParentType()) {
|
||||
/** @var ?Entity */
|
||||
return $this->getMapper()->selectRelated($this->entity, $this->relationName);
|
||||
}
|
||||
|
||||
@@ -445,7 +446,10 @@ class RDBRelation
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$seed = $this->entityManager->getEntityFactory()->create($this->foreignEntityType);
|
||||
/** @var string */
|
||||
$foreignEntityType = $this->foreignEntityType;
|
||||
|
||||
$seed = $this->entityManager->getEntityFactory()->create($foreignEntityType);
|
||||
|
||||
$seed->set('id', $id);
|
||||
|
||||
@@ -465,7 +469,10 @@ class RDBRelation
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$seed = $this->entityManager->getEntityFactory()->create($this->foreignEntityType);
|
||||
/** @var string */
|
||||
$foreignEntityType = $this->foreignEntityType;
|
||||
|
||||
$seed = $this->entityManager->getEntityFactory()->create($foreignEntityType);
|
||||
|
||||
$seed->set('id', $id);
|
||||
|
||||
@@ -485,7 +492,11 @@ class RDBRelation
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$seed = $this->entityManager->getEntityFactory()->create($this->foreignEntityType);
|
||||
/** @var string */
|
||||
$foreignEntityType = $this->foreignEntityType;
|
||||
|
||||
$seed = $this->entityManager->getEntityFactory()->create($foreignEntityType);
|
||||
|
||||
$seed->set('id', $id);
|
||||
|
||||
$this->updateColumns($seed, $columnData);
|
||||
@@ -551,7 +562,13 @@ class RDBRelation
|
||||
throw new RuntimeException("Can't update not many-to-many relation.");
|
||||
}
|
||||
|
||||
$this->getMapper()->updateRelationColumns($this->entity, $this->relationName, $entity->getId(), $columnData);
|
||||
$id = $entity->getId();
|
||||
|
||||
if ($id === null) {
|
||||
throw new RuntimeException("Entity w/o ID.");
|
||||
}
|
||||
|
||||
$this->getMapper()->updateRelationColumns($this->entity, $this->relationName, $id, $columnData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -567,7 +584,13 @@ class RDBRelation
|
||||
throw new RuntimeException("Can't get a column of not many-to-many relation.");
|
||||
}
|
||||
|
||||
return $this->getMapper()->getRelationColumn($this->entity, $this->relationName, $entity->getId(), $column);
|
||||
$id = $entity->getId();
|
||||
|
||||
if ($id === null) {
|
||||
throw new RuntimeException("Entity w/o ID.");
|
||||
}
|
||||
|
||||
return $this->getMapper()->getRelationColumn($this->entity, $this->relationName, $id, $column);
|
||||
}
|
||||
|
||||
private function beforeRelate(Entity $entity, ?array $columnData, array $options): void
|
||||
|
||||
@@ -60,7 +60,7 @@ class RDBRepository implements Repository
|
||||
|
||||
protected EntityFactory $entityFactory;
|
||||
|
||||
protected ?HookMediator $hookMediator;
|
||||
protected HookMediator $hookMediator;
|
||||
|
||||
protected RDBTransactionManager $transactionManager;
|
||||
|
||||
@@ -320,16 +320,28 @@ class RDBRepository implements Repository
|
||||
}
|
||||
|
||||
if ($type === Entity::MANY_MANY && count($additionalColumns)) {
|
||||
if ($select === null) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$select = $this->applyRelationAdditionalColumns($entity, $relationName, $additionalColumns, $select);
|
||||
}
|
||||
|
||||
// @todo Get rid of 'additionalColumnsConditions' usage. Use 'whereClause' instead.
|
||||
if ($type === Entity::MANY_MANY && count($additionalColumnsConditions)) {
|
||||
if ($select === null) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$select = $this->applyRelationAdditionalColumnsConditions(
|
||||
$entity, $relationName, $additionalColumnsConditions, $select
|
||||
$entity,
|
||||
$relationName,
|
||||
$additionalColumnsConditions,
|
||||
$select
|
||||
);
|
||||
}
|
||||
|
||||
/** @var Collection<TEntity>|TEntity|null */
|
||||
$result = $this->getMapper()->selectRelated($entity, $relationName, $select);
|
||||
|
||||
if ($result instanceof SthCollection) {
|
||||
@@ -373,8 +385,15 @@ class RDBRepository implements Repository
|
||||
}
|
||||
|
||||
if ($type === Entity::MANY_MANY && count($additionalColumnsConditions)) {
|
||||
if ($select === null) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$select = $this->applyRelationAdditionalColumnsConditions(
|
||||
$entity, $relationName, $additionalColumnsConditions, $select
|
||||
$entity,
|
||||
$relationName,
|
||||
$additionalColumnsConditions,
|
||||
$select
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -57,9 +57,9 @@ class RDBSelectBuilder
|
||||
private SelectBuilder $builder;
|
||||
|
||||
/**
|
||||
* @var RDBRepository<TEntity>|null
|
||||
* @var RDBRepository<TEntity>
|
||||
*/
|
||||
private ?RDBRepository $repository = null;
|
||||
private RDBRepository $repository;
|
||||
|
||||
private bool $returnSthCollection = false;
|
||||
|
||||
@@ -67,7 +67,10 @@ class RDBSelectBuilder
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
|
||||
$this->repository = $this->entityManager->getRepository($entityType);
|
||||
/** @var RDBRepository<TEntity> */
|
||||
$repository = $this->entityManager->getRepository($entityType);
|
||||
|
||||
$this->repository = $repository;
|
||||
|
||||
if ($query && $query->getFrom() !== $entityType) {
|
||||
throw new RuntimeException("SelectBuilder: Passed query doesn't match the entity type.");
|
||||
@@ -114,6 +117,7 @@ class RDBSelectBuilder
|
||||
|
||||
if ($params !== null) { // @todo Remove.
|
||||
$query = $this->getMergedParams($params);
|
||||
|
||||
$builder = $this->repository->clone($query);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ use Countable;
|
||||
use Traversable;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Reasonable to use when selecting a large number of records.
|
||||
@@ -106,6 +107,7 @@ class SthCollection implements Collection, IteratorAggregate, Countable
|
||||
|
||||
private function getQuery(): SelectQuery
|
||||
{
|
||||
/** @var SelectQuery */
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
@@ -145,6 +147,8 @@ class SthCollection implements Collection, IteratorAggregate, Countable
|
||||
{
|
||||
$this->executeQueryIfNotExecuted();
|
||||
|
||||
assert($this->sth !== null);
|
||||
|
||||
return $this->sth->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
@@ -152,6 +156,8 @@ class SthCollection implements Collection, IteratorAggregate, Countable
|
||||
{
|
||||
$this->executeQueryIfNotExecuted();
|
||||
|
||||
assert($this->sth !== null);
|
||||
|
||||
$rowCount = $this->sth->rowCount();
|
||||
|
||||
// MySQL may not return a row count for select queries.
|
||||
@@ -193,6 +199,7 @@ class SthCollection implements Collection, IteratorAggregate, Countable
|
||||
|
||||
public function getValueMapList(): array
|
||||
{
|
||||
/** @var \stdClass[] */
|
||||
return $this->toArray(true);
|
||||
}
|
||||
|
||||
@@ -221,7 +228,13 @@ class SthCollection implements Collection, IteratorAggregate, Countable
|
||||
/** @var self<Entity> */
|
||||
$obj = new self($entityManager);
|
||||
|
||||
$obj->entityType = $query->getFrom();
|
||||
$entityType = $query->getFrom();
|
||||
|
||||
if ($entityType === null) {
|
||||
throw new RuntimeException("Query w/o entity type.");
|
||||
}
|
||||
|
||||
$obj->entityType = $entityType;
|
||||
$obj->query = $query;
|
||||
|
||||
return $obj;
|
||||
|
||||
@@ -39,7 +39,7 @@ class GeneralAttributeExtractor
|
||||
private AttributeExtractorFactory $factory;
|
||||
|
||||
/**
|
||||
* @var array<string,AttributeExtractorFactory<object>>
|
||||
* @var array<string,AttributeExtractor<object>>
|
||||
*/
|
||||
private $cache = [];
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class GeneralValueFactory
|
||||
private ValueFactoryFactory $valueFactoryFactory;
|
||||
|
||||
/**
|
||||
* @var array<string,ValueFactory>
|
||||
* @var array<string,?ValueFactory>
|
||||
*/
|
||||
private array $factoryCache = [];
|
||||
|
||||
@@ -74,6 +74,7 @@ class GeneralValueFactory
|
||||
throw new RuntimeException("No value-object factory for '{$entityType}.{$field}'.");
|
||||
}
|
||||
|
||||
/** @var ValueFactory */
|
||||
return $factory->createFromEntity($entity, $field);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user