- A lot of fixes made again in this patch

This commit is contained in:
Dave Mc Nicoll 2021-05-24 20:03:38 +00:00
parent 4df9ececba
commit 7fb591e1f5
12 changed files with 222 additions and 52 deletions

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -8,6 +8,8 @@ class Relation implements \Ulmus\Annotation\Annotation {
public /*stringable*/ $key; public /*stringable*/ $key;
public /* callable */ $generateKey;
public /*stringable*/ $foreignKey; public /*stringable*/ $foreignKey;
public array $foreignKeys; public array $foreignKeys;

View File

@ -131,7 +131,7 @@ class EntityResolver {
return $found ? $found[0] : null; return $found ? $found[0] : null;
} }
public function searchFieldAnnotationList(string $field, Annotation $annotationType) : array public function searchFieldAnnotationList(string $field, Annotation $annotationType) : array
{ {
$list = []; $list = [];

View File

@ -74,7 +74,7 @@ class EntityCollection extends \ArrayObject {
return null; return null;
} }
public function remove($value, string $field, bool $strict = true) : array public function remove(/* mixed */ $values, string $field, bool $strict = true) : array
{ {
$removed = []; $removed = [];
@ -89,7 +89,7 @@ class EntityCollection extends \ArrayObject {
public function search($value, string $field, bool $strict = true) : Generator 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; yield $key => $item;
} }
} }
@ -104,12 +104,31 @@ class EntityCollection extends \ArrayObject {
return null; 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(); $obj = new static();
foreach($this->search($value, $field, $strict) as $item) { $values = is_array($values) && $compareArray ? [ $values ] : $values;
$obj->append($item);
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; return $obj;

View File

@ -8,7 +8,7 @@ use Ulmus\Repository,
Ulmus\Common\EntityField; Ulmus\Common\EntityField;
use Ulmus\Annotation\Classes\{ Method, Table, Collation, }; 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\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, Datetime as DateTime, Date, Time, Bigint, Tinyint, Text, Mediumtext, Longtext, };
use Ulmus\Annotation\Property\Relation\{ Ignore as RelationIgnore }; use Ulmus\Annotation\Property\Relation\{ Ignore as RelationIgnore };
@ -254,7 +254,23 @@ trait EntityTrait {
{ {
return array_keys($this->resolveEntity()->fieldList()); 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 * @Ignore
*/ */

View File

@ -0,0 +1,10 @@
<?php
namespace Ulmus\Event;
use Ulmus\Repository;
interface PdoObjectSelectInterface
{
public function execute(string $name, Repository $repository): Repository;
}

35
src/Query/Alter.php Normal file
View File

@ -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 . ")";
}
}

View File

@ -351,6 +351,30 @@ class QueryBuilder implements Query\QueryBuilderInterface
return $this; 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 public function engine(string $value) : self
{ {
if ( null === $engine = $this->getFragment(Query\Engine::class) ) { if ( null === $engine = $this->getFragment(Query\Engine::class) ) {
@ -413,13 +437,22 @@ class QueryBuilder implements Query\QueryBuilderInterface
return null; 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) { foreach($this->queryStack as $key => $item) {
if ( $item === $fragment ) { if ( get_class($item) === $fragment ) {
unset($this->queryStack[$key]); unset($this->queryStack[$key]);
} }
} }
if ( $fragment === Query\Where::class ) {
unset($this->where);
}
elseif ( $fragment === Query\Having::class ) {
unset($this->having);
}
} }
public function getFragments() : array public function getFragments() : array

View File

@ -2,7 +2,7 @@
namespace Ulmus; 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; use Ulmus\Common\EntityResolver;
class Repository class Repository
@ -63,7 +63,7 @@ class Repository
public function count() : int public function count() : int
{ {
$this->removeQueryFragment($this->queryBuilder->getFragment(Query\Select::class)); $this->removeQueryFragment(Query\Select::class);
if ( $this->queryBuilder->getFragment(Query\GroupBy::class) ) { if ( $this->queryBuilder->getFragment(Query\GroupBy::class) ) {
$this->select( "DISTINCT COUNT(*) OVER ()" ); $this->select( "DISTINCT COUNT(*) OVER ()" );
@ -79,12 +79,12 @@ class Repository
return Ulmus::runSelectQuery($this->queryBuilder, $this->adapter)->fetchColumn(0); return Ulmus::runSelectQuery($this->queryBuilder, $this->adapter)->fetchColumn(0);
} }
protected function deleteOne() public function deleteOne()
{ {
return $this->limit(1)->deleteSqlQuery()->runQuery(); return $this->limit(1)->deleteSqlQuery()->runQuery();
} }
protected function deleteAll() public function deleteAll()
{ {
return $this->deleteSqlQuery()->runQuery(); 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) ) { if ( ! $this->matchEntity($entity) ) {
throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`"); 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(); $primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
if ( ! $entity->isLoaded() ) { if ( ! $entity->isLoaded() ) {
$statement = $this->insertSqlQuery($dataset)->runQuery(); $statement = $this->insertSqlQuery($fieldsAndValue ?? $dataset)->runQuery();
if ( ( 0 !== $statement->lastInsertId ) && if ( ( 0 !== $statement->lastInsertId ) &&
( null !== $primaryKeyDefinition )) { ( null !== $primaryKeyDefinition )) {
@ -152,7 +152,9 @@ class Repository
throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass)); 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); $pkField = key($primaryKeyDefinition);
$pkFieldName = $primaryKeyDefinition[$pkField]->name ?? $pkField; $pkFieldName = $primaryKeyDefinition[$pkField]->name ?? $pkField;
$this->where($pkFieldName, $dataset[$pkFieldName]); $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 public function selectEntity(string $entity, string $alias, string $prependField = "") : self
{ {
$prependField and ($prependField .= "$"); $prependField and ($prependField .= "$");
@ -435,7 +430,6 @@ class Repository
$this->select("{$this->alias}.*"); $this->select("{$this->alias}.*");
} }
# @TODO Apply FILTER annotation to this too !
foreach(array_filter((array) $fields) as $item) { foreach(array_filter((array) $fields) as $item) {
$annotation = $this->entityResolver->searchFieldAnnotation($item, new Join) ?: $annotation = $this->entityResolver->searchFieldAnnotation($item, new Join) ?:
$this->entityResolver->searchFieldAnnotation($item, new Relation); $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); $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(); $this->close();
$key = is_string($annotation->key) ? $this->entityClass::field($annotation->key) : $annotation->key; $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); $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 { else {
@ -560,30 +562,6 @@ class Repository
return $this; 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 public function collectionFromQuery(? string $entityClass = null) : EntityCollection
{ {
$class = $entityClass ?: $this->entityClass; $class = $entityClass ?: $this->entityClass;
@ -677,7 +655,7 @@ class Repository
public function createSqlQuery() : self public function createSqlQuery() : self
{ {
if ( null === $this->queryBuilder->getFragment(Query\Create::class) ) { 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) ) { if ( null === $this->queryBuilder->getFragment(Query\Engine::class) ) {
@ -689,6 +667,16 @@ class Repository
return $this; 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 protected function fromRow($row) : self
{ {
@ -770,4 +758,29 @@ class Repository
} }
protected function finalizeQuery() : void {} 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);
}
} }

View File

@ -150,4 +150,13 @@ trait ConditionTrait
return $this; return $this;
} }
public function removeQueryFragment(/* Query\Fragment | stringable | array */ $fragment) : self
{
foreach((array) $fragment as $item) {
$this->queryBuilder->removeFragment($item);
}
return $this;
}
} }

View File

@ -9,6 +9,10 @@ use Closure;
class RelationBuilder class RelationBuilder
{ {
const SUBQUERY_FIELD_SUFFIX = "%s\$collection";
const JOIN_FIELD_SEPARATOR = "%s\$";
protected Repository $repository; protected Repository $repository;
protected /*object|string*/ $entity; protected /*object|string*/ $entity;
@ -152,7 +156,7 @@ class RelationBuilder
} }
foreach($data ?: $this->entity->entityLoadedDataset as $key => $value) { foreach($data ?: $this->entity->entityLoadedDataset as $key => $value) {
if ( $key === "{$name}\$collection" ) { if ( $key === sprintf(static::SUBQUERY_FIELD_SUFFIX, $name) ) {
if ($value) { if ($value) {
if ( null === ( $dataset = \json_decode($value, true) ) ) { if ( null === ( $dataset = \json_decode($value, true) ) ) {
throw new \Exception(sprintf("JSON error '%s' from '%s'", \json_last_error_msg(), $value)); throw new \Exception(sprintf("JSON error '%s' from '%s'", \json_last_error_msg(), $value));
@ -164,7 +168,7 @@ class RelationBuilder
return $entity::entityCollection(); 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; $vars[substr($key, $len)] = $value;
} }
} }