2019-08-21 20:13:00 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Ulmus;
|
|
|
|
|
2023-01-26 13:25:59 +00:00
|
|
|
use Ulmus\Annotation\Property\{Field,
|
|
|
|
OrderBy,
|
|
|
|
Where,
|
|
|
|
Having,
|
|
|
|
Relation,
|
|
|
|
Filter,
|
|
|
|
Join,
|
|
|
|
FilterJoin,
|
|
|
|
WithJoin,
|
|
|
|
Relation\Ignore as RelationIgnore};
|
2019-08-21 20:13:00 +00:00
|
|
|
use Ulmus\Common\EntityResolver;
|
2023-08-29 20:32:25 +00:00
|
|
|
use Ulmus\Repository\WithOptionEnum;
|
2019-08-21 20:13:00 +00:00
|
|
|
|
|
|
|
class Repository
|
|
|
|
{
|
2022-04-13 03:31:48 +00:00
|
|
|
use EventTrait, Repository\ConditionTrait, Repository\EscapeTrait;
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2019-08-21 20:13:00 +00:00
|
|
|
const DEFAULT_ALIAS = "this";
|
|
|
|
|
2020-04-09 17:20:07 +00:00
|
|
|
public ? ConnectionAdapter $adapter;
|
2019-08-21 20:13:00 +00:00
|
|
|
|
|
|
|
public string $alias;
|
|
|
|
|
|
|
|
public string $entityClass;
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-05-20 19:34:50 +00:00
|
|
|
public array $events = [];
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2023-03-23 14:56:13 +00:00
|
|
|
protected Query\QueryBuilderInterface $queryBuilder;
|
|
|
|
|
|
|
|
protected EntityResolver $entityResolver;
|
|
|
|
|
|
|
|
protected array $joined = [];
|
|
|
|
|
2019-08-21 20:13:00 +00:00
|
|
|
public function __construct(string $entity, string $alias = self::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) {
|
|
|
|
$this->entityClass = $entity;
|
|
|
|
$this->alias = $alias;
|
|
|
|
$this->entityResolver = Ulmus::resolveEntity($entity);
|
2020-10-16 15:27:54 +00:00
|
|
|
$this->adapter = $adapter ?? $this->entityResolver->databaseAdapter();
|
2023-03-27 18:38:31 +00:00
|
|
|
|
|
|
|
$queryBuilder = $this->adapter->adapter()->queryBuilderClass();
|
|
|
|
$this->queryBuilder = new $queryBuilder();
|
2019-08-21 20:13:00 +00:00
|
|
|
}
|
2022-04-13 03:31:48 +00:00
|
|
|
|
2021-03-09 15:25:46 +00:00
|
|
|
public function __clone()
|
2020-11-27 17:09:15 +00:00
|
|
|
{
|
|
|
|
#$this->queryBuilder = clone $this->queryBuilder;
|
|
|
|
}
|
2019-08-21 20:13:00 +00:00
|
|
|
|
2020-01-31 21:38:48 +00:00
|
|
|
public function loadOne() : ? object
|
2019-08-21 20:13:00 +00:00
|
|
|
{
|
2023-04-13 19:39:16 +00:00
|
|
|
return $this->limit(1)->selectSqlQuery()->collectionFromQuery()[0] ?? null;
|
2019-08-21 20:13:00 +00:00
|
|
|
}
|
|
|
|
|
2020-02-17 13:23:41 +00:00
|
|
|
public function loadOneFromField($field, $value) : ? object
|
|
|
|
{
|
|
|
|
return $this->where($field, $value)->loadOne();
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2022-11-11 13:57:29 +00:00
|
|
|
public function loadFromPk($value, null|string|\Stringable $primaryKey = null) : ? object
|
2019-08-21 20:13:00 +00:00
|
|
|
{
|
2020-05-20 19:34:50 +00:00
|
|
|
return $primaryKey ? $this->loadOneFromField($primaryKey, $value) : $this->wherePrimaryKey($value)->loadOne();
|
2019-08-21 20:13:00 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2023-10-27 17:53:55 +00:00
|
|
|
public function loadAll () : EntityCollection
|
2019-08-21 20:13:00 +00:00
|
|
|
{
|
2023-01-14 02:19:42 +00:00
|
|
|
return $this->selectSqlQuery()->collectionFromQuery();
|
2019-08-21 20:13:00 +00:00
|
|
|
}
|
|
|
|
|
2021-11-24 16:22:28 +00:00
|
|
|
public function loadAllFromField($field, $value, $operator= Query\Where::OPERATOR_EQUAL) : EntityCollection
|
2021-10-19 12:41:04 +00:00
|
|
|
{
|
2021-11-24 16:22:28 +00:00
|
|
|
return $this->loadFromField($field, $value, $operator);
|
2021-10-19 12:41:04 +00:00
|
|
|
}
|
|
|
|
|
2021-11-24 16:22:28 +00:00
|
|
|
public function loadFromField($field, $value, $operator= Query\Where::OPERATOR_EQUAL) : EntityCollection
|
2019-08-21 20:13:00 +00:00
|
|
|
{
|
2023-01-14 02:19:42 +00:00
|
|
|
return $this->selectSqlQuery()->where($field, $value, $operator)->collectionFromQuery();
|
2019-08-21 20:13:00 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-02-01 03:01:56 +00:00
|
|
|
public function count() : int
|
|
|
|
{
|
2022-06-21 15:38:06 +00:00
|
|
|
$this->removeQueryFragment([ Query\Select::class, Query\OrderBy::class, ]);
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-10-06 15:00:12 +00:00
|
|
|
if ( $this->queryBuilder->getFragment(Query\GroupBy::class) ) {
|
|
|
|
$this->select( "DISTINCT COUNT(*) OVER ()" );
|
|
|
|
}
|
|
|
|
else {
|
2022-04-13 03:31:48 +00:00
|
|
|
$this->select(Common\Sql::function("COUNT", '*'));
|
2020-10-06 15:00:12 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-10-06 15:00:12 +00:00
|
|
|
$this->selectSqlQuery();
|
2020-02-07 21:36:38 +00:00
|
|
|
|
|
|
|
$this->finalizeQuery();
|
2020-02-01 03:01:56 +00:00
|
|
|
|
2020-04-09 13:50:09 +00:00
|
|
|
return Ulmus::runSelectQuery($this->queryBuilder, $this->adapter)->fetchColumn(0);
|
2020-02-01 03:01:56 +00:00
|
|
|
}
|
2019-08-21 20:13:00 +00:00
|
|
|
|
2021-05-24 20:03:38 +00:00
|
|
|
public function deleteOne()
|
2020-02-07 21:36:38 +00:00
|
|
|
{
|
2021-10-19 12:41:04 +00:00
|
|
|
return $this->limit(1)->deleteSqlQuery()->runDeleteQuery();
|
2019-12-19 14:54:33 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2021-05-24 20:03:38 +00:00
|
|
|
public function deleteAll()
|
2020-10-16 15:04:05 +00:00
|
|
|
{
|
2021-10-19 12:41:04 +00:00
|
|
|
return $this->deleteSqlQuery()->runDeleteQuery();
|
2019-12-19 14:54:33 +00:00
|
|
|
}
|
|
|
|
|
2020-02-07 21:36:38 +00:00
|
|
|
public function deleteFromPk($value) : bool
|
2020-02-06 04:41:57 +00:00
|
|
|
{
|
2020-02-07 21:36:38 +00:00
|
|
|
if ( $value !== 0 && empty($value) ) {
|
|
|
|
throw new Exception\EntityPrimaryKeyUnknown("A primary key value has to be defined to delete an item.");
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2023-04-13 19:39:16 +00:00
|
|
|
return (bool) $this->wherePrimaryKey($value)->deleteOne()->rowCount;
|
2019-12-19 14:54:33 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-05-20 19:34:50 +00:00
|
|
|
public function destroy(object $entity) : bool
|
|
|
|
{
|
|
|
|
if ( ! $this->matchEntity($entity) ) {
|
|
|
|
throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`");
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-05-20 19:34:50 +00:00
|
|
|
$primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
|
|
|
|
|
|
|
|
if ( $primaryKeyDefinition === null ) {
|
|
|
|
throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$pkField = key($primaryKeyDefinition);
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-05-20 19:34:50 +00:00
|
|
|
return $this->deleteFromPk($entity->$pkField);
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-05-20 19:34:50 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-10-16 15:04:05 +00:00
|
|
|
public function destroyAll(EntityCollection $collection) : void
|
2020-05-20 19:34:50 +00:00
|
|
|
{
|
|
|
|
foreach($collection as $entity) {
|
|
|
|
$this->destroy($entity);
|
|
|
|
}
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2022-11-11 13:57:29 +00:00
|
|
|
public function save(object|array $entity, ? array $fieldsAndValue = null, bool $replace = false) : bool
|
2020-10-16 15:04:05 +00:00
|
|
|
{
|
2021-08-02 15:13:17 +00:00
|
|
|
if ( is_array($entity) ) {
|
|
|
|
$entity = ( new $this->entityClass() )->fromArray($entity);
|
|
|
|
}
|
|
|
|
|
2020-05-20 19:34:50 +00:00
|
|
|
if ( ! $this->matchEntity($entity) ) {
|
|
|
|
throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`");
|
|
|
|
}
|
|
|
|
|
2020-02-05 21:19:57 +00:00
|
|
|
$dataset = $entity->toArray();
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-02-06 04:41:57 +00:00
|
|
|
$primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
|
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
if ( $replace || ! $entity->isLoaded() ) {
|
2021-11-24 16:22:28 +00:00
|
|
|
# $dataset = array_filter($dataset, fn($item, $field) => ! ($this->entityResolver->searchFieldAnnotation($field, new Field, false)->readonly ?? false), \ARRAY_FILTER_USE_BOTH);
|
|
|
|
|
2023-04-13 19:39:16 +00:00
|
|
|
$pdoObject = $this->insertSqlQuery($fieldsAndValue ?? $dataset, $replace)->runInsertQuery();
|
2022-04-13 03:31:48 +00:00
|
|
|
|
2022-06-21 15:38:06 +00:00
|
|
|
if ( null !== $primaryKeyDefinition ) {
|
2020-02-06 04:41:57 +00:00
|
|
|
$pkField = key($primaryKeyDefinition);
|
2022-06-21 15:38:06 +00:00
|
|
|
|
2023-04-13 19:39:16 +00:00
|
|
|
if ($pdoObject->lastInsertId ) {
|
|
|
|
$dataset[$pkField] = $pdoObject->lastInsertId;
|
2022-06-21 15:38:06 +00:00
|
|
|
}
|
|
|
|
elseif ($replace) {
|
|
|
|
$pkValue = $dataset[$pkField];
|
|
|
|
}
|
2020-02-06 04:41:57 +00:00
|
|
|
}
|
2021-01-26 16:48:40 +00:00
|
|
|
|
2020-11-27 17:09:15 +00:00
|
|
|
$entity->entityFillFromDataset($dataset, true);
|
2021-11-24 16:22:28 +00:00
|
|
|
|
2023-04-13 19:39:16 +00:00
|
|
|
return (bool) ( $pkValue ?? $pdoObject->lastInsertId );
|
2020-02-05 21:19:57 +00:00
|
|
|
}
|
|
|
|
else {
|
2020-02-07 21:36:38 +00:00
|
|
|
if ( $primaryKeyDefinition === null ) {
|
2020-02-06 04:41:57 +00:00
|
|
|
throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass));
|
|
|
|
}
|
2020-02-17 13:23:41 +00:00
|
|
|
|
2021-10-19 12:41:04 +00:00
|
|
|
$diff = $fieldsAndValue ?? $this->generateWritableDataset($entity);
|
2021-05-24 20:03:38 +00:00
|
|
|
|
|
|
|
if ( [] !== $diff ) {
|
2020-02-17 13:23:41 +00:00
|
|
|
$pkField = key($primaryKeyDefinition);
|
|
|
|
$pkFieldName = $primaryKeyDefinition[$pkField]->name ?? $pkField;
|
|
|
|
$this->where($pkFieldName, $dataset[$pkFieldName]);
|
|
|
|
|
2021-10-19 12:41:04 +00:00
|
|
|
$update = $this->updateSqlQuery($diff)->runUpdateQuery();
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2021-10-19 12:41:04 +00:00
|
|
|
$entity->entityFillFromDataset($dataset, true);
|
2021-01-26 16:48:40 +00:00
|
|
|
|
2023-04-13 19:39:16 +00:00
|
|
|
return $update ? (bool) $update->rowCount : false;
|
2020-02-17 13:23:41 +00:00
|
|
|
}
|
2020-02-05 21:19:57 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-02-05 21:19:57 +00:00
|
|
|
return false;
|
2020-01-31 21:38:48 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2023-05-12 18:56:59 +00:00
|
|
|
public function saveAll(EntityCollection|array $collection) : int
|
2020-01-31 21:38:48 +00:00
|
|
|
{
|
2021-11-24 16:22:28 +00:00
|
|
|
$changed = 0;
|
|
|
|
|
2021-11-24 16:36:13 +00:00
|
|
|
foreach ($collection as $entity) {
|
2021-11-24 16:22:28 +00:00
|
|
|
$this->save($entity) && $changed++;
|
2020-02-05 21:19:57 +00:00
|
|
|
}
|
2021-11-24 16:22:28 +00:00
|
|
|
|
|
|
|
return $changed;
|
2020-01-31 21:38:48 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2022-11-11 13:57:29 +00:00
|
|
|
public function insertAll(EntityCollection|array $collection, int $size = 1000) : int
|
2021-10-21 19:02:21 +00:00
|
|
|
{
|
2022-04-13 03:31:48 +00:00
|
|
|
if ( empty($collection) ) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
elseif ( is_array($collection) ) {
|
|
|
|
$collection = $this->entityClass::entityCollection($collection);
|
|
|
|
}
|
2021-10-21 19:02:21 +00:00
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
foreach($collection as $entity) {
|
|
|
|
if ( ! $this->matchEntity($entity) ) {
|
|
|
|
throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`");
|
|
|
|
}
|
|
|
|
}
|
2021-10-21 19:02:21 +00:00
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
$dataset = $entity->toArray();
|
2021-10-21 19:02:21 +00:00
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
$primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
|
2021-10-21 19:02:21 +00:00
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
if ( $replace || ! $entity->isLoaded() ) {
|
|
|
|
# $dataset = array_filter($dataset, fn($item, $field) => ! ($this->entityResolver->searchFieldAnnotation($field, new Field, false)->readonly ?? false), \ARRAY_FILTER_USE_BOTH);
|
2021-10-21 19:02:21 +00:00
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
$statement = $this->insertSqlQuery($fieldsAndValue ?? $dataset, $replace)->runInsertQuery();
|
2021-10-21 19:02:21 +00:00
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
if ( ( 0 !== $statement->lastInsertId ) &&
|
|
|
|
( null !== $primaryKeyDefinition )) {
|
2021-10-21 19:02:21 +00:00
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
$pkField = key($primaryKeyDefinition);
|
|
|
|
$dataset[$pkField] = $statement->lastInsertId;
|
|
|
|
}
|
2021-10-21 19:02:21 +00:00
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
$entity->entityFillFromDataset($dataset, true);
|
2021-10-21 19:02:21 +00:00
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
return (bool) $statement->lastInsertId;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if ( $primaryKeyDefinition === null ) {
|
|
|
|
throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass));
|
|
|
|
}
|
2021-10-21 19:02:21 +00:00
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
$diff = $fieldsAndValue ?? $this->generateWritableDataset($entity);
|
2021-10-21 19:02:21 +00:00
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
if ( [] !== $diff ) {
|
|
|
|
$pkField = key($primaryKeyDefinition);
|
|
|
|
$pkFieldName = $primaryKeyDefinition[$pkField]->name ?? $pkField;
|
|
|
|
$this->where($pkFieldName, $dataset[$pkFieldName]);
|
2021-10-21 19:02:21 +00:00
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
$update = $this->updateSqlQuery($diff)->runUpdateQuery();
|
2021-10-21 19:02:21 +00:00
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
$entity->entityFillFromDataset($dataset, true);
|
2021-10-21 19:02:21 +00:00
|
|
|
|
2023-04-13 19:39:16 +00:00
|
|
|
return $update ? (bool) $update->rowCount : false;
|
2021-10-21 19:02:21 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-31 21:38:48 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2022-11-11 13:57:29 +00:00
|
|
|
public function replace(object|array $entity, ? array $fieldsAndValue = null) : bool
|
2021-10-21 19:32:12 +00:00
|
|
|
{
|
|
|
|
return $this->save($entity, $fieldsAndValue, true);
|
|
|
|
}
|
|
|
|
|
2022-11-11 13:57:29 +00:00
|
|
|
public function replaceAll(EntityCollection|array $collection) : int
|
2021-08-02 15:13:17 +00:00
|
|
|
{
|
2022-04-13 03:31:48 +00:00
|
|
|
$changed = 0;
|
|
|
|
|
2021-08-02 15:13:17 +00:00
|
|
|
foreach($collection as $entity) {
|
2022-04-13 03:31:48 +00:00
|
|
|
$this->replace($entity) && $changed++;
|
2021-08-02 15:13:17 +00:00
|
|
|
}
|
2022-04-13 03:31:48 +00:00
|
|
|
|
|
|
|
return $changed;
|
2021-08-02 15:13:17 +00:00
|
|
|
}
|
|
|
|
|
2020-10-06 15:00:12 +00:00
|
|
|
public function truncate(? string $table = null, ? string $alias = null, ? string $schema = null) : self
|
|
|
|
{
|
|
|
|
$schema = $schema ?: $this->entityResolver->schemaName();
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2022-06-21 15:38:06 +00:00
|
|
|
$this->queryBuilder->truncate($this->escapeTable($table ?: $this->entityResolver->tableName()), $this->escapeIdentifier($alias ?: $this->alias), $this->escapedDatabase(), $schema ? $this->escapeSchema($schema) : null);
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-10-06 15:00:12 +00:00
|
|
|
$this->finalizeQuery();
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2023-01-14 02:19:42 +00:00
|
|
|
# ??? $result = Ulmus::runSelectQuery($this->queryBuilder, $this->adapter);
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-10-06 15:00:12 +00:00
|
|
|
return $this;
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-10-16 15:27:54 +00:00
|
|
|
public function createTable()
|
|
|
|
{
|
|
|
|
return $this->createSqlQuery()->runQuery();
|
|
|
|
}
|
|
|
|
|
2023-03-30 15:24:48 +00:00
|
|
|
public function alterTable(array $fields)
|
|
|
|
{
|
|
|
|
return $this->alterSqlQuery($fields)->runQuery();
|
|
|
|
}
|
|
|
|
|
2023-01-14 02:19:42 +00:00
|
|
|
public function listTables(? string $database = null)
|
|
|
|
{
|
|
|
|
return $this->showTablesSqlQuery($database)->runQuery();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function listColumns(? string $table = null)
|
|
|
|
{
|
|
|
|
$table ??= $this->entityResolver->tableName();
|
|
|
|
|
|
|
|
$this->showColumnsSqlQuery($table);
|
|
|
|
|
|
|
|
return $this->collectionFromQuery(Entity\InformationSchema\Column::class);
|
|
|
|
}
|
|
|
|
|
2021-08-02 15:13:17 +00:00
|
|
|
public function generateDatasetDiff(object $entity, bool $oldValues = false) : array
|
2020-03-19 13:55:00 +00:00
|
|
|
{
|
2021-08-02 15:13:17 +00:00
|
|
|
$array = array_change_key_case($entity->toArray());
|
2022-06-28 12:37:02 +00:00
|
|
|
|
2021-08-02 15:13:17 +00:00
|
|
|
$dataset = array_change_key_case($entity->entityGetDataset(false, true));
|
2023-01-26 18:47:53 +00:00
|
|
|
|
2022-10-10 14:26:27 +00:00
|
|
|
return array_udiff_assoc($oldValues ? $dataset : $array , $oldValues ? $array : $dataset, function($e1, $e2) {
|
|
|
|
if ( is_array($e1) ) {
|
|
|
|
if (is_array($e2)) {
|
|
|
|
return Ulmus::encodeArray($e1) !== Ulmus::encodeArray($e2);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (string) $e1 !== (string) $e2;
|
|
|
|
});
|
|
|
|
|
|
|
|
# return array_diff_assoc($oldValues ? $dataset : $array , $oldValues ? $array : $dataset );
|
2020-03-19 13:55:00 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2021-10-19 12:41:04 +00:00
|
|
|
|
|
|
|
public function generateWritableDataset(object $entity, bool $oldValues = false) : array
|
|
|
|
{
|
|
|
|
$intersect = [];
|
|
|
|
|
|
|
|
$dataset = $this->generateDatasetDiff($entity, $oldValues);
|
|
|
|
|
|
|
|
foreach($dataset as $field => $value) {
|
2023-01-26 13:25:59 +00:00
|
|
|
if ( false === ( $this->entityResolver->searchFieldAnnotation($field, [ Attribute\Property\Field::class, Field::class ], false)->readonly ?? false ) ) {
|
2021-10-19 12:41:04 +00:00
|
|
|
$intersect[$field] = $field;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return array_intersect_key($dataset, $intersect);
|
|
|
|
}
|
|
|
|
|
2020-10-06 15:00:12 +00:00
|
|
|
public function yield() : \Generator
|
2019-08-21 20:13:00 +00:00
|
|
|
{
|
2020-10-06 15:00:12 +00:00
|
|
|
$class = $this->entityClass;
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-10-06 15:00:12 +00:00
|
|
|
$this->selectSqlQuery();
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-10-06 15:00:12 +00:00
|
|
|
$this->finalizeQuery();
|
2019-08-21 20:13:00 +00:00
|
|
|
|
2020-10-06 15:00:12 +00:00
|
|
|
foreach(Ulmus::iterateQueryBuilder($this->queryBuilder, $this->adapter) as $entityData) {
|
|
|
|
yield ( new $class() )->entityFillFromDataset($entityData);
|
|
|
|
}
|
2019-08-21 20:13:00 +00:00
|
|
|
}
|
|
|
|
|
2022-11-11 13:57:29 +00:00
|
|
|
public function removeQueryFragment(null|Query\Fragment|string|array $fragment) : self
|
2021-10-21 19:02:21 +00:00
|
|
|
{
|
2022-04-13 03:31:48 +00:00
|
|
|
foreach((array) $fragment as $item) {
|
|
|
|
$this->queryBuilder->removeFragment($item);
|
|
|
|
}
|
2021-10-21 19:02:21 +00:00
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2021-03-01 16:26:06 +00:00
|
|
|
public function selectEntity(string $entity, string $alias, string $prependField = "") : self
|
|
|
|
{
|
2021-03-01 16:31:26 +00:00
|
|
|
$prependField and ($prependField .= "$");
|
2021-03-01 16:26:06 +00:00
|
|
|
|
2021-03-01 16:31:26 +00:00
|
|
|
foreach ($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
|
2023-01-26 13:25:59 +00:00
|
|
|
if (null === $entity::resolveEntity()->searchFieldAnnotation($field['name'], [ Attribute\Property\Relation\Ignore::class, RelationIgnore::class ])) {
|
2022-06-21 15:38:06 +00:00
|
|
|
$this->select(sprintf("%s.$key as {$prependField}{$field['name']}", $this->escapeIdentifier($alias)));
|
2021-03-01 16:26:06 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-09 15:25:46 +00:00
|
|
|
|
|
|
|
return $this;
|
2021-03-01 16:26:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public function selectJsonEntity(string $entity, string $alias, bool $skipFrom = false) : self
|
|
|
|
{
|
|
|
|
$fieldlist = [];
|
|
|
|
|
|
|
|
foreach ($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
|
2023-01-26 13:25:59 +00:00
|
|
|
if (null === $entity::resolveEntity()->searchFieldAnnotation($field['name'], [ Attribute\Property\Relation\Ignore::class, RelationIgnore::class ])) {
|
2021-03-01 16:26:06 +00:00
|
|
|
$fieldlist[] = $key;
|
2022-06-21 15:38:06 +00:00
|
|
|
$fieldlist[] = $entity::field($field['name'], $this->escapeIdentifier($alias));
|
2021-03-01 16:26:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->select(
|
|
|
|
Common\Sql::function('JSON_ARRAYAGG', Common\Sql::function('JSON_OBJECT', ... $fieldlist))
|
|
|
|
);
|
|
|
|
|
|
|
|
return $skipFrom ? $this : $this->from(
|
|
|
|
$entity::resolveEntity()->tableName(), $alias, $entity::resolveEntity()->schemaName()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-11-11 13:57:29 +00:00
|
|
|
public function select(array|string|\Stringable $fields, bool $distinct = false) : self
|
2019-08-21 20:13:00 +00:00
|
|
|
{
|
2021-03-09 15:25:46 +00:00
|
|
|
$this->queryBuilder->select($fields, $distinct);
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2019-08-21 20:13:00 +00:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2022-11-11 13:57:29 +00:00
|
|
|
public function distinct(array|string|\Stringable $fields) : self
|
2021-03-01 16:10:15 +00:00
|
|
|
{
|
|
|
|
$this->queryBuilder->select($fields);
|
|
|
|
$this->queryBuilder->getFragment(Query\Select::class)->distinct = true;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2021-08-02 15:13:17 +00:00
|
|
|
public function insert(array $fieldlist, string $table, string $alias, ? string $schema, bool $replace = false) : self
|
2020-02-05 21:19:57 +00:00
|
|
|
{
|
2022-06-21 15:38:06 +00:00
|
|
|
$this->queryBuilder->insert($fieldlist, $this->escapeTable($table), $this->escapeIdentifier($alias), $this->escapedDatabase(), $schema, $replace);
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-02-05 21:19:57 +00:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function values(array $dataset) : self
|
|
|
|
{
|
|
|
|
$this->queryBuilder->values($dataset);
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-02-05 21:19:57 +00:00
|
|
|
return $this;
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-04-09 13:50:09 +00:00
|
|
|
public function update(string $table, string $alias, ? string $schema) : self
|
2020-02-06 04:41:57 +00:00
|
|
|
{
|
2022-06-29 19:31:27 +00:00
|
|
|
$this->queryBuilder->update($this->escapeTable($table), $alias ? $this->escapeIdentifier($alias) : null, $this->escapedDatabase(), $schema);
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-02-06 04:41:57 +00:00
|
|
|
return $this;
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-02-06 04:41:57 +00:00
|
|
|
public function set(array $dataset) : self
|
|
|
|
{
|
2021-03-15 13:42:52 +00:00
|
|
|
$keys = array_keys($dataset);
|
|
|
|
$escapedFields = array_combine($keys, array_map([ $this, 'escapeField' ], $keys));
|
|
|
|
|
|
|
|
$this->queryBuilder->set($dataset, $escapedFields);
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-02-06 04:41:57 +00:00
|
|
|
return $this;
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
public function delete(...$args) : self
|
2019-12-19 14:54:33 +00:00
|
|
|
{
|
2023-02-03 14:06:00 +00:00
|
|
|
$this->queryBuilder->delete();
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2019-12-19 14:54:33 +00:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2020-02-07 21:36:38 +00:00
|
|
|
public function from(string $table, ? string $alias, ? string $schema) : self
|
2019-08-21 20:13:00 +00:00
|
|
|
{
|
2022-06-29 19:31:27 +00:00
|
|
|
$this->queryBuilder->from($this->escapeTable($table), $alias ? $this->escapeIdentifier($alias) : null, $this->escapedDatabase(), $schema ? $this->escapeSchema($schema) : null);
|
|
|
|
|
2019-08-21 20:13:00 +00:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2023-01-26 18:47:53 +00:00
|
|
|
public function join(string $type, $table, string|\Stringable $field, mixed $value, ? string $alias = null, ? callable $callback = null) : self
|
2019-08-21 20:13:00 +00:00
|
|
|
{
|
2022-06-29 19:31:27 +00:00
|
|
|
$join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, false, $alias ? $this->escapeIdentifier($alias) : null);
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-10-06 15:00:12 +00:00
|
|
|
if ( $callback ) {
|
|
|
|
$callback($join);
|
2020-03-29 02:13:29 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-03-29 02:13:29 +00:00
|
|
|
return $this;
|
|
|
|
}
|
2019-08-21 20:13:00 +00:00
|
|
|
|
2023-01-26 18:47:53 +00:00
|
|
|
public function outerJoin(string $type, $table, string|\Stringable $field, mixed $value, ? string $alias = null, ? callable $callback = null) : self
|
2019-08-21 20:13:00 +00:00
|
|
|
{
|
2022-06-29 19:31:27 +00:00
|
|
|
$join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, true, $alias ? $this->escapeIdentifier($alias) : null);
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-10-06 15:00:12 +00:00
|
|
|
if ( $callback ) {
|
|
|
|
$callback($join);
|
2020-03-29 02:13:29 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-03-29 02:13:29 +00:00
|
|
|
return $this;
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2019-08-21 20:13:00 +00:00
|
|
|
public function match() : self
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public function notMatch() : self
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public function between() : self
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public function notBetween() : self
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2023-01-26 18:47:53 +00:00
|
|
|
public function groupBy(string|\Stringable $field) : self
|
2019-08-21 20:13:00 +00:00
|
|
|
{
|
2020-10-06 15:00:12 +00:00
|
|
|
$this->queryBuilder->groupBy($field);
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2019-08-21 20:13:00 +00:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2020-03-29 02:13:29 +00:00
|
|
|
public function groups(array $groups) : self
|
|
|
|
{
|
2020-10-06 15:00:12 +00:00
|
|
|
foreach($groups as $field ) {
|
|
|
|
$this->groupBy($field);
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-03-29 02:13:29 +00:00
|
|
|
return $this;
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2023-01-26 18:47:53 +00:00
|
|
|
public function orderBy(string|\Stringable $field, ? string $direction = null) : self
|
2019-08-21 20:13:00 +00:00
|
|
|
{
|
|
|
|
$this->queryBuilder->orderBy($field, $direction);
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2019-08-21 20:13:00 +00:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2020-11-27 17:09:15 +00:00
|
|
|
# @UNTESTED
|
|
|
|
public function randomizeOrder() : self
|
|
|
|
{
|
|
|
|
$this->queryBuilder->orderBy(Common\Sql::function('RAND', Sql::identifier('CURDATE()+0')));
|
2022-04-13 03:31:48 +00:00
|
|
|
|
2020-11-27 17:09:15 +00:00
|
|
|
return $this;
|
|
|
|
}
|
2022-04-13 03:31:48 +00:00
|
|
|
|
2020-03-29 02:13:29 +00:00
|
|
|
public function orders(array $orderList) : self
|
|
|
|
{
|
|
|
|
foreach($orderList as $field => $direction) {
|
2020-10-06 15:00:12 +00:00
|
|
|
$this->orderBy($field, $direction);
|
2020-03-29 02:13:29 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-03-29 02:13:29 +00:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2019-08-21 20:13:00 +00:00
|
|
|
public function limit(int $value) : self
|
|
|
|
{
|
|
|
|
$this->queryBuilder->limit($value);
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2019-08-21 20:13:00 +00:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function offset(int $value) : self
|
|
|
|
{
|
|
|
|
$this->queryBuilder->offset($value);
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2019-08-21 20:13:00 +00:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2020-05-20 19:34:50 +00:00
|
|
|
/* @TODO */
|
2019-08-21 20:13:00 +00:00
|
|
|
public function commit() : self
|
|
|
|
{
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2020-05-20 19:34:50 +00:00
|
|
|
/* @TODO */
|
2019-08-21 20:13:00 +00:00
|
|
|
public function rollback() : self
|
|
|
|
{
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2023-01-26 18:47:53 +00:00
|
|
|
public function wherePrimaryKey(mixed $value) : self
|
2020-02-05 21:19:57 +00:00
|
|
|
{
|
|
|
|
if ( null === $primaryKeyField = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField() ) {
|
|
|
|
throw new Exception\EntityPrimaryKeyUnknown("Entity has no field containing attributes 'primary_key'");
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-02-07 21:36:38 +00:00
|
|
|
$pkField = key($primaryKeyField);
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2023-10-27 17:53:55 +00:00
|
|
|
return $this->where($this->entityClass::field($primaryKeyField[$pkField]->name ?? $pkField), $value);
|
2020-02-05 21:19:57 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2023-08-29 20:32:25 +00:00
|
|
|
public function withJoin(string|array $fields, array $options = []) : self
|
2020-05-20 19:34:50 +00:00
|
|
|
{
|
2020-10-06 15:00:12 +00:00
|
|
|
if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) {
|
2023-01-27 15:46:16 +00:00
|
|
|
$select = $this->entityResolver->fieldList(EntityResolver::KEY_COLUMN_NAME, true);
|
|
|
|
$this->select($this->entityClass::fields(array_map(fn($f) => $f['object']->name ?? $f['name'], $select)));
|
2020-10-06 15:00:12 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2021-10-21 19:02:21 +00:00
|
|
|
# @TODO Apply FILTER annotation to this too !
|
2021-03-01 16:26:06 +00:00
|
|
|
foreach(array_filter((array) $fields) as $item) {
|
2023-03-23 14:56:13 +00:00
|
|
|
if ( isset($this->joined[$item]) ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$this->joined[$item] = true;
|
|
|
|
}
|
|
|
|
|
2023-01-26 13:25:59 +00:00
|
|
|
$annotation = $this->entityResolver->searchFieldAnnotation($item, [ Attribute\Property\Join::class, Join::class ]) ?:
|
|
|
|
$this->entityResolver->searchFieldAnnotation($item, [ Attribute\Property\Relation::class, Relation::class ]);
|
2021-01-26 16:48:40 +00:00
|
|
|
|
2023-01-26 13:25:59 +00:00
|
|
|
$isRelation = ( $annotation instanceof Relation ) || ($annotation instanceof Attribute\Property\Relation);
|
|
|
|
|
2023-03-27 18:38:31 +00:00
|
|
|
if ($isRelation && ( $annotation->isManyToMany() )) {
|
2021-01-26 16:48:40 +00:00
|
|
|
throw new Exception("Many-to-many relation can not be preloaded within joins.");
|
|
|
|
}
|
2020-11-27 17:21:10 +00:00
|
|
|
|
2021-01-26 16:48:40 +00:00
|
|
|
if ( $annotation ) {
|
|
|
|
$alias = $annotation->alias ?? $item;
|
|
|
|
|
|
|
|
$entity = $annotation->entity ?? $this->entityResolver->properties[$item]['type'];
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-11-27 17:09:15 +00:00
|
|
|
foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
|
2023-01-26 13:25:59 +00:00
|
|
|
if ( null === $entity::resolveEntity()->searchFieldAnnotation($field['name'], [ Attribute\Property\Relation\Ignore::class, RelationIgnore::class ]) ) {
|
2022-06-21 15:38:06 +00:00
|
|
|
$escAlias = $this->escapeIdentifier($alias);
|
2023-03-27 18:38:31 +00:00
|
|
|
$fieldName = $this->escapeIdentifier($key);
|
2022-06-21 15:38:06 +00:00
|
|
|
|
2023-01-26 13:25:59 +00:00
|
|
|
$name = $entity::resolveEntity()->searchFieldAnnotation($field['name'], [ Attribute\Property\Field::class, Field::class ])->name ?? $field['name'];
|
2023-01-14 02:19:42 +00:00
|
|
|
|
2023-03-27 18:38:31 +00:00
|
|
|
|
|
|
|
$this->select("$escAlias.$fieldName as $alias\${$name}");
|
2021-03-01 16:26:06 +00:00
|
|
|
}
|
2020-05-20 19:34:50 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2021-01-26 16:48:40 +00:00
|
|
|
$this->open();
|
|
|
|
|
2023-08-29 20:32:25 +00:00
|
|
|
if ( ! in_array(WithOptionEnum::SkipWhere, $options)) {
|
2023-01-26 13:25:59 +00:00
|
|
|
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\Where::class, Where::class ] ) as $condition) {
|
2021-01-26 16:48:40 +00:00
|
|
|
if ( is_object($condition->field) && ( $condition->field->entityClass !== $entity ) ) {
|
2023-02-23 17:15:31 +00:00
|
|
|
$this->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator);
|
2021-01-26 16:48:40 +00:00
|
|
|
}
|
|
|
|
}
|
2023-08-29 20:32:25 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2023-08-29 20:32:25 +00:00
|
|
|
if ( ! in_array(WithOptionEnum::SkipHaving, $options)) {
|
|
|
|
foreach ($this->entityResolver->searchFieldAnnotationList($item, [Attribute\Property\Having::class, Having::class]) as $condition) {
|
|
|
|
$this->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator);
|
|
|
|
}
|
2021-01-26 16:48:40 +00:00
|
|
|
}
|
|
|
|
|
2023-08-29 20:32:25 +00:00
|
|
|
if ( ! in_array(WithOptionEnum::SkipFilter, $options)) {
|
|
|
|
foreach ($this->entityResolver->searchFieldAnnotationList($item, [Attribute\Property\Filter::class, Filter::class]) as $filter) {
|
|
|
|
call_user_func_array([$this->entityClass, $filter->method], [$this, $item, true]);
|
|
|
|
}
|
2021-05-24 20:03:38 +00:00
|
|
|
}
|
|
|
|
|
2021-01-26 16:48:40 +00:00
|
|
|
$this->close();
|
|
|
|
|
|
|
|
$key = is_string($annotation->key) ? $this->entityClass::field($annotation->key) : $annotation->key;
|
|
|
|
|
|
|
|
$foreignKey = is_string($annotation->foreignKey) ? $entity::field($annotation->foreignKey, $alias) : $annotation->foreignKey;
|
|
|
|
|
2023-08-29 20:32:25 +00:00
|
|
|
$this->join("LEFT", $entity::resolveEntity()->tableName(), $key, $foreignKey, $alias, function($join) use ($item, $entity, $alias, $options) {
|
|
|
|
if ( ! in_array(WithOptionEnum::SkipJoinWhere, $options)) {
|
2023-01-26 13:25:59 +00:00
|
|
|
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\Where::class, Where::class ]) as $condition) {
|
2021-03-01 16:26:06 +00:00
|
|
|
if ( ! is_object($condition->field) ) {
|
|
|
|
$field = $this->entityClass::field($condition->field);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$field = clone $condition->field;
|
|
|
|
}
|
2021-01-26 16:48:40 +00:00
|
|
|
|
2022-11-24 19:22:49 +00:00
|
|
|
# Adding directly
|
|
|
|
if ( $field->entityClass === $entity ) {
|
2021-01-26 16:48:40 +00:00
|
|
|
$field->alias = $alias;
|
|
|
|
|
2023-02-23 17:15:31 +00:00
|
|
|
$join->where(is_object($field) ? $field : $entity::field($field, $alias), $condition->getValue(), $condition->operator);
|
2022-11-24 19:22:49 +00:00
|
|
|
}
|
2021-01-26 16:48:40 +00:00
|
|
|
}
|
2023-08-29 20:32:25 +00:00
|
|
|
}
|
2021-05-24 20:03:38 +00:00
|
|
|
|
2023-08-29 20:32:25 +00:00
|
|
|
if ( ! in_array(WithOptionEnum::SkipJoinFilter, $options) ) {
|
|
|
|
foreach ($this->entityResolver->searchFieldAnnotationList($item, [Attribute\Property\FilterJoin::class, FilterJoin::class]) as $filter) {
|
|
|
|
call_user_func_array([$this->entityClass, $filter->method], [$join, $item, true]);
|
|
|
|
}
|
2021-05-24 20:03:38 +00:00
|
|
|
}
|
2021-01-26 16:48:40 +00:00
|
|
|
});
|
2020-05-20 19:34:50 +00:00
|
|
|
}
|
|
|
|
else {
|
2023-08-29 20:32:25 +00:00
|
|
|
throw new \Exception("Referenced field `$item` which do not exist or do not contain a valid @Join or @Relation annotation.");
|
2020-05-20 19:34:50 +00:00
|
|
|
}
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-05-20 19:34:50 +00:00
|
|
|
return $this;
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2023-08-29 20:32:25 +00:00
|
|
|
public function withSubquery(string|array $fields, array $options = []) : self
|
2019-08-21 20:13:00 +00:00
|
|
|
{
|
2021-03-01 16:26:06 +00:00
|
|
|
# We skip subqueries when counting results since it should not affect the row count.
|
|
|
|
if ( $this instanceof Repository\ServerRequestCountRepository ) {
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) {
|
2022-06-21 15:38:06 +00:00
|
|
|
$this->select($this->escapeIdentifier($this->alias) . ".*");
|
2021-03-01 16:26:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# Apply FILTER annotation to this too !
|
|
|
|
foreach(array_filter((array) $fields) as $item) {
|
2023-01-26 13:25:59 +00:00
|
|
|
if ( $relation = $this->entityResolver->searchFieldAnnotation($item, [ Attribute\Property\Relation::class, Relation::class ]) ) {
|
2021-03-01 16:26:06 +00:00
|
|
|
$alias = $relation->alias ?? $item;
|
|
|
|
|
|
|
|
if ( $relation->isManyToMany() ) {
|
|
|
|
$entity = $relation->bridge;
|
|
|
|
$repository = $relation->bridge::repository();
|
|
|
|
$repository->queryBuilder->parent = $this->queryBuilder;
|
|
|
|
|
|
|
|
extract(Repository\RelationBuilder::relationAnnotations($item, $relation));
|
|
|
|
|
|
|
|
$repository->join(Query\Join::TYPE_INNER, $bridgeEntity->tableName(), $relation->bridge::field($relationRelation->key, $relation->bridgeField), $relationRelation->entity::field($relationRelation->foreignKey, $alias), $relation->bridgeField)
|
|
|
|
->where( $entity::field($bridgeRelation->key, $relation->bridgeField), $entity::field($bridgeRelation->foreignKey, $this->alias))
|
|
|
|
->selectJsonEntity($relationRelation->entity, $alias)->open();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$entity = $relation->entity ?? $this->entityResolver->properties[$item]['type'];
|
|
|
|
$repository = $entity::repository()->selectJsonEntity($entity, $alias)->open();
|
|
|
|
}
|
|
|
|
|
2023-02-23 17:15:31 +00:00
|
|
|
# $relation->isManyToMany() and $repository->selectJsonEntity($relation->bridge, $relation->bridgeField, true);
|
2021-03-01 16:26:06 +00:00
|
|
|
|
2023-01-26 13:25:59 +00:00
|
|
|
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\Where::class, Where::class ]) as $condition) {
|
2023-02-23 17:15:31 +00:00
|
|
|
$repository->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator);
|
2021-03-01 16:26:06 +00:00
|
|
|
}
|
|
|
|
|
2023-01-26 13:25:59 +00:00
|
|
|
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\Having::class, Having::class ] ) as $condition) {
|
2023-02-23 17:15:31 +00:00
|
|
|
$repository->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator);
|
2021-03-01 16:26:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$repository->close();
|
|
|
|
|
|
|
|
$key = is_string($relation->key) ? $this->entityClass::field($relation->key) : $relation->key;
|
|
|
|
|
|
|
|
if (! $relation->isManyToMany() ) {
|
|
|
|
$foreignKey = is_string($relation->foreignKey) ? $entity::field($relation->foreignKey, $alias) : $relation->foreignKey;
|
|
|
|
|
|
|
|
$repository->where( $foreignKey, $key);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->select("(" . $r = Common\Sql::raw($repository->queryBuilder->render() . ") as $item\$collection"));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
throw new \Exception("You referenced field `$item` which do not exist or do not contain a valid @Join annotation.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2022-11-11 13:57:29 +00:00
|
|
|
public function loadCollectionRelation(EntityCollection $collection, array|string $fields) : void
|
2022-04-13 03:31:48 +00:00
|
|
|
{
|
|
|
|
foreach ((array)$fields as $name) {
|
2023-01-26 13:25:59 +00:00
|
|
|
if (null !== ($relation = $this->entityResolver->searchFieldAnnotation($name, [ Attribute\Property\Relation::class, Relation::class ] ))) {
|
|
|
|
$order = $this->entityResolver->searchFieldAnnotationList($name, [ Attribute\Property\OrderBy::class, OrderBy::class ]);
|
|
|
|
$where = $this->entityResolver->searchFieldAnnotationList($name, [ Attribute\Property\Where::class, Where::class ]);
|
2022-04-13 03:31:48 +00:00
|
|
|
|
|
|
|
$baseEntity = $relation->entity ?? $relation->bridge ?? $this->entityResolver->properties[$name]['type'];
|
|
|
|
$baseEntityResolver = $baseEntity::resolveEntity();
|
|
|
|
|
|
|
|
$property = ($baseEntityResolver->field($relation->foreignKey, 01, false) ?: $baseEntityResolver->field($relation->foreignKey, 02))['name'];
|
|
|
|
$entityProperty = ($this->entityResolver->field($relation->key, 01, false) ?: $this->entityResolver->field($relation->key, 02))['name'];
|
|
|
|
|
|
|
|
$repository = $baseEntity::repository();
|
|
|
|
|
2022-07-15 19:59:38 +00:00
|
|
|
foreach ($baseEntityResolver->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
|
2023-01-26 13:25:59 +00:00
|
|
|
if (null === $baseEntityResolver->searchFieldAnnotation($field['name'], [ Attribute\Property\Relation\Ignore::class, RelationIgnore::class ])) {
|
2022-07-15 19:59:38 +00:00
|
|
|
$repository->select($baseEntityResolver->entityClass::field($key));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
foreach ($where as $condition) {
|
2023-03-27 18:38:31 +00:00
|
|
|
# $repository->where($condition->field, is_callable($condition->value) ? call_user_func_array($condition->value, [$this]) : $condition->getValue(), $condition->operator, $condition->condition);
|
|
|
|
$repository->where($condition->field, $condition->getValue($this), $condition->operator, $condition->condition);
|
2022-04-13 03:31:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($order as $item) {
|
|
|
|
$repository->orderBy($item->field, $item->order);
|
|
|
|
}
|
|
|
|
|
|
|
|
$field = $relation->key;
|
|
|
|
|
|
|
|
$values = [];
|
|
|
|
|
|
|
|
$key = is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity::field($relation->foreignKey);
|
|
|
|
|
|
|
|
foreach ($collection as $item) {
|
|
|
|
$values[] = is_callable($field) ? $field($item) : $item->$entityProperty;
|
|
|
|
}
|
|
|
|
|
2023-03-27 18:38:31 +00:00
|
|
|
$values = array_unique($values);
|
|
|
|
|
2022-04-13 03:31:48 +00:00
|
|
|
$repository->where($key, $values);
|
|
|
|
|
2023-03-27 18:38:31 +00:00
|
|
|
$results = $repository->loadAll();
|
2022-04-13 03:31:48 +00:00
|
|
|
|
2022-07-15 19:59:38 +00:00
|
|
|
if ($relation->isOneToOne()) {
|
2023-03-27 18:38:31 +00:00
|
|
|
foreach ($collection as $item) {
|
|
|
|
$item->$name = $results->searchOne($item->$entityProperty, $property) ?: new $baseEntity();
|
|
|
|
}
|
2022-07-15 19:59:38 +00:00
|
|
|
}
|
|
|
|
elseif ($relation->isOneToMany()) {
|
|
|
|
foreach ($collection as $item) {
|
|
|
|
$item->$name = $baseEntity::entityCollection();
|
2023-03-27 18:38:31 +00:00
|
|
|
$item->$name->mergeWith($results->searchAll($item->$entityProperty, $property));
|
2022-07-15 19:59:38 +00:00
|
|
|
}
|
2022-04-13 03:31:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-21 19:02:21 +00:00
|
|
|
public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest, bool $count = true) : self
|
|
|
|
{
|
|
|
|
if ($count) {
|
|
|
|
$searchRequest->count = $searchRequest->filter($this->serverRequestCountRepository())
|
|
|
|
->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND)
|
|
|
|
->likes($searchRequest->likes(), Query\Where::CONDITION_OR)
|
|
|
|
->groups($searchRequest->groups())
|
|
|
|
->count();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $searchRequest->filter($this)
|
|
|
|
->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND)
|
|
|
|
->likes($searchRequest->likes(), Query\Where::CONDITION_OR)
|
|
|
|
->orders($searchRequest->orders())
|
|
|
|
->groups($searchRequest->groups())
|
|
|
|
->offset($searchRequest->offset())
|
|
|
|
->limit($searchRequest->limit());
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function serverRequestCountRepository() : Repository
|
|
|
|
{
|
|
|
|
return new Repository\ServerRequestCountRepository($this->entityClass, $this->alias, $this->adapter);
|
|
|
|
}
|
|
|
|
|
2020-04-09 13:50:09 +00:00
|
|
|
public function collectionFromQuery(? string $entityClass = null) : EntityCollection
|
2019-08-21 20:13:00 +00:00
|
|
|
{
|
2023-01-14 02:19:42 +00:00
|
|
|
$class = $entityClass ?? $this->entityClass;
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2023-01-14 02:19:42 +00:00
|
|
|
$entityCollection = $class::entityCollection();
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-02-07 21:36:38 +00:00
|
|
|
$this->finalizeQuery();
|
2023-01-14 02:19:42 +00:00
|
|
|
|
2020-02-13 03:56:53 +00:00
|
|
|
foreach(Ulmus::iterateQueryBuilder($this->queryBuilder, $this->adapter) as $entityData) {
|
2020-10-16 15:04:05 +00:00
|
|
|
$entityCollection->append( ( new $class() )->resetVirtualProperties()->entityFillFromDataset($entityData) );
|
2019-08-21 20:13:00 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2021-03-09 15:25:46 +00:00
|
|
|
$this->eventExecute(Event\Repository\CollectionFromQueryInterface::class, $entityCollection);
|
2019-08-21 20:13:00 +00:00
|
|
|
|
|
|
|
return $entityCollection;
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-05-20 19:34:50 +00:00
|
|
|
public function arrayFromQuery() : array
|
|
|
|
{
|
|
|
|
$this->selectSqlQuery();
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-05-20 19:34:50 +00:00
|
|
|
$this->finalizeQuery();
|
|
|
|
|
|
|
|
return Ulmus::datasetQueryBuilder($this->queryBuilder, $this->adapter);
|
|
|
|
}
|
2019-08-21 20:13:00 +00:00
|
|
|
|
2023-01-26 18:47:53 +00:00
|
|
|
public function runQuery() : mixed
|
2019-12-19 14:54:33 +00:00
|
|
|
{
|
2020-02-07 21:36:38 +00:00
|
|
|
$this->finalizeQuery();
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-02-13 03:56:53 +00:00
|
|
|
return Ulmus::runQuery($this->queryBuilder, $this->adapter);
|
2019-12-19 14:54:33 +00:00
|
|
|
}
|
2021-03-01 16:26:06 +00:00
|
|
|
|
2023-01-26 18:47:53 +00:00
|
|
|
public function runInsertQuery() : mixed
|
2021-10-19 12:41:04 +00:00
|
|
|
{
|
|
|
|
$this->finalizeQuery();
|
|
|
|
|
|
|
|
return Ulmus::runInsertQuery($this->queryBuilder, $this->adapter);
|
|
|
|
}
|
|
|
|
|
2023-01-26 18:47:53 +00:00
|
|
|
public function runUpdateQuery() : mixed
|
2021-10-19 12:41:04 +00:00
|
|
|
{
|
|
|
|
$this->finalizeQuery();
|
|
|
|
|
|
|
|
return Ulmus::runUpdateQuery($this->queryBuilder, $this->adapter);
|
|
|
|
}
|
|
|
|
|
2023-01-26 18:47:53 +00:00
|
|
|
public function runDeleteQuery() : mixed
|
2021-10-19 12:41:04 +00:00
|
|
|
{
|
|
|
|
$this->finalizeQuery();
|
|
|
|
|
|
|
|
return Ulmus::runDeleteQuery($this->queryBuilder, $this->adapter);
|
|
|
|
}
|
|
|
|
|
2020-11-27 17:09:15 +00:00
|
|
|
public function resetQuery() : self
|
|
|
|
{
|
|
|
|
$this->queryBuilder->reset();
|
2021-03-01 16:26:06 +00:00
|
|
|
|
2020-11-27 17:09:15 +00:00
|
|
|
return $this;
|
|
|
|
}
|
2021-03-01 16:26:06 +00:00
|
|
|
|
2021-08-02 15:13:17 +00:00
|
|
|
protected function insertSqlQuery(array $dataset, bool $replace = false) : self
|
2020-02-05 21:19:57 +00:00
|
|
|
{
|
2021-08-02 15:13:17 +00:00
|
|
|
if ( null === $insert = $this->queryBuilder->getFragment(Query\Insert::class) ) {
|
|
|
|
$this->insert(array_map([ $this, 'escapeField' ] , array_keys($dataset)), $this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName(), $replace);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$insert->replace = $replace;
|
2020-02-05 21:19:57 +00:00
|
|
|
}
|
|
|
|
|
2020-02-06 04:41:57 +00:00
|
|
|
$this->values($dataset);
|
2020-02-05 21:19:57 +00:00
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-02-05 21:19:57 +00:00
|
|
|
protected function updateSqlQuery(array $dataset) : self
|
|
|
|
{
|
2020-02-07 21:36:38 +00:00
|
|
|
if ( null === $this->queryBuilder->getFragment(Query\Update::class) ) {
|
2020-04-09 13:50:09 +00:00
|
|
|
$this->update($this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
|
2020-02-05 21:19:57 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-02-06 04:41:57 +00:00
|
|
|
$this->set($dataset);
|
2020-02-05 21:19:57 +00:00
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2019-08-21 20:13:00 +00:00
|
|
|
protected function selectSqlQuery() : self
|
|
|
|
{
|
2020-02-07 21:36:38 +00:00
|
|
|
if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) {
|
2023-01-27 15:46:16 +00:00
|
|
|
$fields = $this->entityResolver->fieldList(EntityResolver::KEY_COLUMN_NAME, true);
|
|
|
|
$this->select($this->entityClass::fields(array_map(fn($f) => $f['object']->name ?? $f['name'], $fields)));
|
2019-08-21 20:13:00 +00:00
|
|
|
}
|
|
|
|
|
2020-02-07 21:36:38 +00:00
|
|
|
if ( null === $this->queryBuilder->getFragment(Query\From::class) ) {
|
2020-01-29 21:11:16 +00:00
|
|
|
$this->from($this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
|
2019-08-21 20:13:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2019-12-19 14:54:33 +00:00
|
|
|
protected function deleteSqlQuery() : self
|
|
|
|
{
|
2020-02-07 21:36:38 +00:00
|
|
|
if ( null === $this->queryBuilder->getFragment(Query\Delete::class) ) {
|
2019-12-19 14:54:33 +00:00
|
|
|
$this->delete();
|
|
|
|
}
|
|
|
|
|
2020-02-07 21:36:38 +00:00
|
|
|
if ( null === $this->queryBuilder->getFragment(Query\From::class) ) {
|
|
|
|
$this->from($this->entityResolver->tableName(), null, $this->entityResolver->schemaName());
|
2019-12-19 14:54:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-10-16 15:27:54 +00:00
|
|
|
public function createSqlQuery() : self
|
|
|
|
{
|
|
|
|
if ( null === $this->queryBuilder->getFragment(Query\Create::class) ) {
|
2022-01-28 16:37:35 +00:00
|
|
|
$this->queryBuilder->create($this->adapter->adapter(), $this->escapeFieldList($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true)), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName());
|
2020-10-16 15:27:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2021-05-24 20:03:38 +00:00
|
|
|
public function alterSqlQuery(array $fields) : self
|
|
|
|
{
|
|
|
|
if ( null === $this->queryBuilder->getFragment(Query\Alter::class) ) {
|
2023-03-30 15:24:48 +00:00
|
|
|
$this->queryBuilder->alter($this->adapter->adapter(), $fields, $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName());
|
2021-05-24 20:03:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2023-01-14 02:19:42 +00:00
|
|
|
public function showDatabasesSqlQuery() : self
|
|
|
|
{
|
|
|
|
if ( null === $this->queryBuilder->getFragment(Query\Show::class) ) {
|
|
|
|
$this->queryBuilder->showDatabases();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function showTablesSqlQuery() : self
|
|
|
|
{
|
|
|
|
if ( null === $this->queryBuilder->getFragment(Query\Show::class) ) {
|
|
|
|
$this->queryBuilder->showTables();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function showColumnsSqlQuery(string $table) : self
|
|
|
|
{
|
|
|
|
if ( null === $this->queryBuilder->getFragment(Query\Show::class) ) {
|
|
|
|
$this->queryBuilder->showColumns($table);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2019-08-21 20:13:00 +00:00
|
|
|
protected function fromRow($row) : self
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function fromCollection($rows) : self
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-10-16 15:27:54 +00:00
|
|
|
public function getSqlQuery(bool $flush = true) : string
|
|
|
|
{
|
|
|
|
$result = $this->queryBuilder->render();
|
|
|
|
|
|
|
|
$flush and $this->queryBuilder->reset();
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
}
|
2022-04-13 03:31:48 +00:00
|
|
|
|
2020-11-27 17:09:15 +00:00
|
|
|
public function instanciateEntityCollection(...$arguments) : EntityCollection
|
2020-02-03 16:13:26 +00:00
|
|
|
{
|
2020-11-27 17:09:15 +00:00
|
|
|
return $this->entityClass::entityCollection(...$arguments);
|
2020-02-03 16:13:26 +00:00
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
|
|
|
public function instanciateEntity() : object
|
|
|
|
{
|
|
|
|
return new $this->entityClass();
|
|
|
|
}
|
|
|
|
|
2021-03-09 15:25:46 +00:00
|
|
|
public function hasFilters() : bool
|
|
|
|
{
|
|
|
|
return isset($this->queryBuilder->where);
|
|
|
|
}
|
|
|
|
|
2020-05-20 19:34:50 +00:00
|
|
|
protected function matchEntity(object $entity) {
|
|
|
|
return get_class($entity) === $this->entityClass;
|
|
|
|
}
|
2020-10-16 15:04:05 +00:00
|
|
|
|
2020-02-07 21:36:38 +00:00
|
|
|
protected function finalizeQuery() : void {}
|
2020-10-16 15:04:05 +00:00
|
|
|
}
|