diff --git a/application/Espo/ORM/BaseEntity.php b/application/Espo/ORM/BaseEntity.php index 3270a15023..d8cf6faf86 100644 --- a/application/Espo/ORM/BaseEntity.php +++ b/application/Espo/ORM/BaseEntity.php @@ -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 $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)) { diff --git a/application/Espo/ORM/Defs/EntityDefs.php b/application/Espo/ORM/Defs/EntityDefs.php index eb9cd843c3..382e0dea4d 100644 --- a/application/Espo/ORM/Defs/EntityDefs.php +++ b/application/Espo/ORM/Defs/EntityDefs.php @@ -33,16 +33,31 @@ use RuntimeException; class EntityDefs { - private $data; + /** + * @var array> + */ + private array $data; - private $name; + private string $name; + /** + * @var array + */ private $attributeCache = []; + /** + * @var array + */ private $relationCache = []; + /** + * @var array + */ private $indexCache = []; + /** + * @var array + */ 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]; } diff --git a/application/Espo/ORM/EntityCollection.php b/application/Espo/ORM/EntityCollection.php index c3d9dfe867..eaffdef794 100644 --- a/application/Espo/ORM/EntityCollection.php +++ b/application/Espo/ORM/EntityCollection.php @@ -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); diff --git a/application/Espo/ORM/EntityManager.php b/application/Espo/ORM/EntityManager.php index 223840c5fd..1376255669 100644 --- a/application/Espo/ORM/EntityManager.php +++ b/application/Espo/ORM/EntityManager.php @@ -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; } /** diff --git a/application/Espo/ORM/Mapper/BaseMapper.php b/application/Espo/ORM/Mapper/BaseMapper.php index 1da63fdd3e..7e3562d250 100644 --- a/application/Espo/ORM/Mapper/BaseMapper.php +++ b/application/Espo/ORM/Mapper/BaseMapper.php @@ -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(), diff --git a/application/Espo/ORM/Metadata.php b/application/Espo/ORM/Metadata.php index abdcdcb88e..dd6baa9ff7 100644 --- a/application/Espo/ORM/Metadata.php +++ b/application/Espo/ORM/Metadata.php @@ -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) { diff --git a/application/Espo/ORM/PDO/DefaultPDOProvider.php b/application/Espo/ORM/PDO/DefaultPDOProvider.php index 8e1d0fe1b4..977743ed72 100644 --- a/application/Espo/ORM/PDO/DefaultPDOProvider.php +++ b/application/Espo/ORM/PDO/DefaultPDOProvider.php @@ -54,6 +54,8 @@ class DefaultPDOProvider implements PDOProvider $this->intPDO(); } + assert($this->pdo !== null); + return $this->pdo; } diff --git a/application/Espo/ORM/PDO/Options.php b/application/Espo/ORM/PDO/Options.php index 1e03cf0206..4a29ac0960 100644 --- a/application/Espo/ORM/PDO/Options.php +++ b/application/Espo/ORM/PDO/Options.php @@ -36,7 +36,7 @@ use PDO; class Options { /** - * @return array + * @return array */ public static function getOptionsFromDatabaseParams(DatabaseParams $databaseParams): array { diff --git a/application/Espo/ORM/Query/SelectBuilder.php b/application/Espo/ORM/Query/SelectBuilder.php index 3fd2ff0c59..8ebbde1b31 100644 --- a/application/Espo/ORM/Query/SelectBuilder.php +++ b/application/Espo/ORM/Query/SelectBuilder.php @@ -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|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 { diff --git a/application/Espo/ORM/Query/SelectingBuilderTrait.php b/application/Espo/ORM/Query/SelectingBuilderTrait.php index 25ee6f852e..874def7a78 100644 --- a/application/Espo/ORM/Query/SelectingBuilderTrait.php +++ b/application/Espo/ORM/Query/SelectingBuilderTrait.php @@ -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|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 { diff --git a/application/Espo/ORM/QueryComposer/BaseQueryComposer.php b/application/Espo/ORM/QueryComposer/BaseQueryComposer.php index fd9ddeb5c5..a2d2ecac17 100644 --- a/application/Espo/ORM/QueryComposer/BaseQueryComposer.php +++ b/application/Espo/ORM/QueryComposer/BaseQueryComposer.php @@ -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( diff --git a/application/Espo/ORM/QueryComposer/MysqlQueryComposer.php b/application/Espo/ORM/QueryComposer/MysqlQueryComposer.php index be306b6769..4580db2635 100644 --- a/application/Espo/ORM/QueryComposer/MysqlQueryComposer.php +++ b/application/Espo/ORM/QueryComposer/MysqlQueryComposer.php @@ -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 diff --git a/application/Espo/ORM/Repository/RDBRelation.php b/application/Espo/ORM/Repository/RDBRelation.php index 78a3b2f828..10ac093454 100644 --- a/application/Espo/ORM/Repository/RDBRelation.php +++ b/application/Espo/ORM/Repository/RDBRelation.php @@ -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 diff --git a/application/Espo/ORM/Repository/RDBRepository.php b/application/Espo/ORM/Repository/RDBRepository.php index d1df778337..fe2a1fe088 100644 --- a/application/Espo/ORM/Repository/RDBRepository.php +++ b/application/Espo/ORM/Repository/RDBRepository.php @@ -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|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 ); } diff --git a/application/Espo/ORM/Repository/RDBSelectBuilder.php b/application/Espo/ORM/Repository/RDBSelectBuilder.php index 2c0c2cc4f1..e233d631c3 100644 --- a/application/Espo/ORM/Repository/RDBSelectBuilder.php +++ b/application/Espo/ORM/Repository/RDBSelectBuilder.php @@ -57,9 +57,9 @@ class RDBSelectBuilder private SelectBuilder $builder; /** - * @var RDBRepository|null + * @var RDBRepository */ - 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 */ + $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); } diff --git a/application/Espo/ORM/SthCollection.php b/application/Espo/ORM/SthCollection.php index 658dbe4ceb..e841cbf994 100644 --- a/application/Espo/ORM/SthCollection.php +++ b/application/Espo/ORM/SthCollection.php @@ -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 */ $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; diff --git a/application/Espo/ORM/Value/GeneralAttributeExtractor.php b/application/Espo/ORM/Value/GeneralAttributeExtractor.php index 3b115f03b9..36aa95b48c 100644 --- a/application/Espo/ORM/Value/GeneralAttributeExtractor.php +++ b/application/Espo/ORM/Value/GeneralAttributeExtractor.php @@ -39,7 +39,7 @@ class GeneralAttributeExtractor private AttributeExtractorFactory $factory; /** - * @var array> + * @var array> */ private $cache = []; diff --git a/application/Espo/ORM/Value/GeneralValueFactory.php b/application/Espo/ORM/Value/GeneralValueFactory.php index c9451035c8..788fa5aaba 100644 --- a/application/Espo/ORM/Value/GeneralValueFactory.php +++ b/application/Espo/ORM/Value/GeneralValueFactory.php @@ -38,7 +38,7 @@ class GeneralValueFactory private ValueFactoryFactory $valueFactoryFactory; /** - * @var array + * @var array */ 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); }