mirror of
https://github.com/Karaka-Management/phpOMS.git
synced 2026-01-11 17:58:41 +00:00
fix bug and add query yield
This commit is contained in:
parent
e873f909fd
commit
cd9a5cdc11
|
|
@ -202,7 +202,9 @@ abstract class GrammarAbstract
|
|||
$expression .= $element() . (\is_string($key) ? ' as ' . $key : '') . ', ';
|
||||
} elseif ($element instanceof BuilderAbstract) {
|
||||
$expression .= $element->toSql() . (\is_string($key) ? ' as ' . $key : '') . ', ';
|
||||
} else {
|
||||
} elseif (\is_int($element)) {
|
||||
$expression .= $element . ', ';
|
||||
}else {
|
||||
throw new \InvalidArgumentException();
|
||||
}
|
||||
}
|
||||
|
|
@ -226,15 +228,18 @@ abstract class GrammarAbstract
|
|||
$identifierStart = $this->systemIdentifierStart;
|
||||
$identifierEnd = $this->systemIdentifierEnd;
|
||||
|
||||
// @todo: maybe the if/elseif has to get swapped in order. There could be a count(table.name) for example
|
||||
if ((\stripos($system, '.')) !== false) {
|
||||
// The following code could have been handled with \explode more elegantly but \explode needs more memory and more time
|
||||
// Normally this wouldn't be a problem but in this case there are so many function calls to this routine,
|
||||
// that it makes sense to make this "minor" improvement.
|
||||
|
||||
// The order of this if/elseif statement is important!!!
|
||||
if ($system === '*'
|
||||
|| \stripos($system, '(') !== false
|
||||
|| \is_numeric($system)
|
||||
) {
|
||||
$identifierStart = '';
|
||||
$identifierEnd = '';
|
||||
} elseif ((\stripos($system, '.')) !== false) {
|
||||
// This is actually slower than \explode(), despite knowing the first index
|
||||
//$split = [\substr($system, 0, $pos), \substr($system, $pos + 1)];
|
||||
|
||||
// Faster! But might requires more memory?
|
||||
$split = \explode('.', $system);
|
||||
|
||||
$identifierTwoStart = $identifierStart;
|
||||
|
|
@ -248,12 +253,6 @@ abstract class GrammarAbstract
|
|||
return $identifierStart . $split[0] . $identifierEnd
|
||||
. '.'
|
||||
. $identifierTwoStart . $split[1] . $identifierTwoEnd;
|
||||
} elseif ($system === '*'
|
||||
|| \stripos($system, '(') !== false
|
||||
|| \is_numeric($system)
|
||||
) {
|
||||
$identifierStart = '';
|
||||
$identifierEnd = '';
|
||||
}
|
||||
|
||||
return $identifierStart . $system . $identifierEnd;
|
||||
|
|
|
|||
|
|
@ -209,6 +209,23 @@ class DataMapperFactory
|
|||
return $reader->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create read mapper
|
||||
*
|
||||
* @param ConnectionAbstract $db Database connection
|
||||
*
|
||||
* @return ReadMapper<T>
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function yield(ConnectionAbstract $db = null) : ReadMapper
|
||||
{
|
||||
/** @var ReadMapper<T> $reader */
|
||||
$reader = new ReadMapper(new static(), $db ?? self::$db);
|
||||
|
||||
return $reader->yield();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create read mapper
|
||||
*
|
||||
|
|
@ -268,6 +285,20 @@ class DataMapperFactory
|
|||
return (new ReadMapper(new static(), $db ?? self::$db))->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create read mapper
|
||||
*
|
||||
* @param ConnectionAbstract $db Database connection
|
||||
*
|
||||
* @return ReadMapper
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function has(ConnectionAbstract $db = null) : ReadMapper
|
||||
{
|
||||
return (new ReadMapper(new static(), $db ?? self::$db))->has();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create read mapper
|
||||
*
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ abstract class MapperType extends Enum
|
|||
{
|
||||
public const GET = 1;
|
||||
|
||||
public const GET_YIELD = 2;
|
||||
|
||||
public const GET_ALL = 4;
|
||||
|
||||
public const FIND = 7;
|
||||
|
|
@ -40,6 +42,8 @@ abstract class MapperType extends Enum
|
|||
|
||||
public const MODEL_EXISTS = 13;
|
||||
|
||||
public const MODEL_HAS_RELATION = 14;
|
||||
|
||||
// -------------------------------------------- //
|
||||
|
||||
public const CREATE = 1001;
|
||||
|
|
|
|||
|
|
@ -58,6 +58,22 @@ final class ReadMapper extends DataMapperAbstract
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create yield mapper
|
||||
*
|
||||
* This makes execute() return a single object or an array of object depending the result size
|
||||
*
|
||||
* @return self
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function yield() : self
|
||||
{
|
||||
$this->type = MapperType::GET_YIELD;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw result set
|
||||
*
|
||||
|
|
@ -103,7 +119,7 @@ final class ReadMapper extends DataMapperAbstract
|
|||
}
|
||||
|
||||
/**
|
||||
* Create count mapper
|
||||
* Create exists mapper
|
||||
*
|
||||
* @return self
|
||||
*
|
||||
|
|
@ -116,6 +132,20 @@ final class ReadMapper extends DataMapperAbstract
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create has mapper
|
||||
*
|
||||
* @return self
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function has() : self
|
||||
{
|
||||
$this->type = MapperType::MODEL_HAS_RELATION;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create random mapper
|
||||
*
|
||||
|
|
@ -162,6 +192,9 @@ final class ReadMapper extends DataMapperAbstract
|
|||
case MapperType::GET:
|
||||
/** @var null|Builder ...$options */
|
||||
return $this->executeGet(...$options);
|
||||
case MapperType::GET_YIELD:
|
||||
/** @var null|Builder ...$options */
|
||||
return $this->executeGetYield(...$options);
|
||||
case MapperType::GET_RAW:
|
||||
/** @var null|Builder ...$options */
|
||||
return $this->executeGetRaw(...$options);
|
||||
|
|
@ -174,6 +207,8 @@ final class ReadMapper extends DataMapperAbstract
|
|||
return $this->executeCount();
|
||||
case MapperType::MODEL_EXISTS:
|
||||
return $this->executeExists();
|
||||
case MapperType::MODEL_HAS_RELATION:
|
||||
return $this->executeHas();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
@ -204,9 +239,13 @@ final class ReadMapper extends DataMapperAbstract
|
|||
$obj = [];
|
||||
|
||||
// Get remaining objects (not available in memory cache) or remaining where clauses.
|
||||
$dbData = $this->executeGetRaw($query);
|
||||
//$dbData = $this->executeGetRaw($query);
|
||||
|
||||
foreach ($this->executeGetRawYield($query) as $row) {
|
||||
if ($row === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($dbData as $row) {
|
||||
$value = $row[$this->mapper::PRIMARYFIELD . '_d' . $this->depth];
|
||||
$obj[$value] = $this->mapper::createBaseModel($row);
|
||||
|
||||
|
|
@ -225,6 +264,34 @@ final class ReadMapper extends DataMapperAbstract
|
|||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute mapper
|
||||
*
|
||||
* @param null|Builder $query Query to use instead of the internally generated query
|
||||
* Careful, this doesn't merge with the internal query.
|
||||
* If you want to merge it use ->query() instead
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function executeGetYield(Builder $query = null)
|
||||
{
|
||||
$primaryKeys = [];
|
||||
$memberOfPrimaryField = $this->mapper::COLUMNS[$this->mapper::PRIMARYFIELD]['internal'];
|
||||
|
||||
if (isset($this->where[$memberOfPrimaryField])) {
|
||||
$keys = $this->where[$memberOfPrimaryField][0]['value'];
|
||||
$primaryKeys = \array_merge(\is_array($keys) ? $keys : [$keys], $primaryKeys);
|
||||
}
|
||||
|
||||
foreach ($this->executeGetRawYield($query) as $row) {
|
||||
$obj = $this->mapper::createBaseModel($row);
|
||||
$obj = $this->populateAbstract($row, $obj);
|
||||
$this->loadHasManyRelations($obj);
|
||||
|
||||
yield $obj;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute mapper
|
||||
*
|
||||
|
|
@ -263,6 +330,47 @@ final class ReadMapper extends DataMapperAbstract
|
|||
return $results === false ? [] : $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute mapper
|
||||
*
|
||||
* @param null|Builder $query Query to use instead of the internally generated query
|
||||
* Careful, this doesn't merge with the internal query.
|
||||
* If you want to merge it use ->query() instead
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function executeGetRawYield(Builder $query = null)
|
||||
{
|
||||
$query ??= $this->getQuery();
|
||||
|
||||
try {
|
||||
$sth = $this->db->con->prepare($query->toSql());
|
||||
if ($sth === false) {
|
||||
yield [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$sth->execute();
|
||||
|
||||
while ($row = $sth->fetch(\PDO::FETCH_ASSOC)) {
|
||||
yield $row;
|
||||
}
|
||||
} catch (\Throwable $t) {
|
||||
\phpOMS\Log\FileLogger::getInstance()->error(
|
||||
\phpOMS\Log\FileLogger::MSG_FULL, [
|
||||
'message' => $t->getMessage() . ':' . $query->toSql(),
|
||||
'line' => __LINE__,
|
||||
'file' => self::class,
|
||||
]
|
||||
);
|
||||
|
||||
yield [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute mapper
|
||||
*
|
||||
|
|
@ -304,17 +412,33 @@ final class ReadMapper extends DataMapperAbstract
|
|||
/**
|
||||
* Check if any element exists
|
||||
*
|
||||
* @return int
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function executeExists() : bool
|
||||
{
|
||||
$query = $this->getQuery(null, ['1']);
|
||||
$query = $this->getQuery(null, [1]);
|
||||
|
||||
return ($query->execute()?->fetchColumn() ?? 0) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any element exists
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function executeHas() : bool
|
||||
{
|
||||
$obj = isset($this->where[$this->mapper::COLUMNS[$this->mapper::PRIMARYFIELD]['internal']])
|
||||
? $this->mapper::createNullModel($this->where[$this->mapper::COLUMNS[$this->mapper::PRIMARYFIELD]['internal']][0]['value'])
|
||||
: $this->columns([1])->executeGet();
|
||||
|
||||
return $this->hasManyRelations($obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get random object
|
||||
*
|
||||
|
|
@ -348,10 +472,18 @@ final class ReadMapper extends DataMapperAbstract
|
|||
: $columns;
|
||||
|
||||
foreach ($columns as $key => $values) {
|
||||
if (\is_string($values)) {
|
||||
$query->selectAs($key, $values);
|
||||
if (\is_string($values) || \is_int($values)) {
|
||||
if (\is_int($key)) {
|
||||
$query->select($values);
|
||||
} else {
|
||||
$query->selectAs($key, $values);
|
||||
}
|
||||
} elseif (($values['writeonly'] ?? false) === false || isset($this->with[$values['internal']])) {
|
||||
$query->selectAs($this->mapper::TABLE . '_d' . $this->depth . '.' . $key, $key . '_d' . $this->depth);
|
||||
if (\is_int($key)) {
|
||||
$query->select($key);
|
||||
} else {
|
||||
$query->selectAs($this->mapper::TABLE . '_d' . $this->depth . '.' . $key, $key . '_d' . $this->depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -472,7 +604,7 @@ final class ReadMapper extends DataMapperAbstract
|
|||
$where1->where($this->mapper::TABLE . '_d' . $this->depth . '.' . $col, $comparison, $where['value'], 'and');
|
||||
|
||||
$where2 = new Builder($this->db);
|
||||
$where2->select('1')
|
||||
$where2->select('1') // @todo: why is this in quotes?
|
||||
->from($this->mapper::TABLE . '_d' . $this->depth)
|
||||
->where($this->mapper::TABLE . '_d' . $this->depth . '.' . $col, 'in', $alt);
|
||||
|
||||
|
|
@ -991,4 +1123,87 @@ final class ReadMapper extends DataMapperAbstract
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if object has certain relations
|
||||
*
|
||||
* @param object $obj Object to check
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function hasManyRelations(object $obj) : bool
|
||||
{
|
||||
if (empty($this->with)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$primaryKey = $this->mapper::getObjectId($obj);
|
||||
if (empty($primaryKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$refClass = null;
|
||||
|
||||
// @todo: check if there are more cases where the relation is already loaded with joins etc.
|
||||
// there can be pseudo has many elements like localizations. They are has manies but these are already loaded with joins!
|
||||
foreach ($this->with as $member => $withData) {
|
||||
if (isset($this->mapper::HAS_MANY[$member])) {
|
||||
$many = $this->mapper::HAS_MANY[$member];
|
||||
if (isset($many['column'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// @todo: withData doesn't store this directly, it is in [0]['private] ?!?!
|
||||
$isPrivate = $withData['private'] ?? false;
|
||||
|
||||
$objectMapper = $this->createRelationMapper($many['mapper']::exists(db: $this->db), $member);
|
||||
if ($many['external'] === null/* same as $many['table'] !== $many['mapper']::TABLE */) {
|
||||
$objectMapper->where($many['mapper']::COLUMNS[$many['self']]['internal'], $primaryKey);
|
||||
} else {
|
||||
$query = new Builder($this->db, true);
|
||||
$query->leftJoin($many['table'])
|
||||
->on($many['mapper']::TABLE . '_d1.' . $many['mapper']::PRIMARYFIELD, '=', $many['table'] . '.' . $many['external'])
|
||||
->where($many['table'] . '.' . $many['self'], '=', $primaryKey);
|
||||
|
||||
// Cannot use join, because join only works on members and we don't have members for a relation table
|
||||
// This is why we need to create a "base" query which contians the join on table columns
|
||||
$objectMapper->query($query);
|
||||
}
|
||||
|
||||
$objects = $objectMapper->execute();
|
||||
if (empty($objects) || $objects === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} elseif (isset($this->mapper::OWNS_ONE[$member])
|
||||
|| isset($this->mapper::BELONGS_TO[$member])
|
||||
) {
|
||||
$relation = isset($this->mapper::OWNS_ONE[$member])
|
||||
? $this->mapper::OWNS_ONE[$member]
|
||||
: $this->mapper::BELONGS_TO[$member];
|
||||
|
||||
if (\count($withData) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var ReadMapper $relMapper */
|
||||
$relMapper = $this->createRelationMapper($relation['mapper']::reader($this->db), $member);
|
||||
|
||||
$isPrivate = $withData['private'] ?? false;
|
||||
if ($isPrivate) {
|
||||
if ($refClass === null) {
|
||||
$refClass = new \ReflectionClass($obj);
|
||||
}
|
||||
|
||||
$refProp = $refClass->getProperty($member);
|
||||
return $relMapper->hasManyRelations($refProp->getValue($obj));
|
||||
} else {
|
||||
return $relMapper->hasManyRelations($obj->{$member});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -280,11 +280,7 @@ class Builder extends BuilderAbstract
|
|||
/** @var mixed[] $columns */
|
||||
/** @var mixed $column */
|
||||
foreach ($columns as $column) {
|
||||
if (\is_string($column) || $column instanceof self) {
|
||||
$this->selects[] = $column;
|
||||
} else {
|
||||
throw new \InvalidArgumentException();
|
||||
}
|
||||
$this->selects[] = $column;
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user