- A lot of fixes made again in this patch
This commit is contained in:
parent
4df9ececba
commit
7fb591e1f5
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Annotation\Property;
|
||||
|
||||
class FilterJoin implements \Ulmus\Annotation\Annotation {
|
||||
|
||||
public string $method;
|
||||
|
||||
public function __construct(string $method = null)
|
||||
{
|
||||
if ( $method !== null ) {
|
||||
$this->method = $method;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Annotation\Property;
|
||||
|
||||
use Ulmus\Query;
|
||||
|
||||
class OrWhere extends Where {
|
||||
|
||||
public function __construct(/* ? Stringable */ $field = null, $value = null, ? string $operator = null, ? string $condition = null)
|
||||
{
|
||||
parent::__construct($field, $value, $operator, Query\Where::CONDITION_OR);
|
||||
}
|
||||
|
||||
}
|
|
@ -8,6 +8,8 @@ class Relation implements \Ulmus\Annotation\Annotation {
|
|||
|
||||
public /*stringable*/ $key;
|
||||
|
||||
public /* callable */ $generateKey;
|
||||
|
||||
public /*stringable*/ $foreignKey;
|
||||
|
||||
public array $foreignKeys;
|
||||
|
|
|
@ -74,7 +74,7 @@ class EntityCollection extends \ArrayObject {
|
|||
return null;
|
||||
}
|
||||
|
||||
public function remove($value, string $field, bool $strict = true) : array
|
||||
public function remove(/* mixed */ $values, string $field, bool $strict = true) : array
|
||||
{
|
||||
$removed = [];
|
||||
|
||||
|
@ -89,7 +89,7 @@ class EntityCollection extends \ArrayObject {
|
|||
|
||||
public function search($value, string $field, bool $strict = true) : Generator
|
||||
{
|
||||
foreach($this->filters(fn($v) => $strict ? $v->$field === $value : $v->$field == $value) as $key => $item) {
|
||||
foreach($this->filters(fn($v) => isset($v->$field) ? ( $strict ? $v->$field === $value : $v->$field == $value ) : false) as $key => $item) {
|
||||
yield $key => $item;
|
||||
}
|
||||
}
|
||||
|
@ -104,13 +104,32 @@ class EntityCollection extends \ArrayObject {
|
|||
return null;
|
||||
}
|
||||
|
||||
public function searchAll($value, string $field, bool $strict = true) : self
|
||||
public function searchAll(/* mixed*/ $values, string $field, bool $strict = true, bool $compareArray = false) : self
|
||||
{
|
||||
$obj = new static();
|
||||
|
||||
$values = is_array($values) && $compareArray ? [ $values ] : $values;
|
||||
|
||||
foreach((array) $values as $value) {
|
||||
foreach ($this->search($value, $field, $strict) as $item) {
|
||||
$obj->append($item);
|
||||
}
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function diffAll(/* mixed */ $values, string $field, bool $strict = true, bool $compareArray = false) : self
|
||||
{
|
||||
$obj = new static($this->getArrayCopy());
|
||||
|
||||
$values = is_array($values) && $compareArray ? [ $values ] : $values;
|
||||
|
||||
foreach((array) $values as $value) {
|
||||
foreach($obj->search($value, $field, $strict) as $key => $item) {
|
||||
$obj->offsetUnset($key);
|
||||
}
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use Ulmus\Repository,
|
|||
Ulmus\Common\EntityField;
|
||||
|
||||
use Ulmus\Annotation\Classes\{ Method, Table, Collation, };
|
||||
use Ulmus\Annotation\Property\{ Field, Filter, Relation, OrderBy, Where, Join, Virtual, On, WithJoin, };
|
||||
use Ulmus\Annotation\Property\{ Field, Filter, FilterJoin, Relation, OrderBy, Where, OrWhere, Join, Virtual, On, WithJoin, };
|
||||
use Ulmus\Annotation\Property\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, Datetime as DateTime, Date, Time, Bigint, Tinyint, Text, Mediumtext, Longtext, };
|
||||
use Ulmus\Annotation\Property\Relation\{ Ignore as RelationIgnore };
|
||||
|
||||
|
@ -255,6 +255,22 @@ trait EntityTrait {
|
|||
return array_keys($this->resolveEntity()->fieldList());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach($this as $prop) {
|
||||
|
||||
}
|
||||
|
||||
if ( null !== $pkField = $this->resolveEntity()->getPrimaryKeyField($this) ) {
|
||||
$key = key($pkField);
|
||||
|
||||
unset($this->$key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Ignore
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Event;
|
||||
|
||||
use Ulmus\Repository;
|
||||
|
||||
interface PdoObjectSelectInterface
|
||||
{
|
||||
public function execute(string $name, Repository $repository): Repository;
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Ulmus\Query;
|
||||
|
||||
use Ulmus\Annotation,
|
||||
Ulmus\Common\EntityField;
|
||||
|
||||
class Alter extends Fragment {
|
||||
|
||||
const SQL_TOKEN = "ALTER TABLE";
|
||||
|
||||
public string $table;
|
||||
|
||||
public string $engine;
|
||||
|
||||
public int $order = -80;
|
||||
|
||||
public bool $skipExisting = true;
|
||||
|
||||
public array $fieldList;
|
||||
|
||||
public function render() : string
|
||||
{
|
||||
return $this->renderSegments([
|
||||
static::SQL_TOKEN, $this->renderTables($this->table), $this->renderFields(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function renderFields() : string
|
||||
{
|
||||
return "(" . PHP_EOL . implode("," . PHP_EOL, array_map(function($field) {
|
||||
return " " . EntityField::generateAlterColumn($field);
|
||||
}, $this->fieldList)) . PHP_EOL . ")";
|
||||
}
|
||||
}
|
|
@ -351,6 +351,30 @@ class QueryBuilder implements Query\QueryBuilderInterface
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function alter(array $fieldlist, string $table, ? string $database = null, ? string $schema = null) : self
|
||||
{
|
||||
if ( null === $this->getFragment(Query\Create::class) ) {
|
||||
if ( $schema ) {
|
||||
$table = "$schema.$table";
|
||||
}
|
||||
|
||||
if ( $database ) {
|
||||
$table = "$database.$table";
|
||||
}
|
||||
|
||||
$alter = new Query\Alter();
|
||||
$this->push($alter);
|
||||
|
||||
$alter->fieldList = $fieldlist;
|
||||
$alter->table = $table;
|
||||
}
|
||||
else {
|
||||
throw new \Exception("A create SQL fragment was already found within the query builder");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function engine(string $value) : self
|
||||
{
|
||||
if ( null === $engine = $this->getFragment(Query\Engine::class) ) {
|
||||
|
@ -413,13 +437,22 @@ class QueryBuilder implements Query\QueryBuilderInterface
|
|||
return null;
|
||||
}
|
||||
|
||||
public function removeFragment(Query\Fragment $fragment) : void
|
||||
public function removeFragment(/*Query\Fragment|array*/ $fragment) : void
|
||||
{
|
||||
is_object($fragment) && $fragment = get_class($fragment);
|
||||
|
||||
foreach($this->queryStack as $key => $item) {
|
||||
if ( $item === $fragment ) {
|
||||
if ( get_class($item) === $fragment ) {
|
||||
unset($this->queryStack[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if ( $fragment === Query\Where::class ) {
|
||||
unset($this->where);
|
||||
}
|
||||
elseif ( $fragment === Query\Having::class ) {
|
||||
unset($this->having);
|
||||
}
|
||||
}
|
||||
|
||||
public function getFragments() : array
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Ulmus;
|
||||
|
||||
use Ulmus\Annotation\Property\{Field, Where, Having, Relation, Join, WithJoin, Relation\Ignore as RelationIgnore};
|
||||
use Ulmus\Annotation\Property\{ Field, Filter, FilterJoin, Where, Having, Relation, Join, WithJoin, Relation\Ignore as RelationIgnore };
|
||||
use Ulmus\Common\EntityResolver;
|
||||
|
||||
class Repository
|
||||
|
@ -63,7 +63,7 @@ class Repository
|
|||
|
||||
public function count() : int
|
||||
{
|
||||
$this->removeQueryFragment($this->queryBuilder->getFragment(Query\Select::class));
|
||||
$this->removeQueryFragment(Query\Select::class);
|
||||
|
||||
if ( $this->queryBuilder->getFragment(Query\GroupBy::class) ) {
|
||||
$this->select( "DISTINCT COUNT(*) OVER ()" );
|
||||
|
@ -79,12 +79,12 @@ class Repository
|
|||
return Ulmus::runSelectQuery($this->queryBuilder, $this->adapter)->fetchColumn(0);
|
||||
}
|
||||
|
||||
protected function deleteOne()
|
||||
public function deleteOne()
|
||||
{
|
||||
return $this->limit(1)->deleteSqlQuery()->runQuery();
|
||||
}
|
||||
|
||||
protected function deleteAll()
|
||||
public function deleteAll()
|
||||
{
|
||||
return $this->deleteSqlQuery()->runQuery();
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ class Repository
|
|||
}
|
||||
}
|
||||
|
||||
public function save(object $entity) : bool
|
||||
public function save(object $entity, ? array $fieldsAndValue = null) : bool
|
||||
{
|
||||
if ( ! $this->matchEntity($entity) ) {
|
||||
throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`");
|
||||
|
@ -136,7 +136,7 @@ class Repository
|
|||
$primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
|
||||
|
||||
if ( ! $entity->isLoaded() ) {
|
||||
$statement = $this->insertSqlQuery($dataset)->runQuery();
|
||||
$statement = $this->insertSqlQuery($fieldsAndValue ?? $dataset)->runQuery();
|
||||
|
||||
if ( ( 0 !== $statement->lastInsertId ) &&
|
||||
( null !== $primaryKeyDefinition )) {
|
||||
|
@ -152,7 +152,9 @@ class Repository
|
|||
throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass));
|
||||
}
|
||||
|
||||
if ( [] !== $diff = $this->generateDatasetDiff($entity) ) {
|
||||
$diff = $fieldsAndValue ?? $this->generateDatasetDiff($entity);
|
||||
|
||||
if ( [] !== $diff ) {
|
||||
$pkField = key($primaryKeyDefinition);
|
||||
$pkFieldName = $primaryKeyDefinition[$pkField]->name ?? $pkField;
|
||||
$this->where($pkFieldName, $dataset[$pkFieldName]);
|
||||
|
@ -211,13 +213,6 @@ class Repository
|
|||
}
|
||||
}
|
||||
|
||||
public function removeQueryFragment(? Query\Fragment $fragment) : self
|
||||
{
|
||||
$fragment && $this->queryBuilder->removeFragment($fragment);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function selectEntity(string $entity, string $alias, string $prependField = "") : self
|
||||
{
|
||||
$prependField and ($prependField .= "$");
|
||||
|
@ -435,7 +430,6 @@ class Repository
|
|||
$this->select("{$this->alias}.*");
|
||||
}
|
||||
|
||||
# @TODO Apply FILTER annotation to this too !
|
||||
foreach(array_filter((array) $fields) as $item) {
|
||||
$annotation = $this->entityResolver->searchFieldAnnotation($item, new Join) ?:
|
||||
$this->entityResolver->searchFieldAnnotation($item, new Relation);
|
||||
|
@ -467,6 +461,10 @@ class Repository
|
|||
$this->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->value, $condition->operator);
|
||||
}
|
||||
|
||||
foreach($this->entityResolver->searchFieldAnnotationList($item, new Filter() ) as $filter) {
|
||||
call_user_func_array([ $this->entityClass, $filter->method ], [ $this, $item ]);
|
||||
}
|
||||
|
||||
$this->close();
|
||||
|
||||
$key = is_string($annotation->key) ? $this->entityClass::field($annotation->key) : $annotation->key;
|
||||
|
@ -488,6 +486,10 @@ class Repository
|
|||
$join->where(is_object($field) ? $field : $entity::field($field, $alias), $condition->value, $condition->operator);
|
||||
}
|
||||
}
|
||||
|
||||
foreach($this->entityResolver->searchFieldAnnotationList($item, new FilterJoin() ) as $filter) {
|
||||
call_user_func_array([ $this->entityClass, $filter->method ], [ $join, $item ]);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
@ -560,30 +562,6 @@ class Repository
|
|||
return $this;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public function collectionFromQuery(? string $entityClass = null) : EntityCollection
|
||||
{
|
||||
$class = $entityClass ?: $this->entityClass;
|
||||
|
@ -677,7 +655,7 @@ class Repository
|
|||
public function createSqlQuery() : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Create::class) ) {
|
||||
$this->queryBuilder->create($this->escapeFieldList($this->entityResolver->fieldList()), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName());
|
||||
$this->queryBuilder->create($this->escapeFieldList($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true)), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Engine::class) ) {
|
||||
|
@ -689,6 +667,16 @@ class Repository
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function alterSqlQuery(array $fields) : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Alter::class) ) {
|
||||
$this->queryBuilder->create($this->escapeFieldList($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true)), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName());
|
||||
}
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function fromRow($row) : self
|
||||
{
|
||||
|
||||
|
@ -770,4 +758,29 @@ class Repository
|
|||
}
|
||||
|
||||
protected function finalizeQuery() : void {}
|
||||
|
||||
public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest, bool $count = true) : self
|
||||
{
|
||||
if ($count) {
|
||||
# @TODO Must be placed inside an event instead of directly there !
|
||||
$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);
|
||||
}
|
||||
}
|
|
@ -150,4 +150,13 @@ trait ConditionTrait
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeQueryFragment(/* Query\Fragment | stringable | array */ $fragment) : self
|
||||
{
|
||||
foreach((array) $fragment as $item) {
|
||||
$this->queryBuilder->removeFragment($item);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -9,6 +9,10 @@ use Closure;
|
|||
|
||||
class RelationBuilder
|
||||
{
|
||||
const SUBQUERY_FIELD_SUFFIX = "%s\$collection";
|
||||
|
||||
const JOIN_FIELD_SEPARATOR = "%s\$";
|
||||
|
||||
protected Repository $repository;
|
||||
|
||||
protected /*object|string*/ $entity;
|
||||
|
@ -152,7 +156,7 @@ class RelationBuilder
|
|||
}
|
||||
|
||||
foreach($data ?: $this->entity->entityLoadedDataset as $key => $value) {
|
||||
if ( $key === "{$name}\$collection" ) {
|
||||
if ( $key === sprintf(static::SUBQUERY_FIELD_SUFFIX, $name) ) {
|
||||
if ($value) {
|
||||
if ( null === ( $dataset = \json_decode($value, true) ) ) {
|
||||
throw new \Exception(sprintf("JSON error '%s' from '%s'", \json_last_error_msg(), $value));
|
||||
|
@ -164,7 +168,7 @@ class RelationBuilder
|
|||
return $entity::entityCollection();
|
||||
}
|
||||
}
|
||||
elseif ( substr($key, 0, $len ) === "{$name}\$" ) {
|
||||
elseif ( substr($key, 0, $len ) === sprintf(static::JOIN_FIELD_SEPARATOR, $name) ) {
|
||||
$vars[substr($key, $len)] = $value;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue