This commit is contained in:
Yuri Kuznetsov
2022-02-17 16:53:09 +02:00
parent 0f020bb3ba
commit e5a0774e3e
18 changed files with 249 additions and 92 deletions
+22 -7
View File
@@ -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)) {
+21 -2
View File
@@ -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);
+30 -8
View File
@@ -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;
}
/**
+29 -31
View File
@@ -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(),
+6 -1
View File
@@ -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;
}
+1 -1
View File
@@ -36,7 +36,7 @@ use PDO;
class Options
{
/**
* @return array<string, mixed>
* @return array<int, mixed>
*/
public static function getOptionsFromDatabaseParams(DatabaseParams $databaseParams): array
{
+2 -2
View File
@@ -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
+36 -13
View File
@@ -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);
}
+14 -1
View File
@@ -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);
}