- Multiple bug fixes related to notes 2.x

This commit is contained in:
Dave Mc Nicoll 2024-05-31 12:26:39 +00:00
parent f3be11a590
commit bd9078230d
9 changed files with 69 additions and 73 deletions

View File

@ -35,7 +35,7 @@ class Relation implements ResettablePropertyInterface {
try { try {
$e = $this->entity; $e = $this->entity;
} catch (\Throwable $ex) { } catch (\Throwable $ex) {
throw new \Exception("Your @Relation annotation seems to be missing an `entity` entry."); throw new \Exception("Your @Relation attribute seems to be missing an `entity` entry.");
} }
return new $e(); return new $e();

View File

@ -2,12 +2,10 @@
namespace Ulmus\Common; namespace Ulmus\Common;
use Ulmus\Annotation\Annotation; use Ulmus\Attribute\Property\{ Field };
use Ulmus\Attribute;
use Ulmus\Migration\FieldDefinition; use Ulmus\Migration\FieldDefinition;
use Ulmus\Ulmus, use Ulmus\Ulmus,
Ulmus\Adapter\AdapterInterface, Ulmus\Adapter\AdapterInterface,
Ulmus\Annotation\Property\Field,
Ulmus\Query\WhereRawParameter; Ulmus\Query\WhereRawParameter;
class EntityField implements WhereRawParameter class EntityField implements WhereRawParameter
@ -30,7 +28,7 @@ class EntityField implements WhereRawParameter
public function name($useAlias = true) : string public function name($useAlias = true) : string
{ {
$name = $this->entityResolver->searchFieldAnnotation($this->name, [ Attribute\Property\Field::class, Field::class ] )->name ?? $this->name; $name = $this->entityResolver->searchFieldAnnotation($this->name, [ Field::class ] )->name ?? $this->name;
$name = $this->entityResolver->databaseAdapter()->adapter()->escapeIdentifier($name, AdapterInterface::IDENTIFIER_FIELD); $name = $this->entityResolver->databaseAdapter()->adapter()->escapeIdentifier($name, AdapterInterface::IDENTIFIER_FIELD);

View File

@ -12,6 +12,8 @@ use Ulmus\Ulmus,
Ulmus\Attribute\Property\Relation, Ulmus\Attribute\Property\Relation,
Ulmus\Attribute\Property\Virtual; Ulmus\Attribute\Property\Virtual;
use Notes\Common\ReflectedAttribute;
use Notes\ObjectReflection; use Notes\ObjectReflection;
class EntityResolver { class EntityResolver {
@ -118,7 +120,7 @@ class EntityResolver {
return $this->searchFieldAnnotationList($field, $annotationType, $caseSensitive)[0] ?? null; return $this->searchFieldAnnotationList($field, $annotationType, $caseSensitive)[0] ?? null;
} }
public function searchFieldAnnotationList(string $field, array|object|string $annotationType, bool $caseSensitive = true) : array public function searchFieldAnnotationList(string $field, array|object|string $attributeType, bool $caseSensitive = true) : false|array
{ {
$list = []; $list = [];
@ -126,19 +128,11 @@ class EntityResolver {
$search = $caseSensitive ? $properties : array_change_key_case($properties, \CASE_LOWER); $search = $caseSensitive ? $properties : array_change_key_case($properties, \CASE_LOWER);
$annotations = is_array($annotationType) ? $annotationType : [ $annotationType ];
if ( null !== ( $search[$field] ?? null ) ) { if ( null !== ( $search[$field] ?? null ) ) {
foreach($search[$field]->getAttributes() as $tag) { return array_map(fn(ReflectedAttribute $e) => $e->object, $search[$field]->getAttributes((array) $attributeType));
foreach($annotations as $annotation) {
if ( $tag->object instanceof $annotation ) {
$list[] = $tag->object;
}
}
}
} }
return $list; return false;
} }
public function tableName($required = false) : string public function tableName($required = false) : string

View File

@ -20,13 +20,13 @@ class DatasetHandler
public function pull(object $entity) : Generator public function pull(object $entity) : Generator
{ {
foreach($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true) as $key => $field) { foreach($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true) as $key => $field) {
$annotation = $this->entityResolver->searchFieldAnnotation($key,[ Field::class ]); $attribute = $this->entityResolver->searchFieldAnnotation($key,[ Field::class ]);
if ( $entity->__isset($key) ) { if ( $entity->__isset($key) ) {
yield $annotation->name ?? $key => $entity->$key; yield $attribute->name ?? $key => $entity->$key;
} }
elseif ( $field->allowsNull() ) { elseif ( $field->allowsNull() ) {
yield $annotation->name ?? $key => null; yield $attribute->name ?? $key => null;
} }
} }
} }
@ -77,10 +77,10 @@ class DatasetHandler
elseif ( EntityField::isScalarType($type->type) ) { elseif ( EntityField::isScalarType($type->type) ) {
if ( $type->type === 'string' ) { if ( $type->type === 'string' ) {
$annotation = $this->entityResolver->searchFieldAnnotation($field->name, [ Field::class ] ); $attribute = $this->entityResolver->searchFieldAnnotation($field->name, [ Field::class ] );
if ( $annotation->length ?? null ) { if ( $attribute->length ?? null ) {
$value = mb_substr($value, 0, $annotation->length); $value = mb_substr($value, 0, $attribute->length);
} }
} }
elseif ( $type->type === 'bool' ) { elseif ( $type->type === 'bool' ) {

View File

@ -3,8 +3,7 @@
namespace Ulmus\Query; namespace Ulmus\Query;
use Ulmus\Adapter\AdapterInterface; use Ulmus\Adapter\AdapterInterface;
use Ulmus\Annotation, use Ulmus\Common\EntityField;
Ulmus\Common\EntityField;
class Alter extends Fragment { class Alter extends Fragment {

View File

@ -3,8 +3,7 @@
namespace Ulmus\Query; namespace Ulmus\Query;
use Ulmus\Adapter\AdapterInterface; use Ulmus\Adapter\AdapterInterface;
use Ulmus\Annotation, use Ulmus\Common\EntityField;
Ulmus\Common\EntityField;
class Create extends Fragment { class Create extends Fragment {

View File

@ -2,16 +2,9 @@
namespace Ulmus; namespace Ulmus;
use Ulmus\Annotation\Property\{Field, use Ulmus\Attribute\Property\{
OrderBy, Field, OrderBy, Where, Having, Relation, Filter, Join, FilterJoin, WithJoin
Where, };
Having,
Relation,
Filter,
Join,
FilterJoin,
WithJoin,
Relation\Ignore as RelationIgnore};
use Ulmus\Common\EntityResolver; use Ulmus\Common\EntityResolver;
use Ulmus\Repository\WithOptionEnum; use Ulmus\Repository\WithOptionEnum;
@ -88,7 +81,16 @@ class Repository
$this->select( "DISTINCT COUNT(*) OVER ()" ); $this->select( "DISTINCT COUNT(*) OVER ()" );
} }
else { else {
$this->select(Common\Sql::function("COUNT", '*')); $pk = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
if (count($pk) === 1) {
$field = key($pk);
$this->select(Common\Sql::function('COUNT', Common\Sql::raw("DISTINCT " . $this->entityClass::field($field))));
}
else {
$this->select(Common\Sql::function('COUNT', Common\Sql::raw('*')));
}
} }
$this->selectSqlQuery(); $this->selectSqlQuery();
@ -362,7 +364,7 @@ class Repository
$dataset = $this->generateDatasetDiff($entity, $oldValues); $dataset = $this->generateDatasetDiff($entity, $oldValues);
foreach($dataset as $field => $value) { foreach($dataset as $field => $value) {
if ( false === ( $this->entityResolver->searchFieldAnnotation($field, [ Attribute\Property\Field::class, Field::class ], false)->readonly ?? false ) ) { if ( false === ( $this->entityResolver->searchFieldAnnotation($field, [ Field::class, Field::class ], false)->readonly ?? false ) ) {
$intersect[$field] = $field; $intersect[$field] = $field;
} }
} }
@ -395,7 +397,7 @@ class Repository
$prependField and ($prependField .= "$"); $prependField and ($prependField .= "$");
foreach ($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) { foreach ($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
if (null === $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Attribute\Property\Relation\Ignore::class, RelationIgnore::class ])) { if (null === $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Relation\Ignore::class ])) {
$this->select(sprintf("%s.$key as {$prependField}{$field->name}", $this->escapeIdentifier($alias))); $this->select(sprintf("%s.$key as {$prependField}{$field->name}", $this->escapeIdentifier($alias)));
} }
} }
@ -408,7 +410,7 @@ class Repository
$fieldlist = []; $fieldlist = [];
foreach ($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) { foreach ($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
if (null === $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Attribute\Property\Relation\Ignore::class, RelationIgnore::class ])) { if (null === $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Relation\Ignore::class ])) {
$fieldlist[] = $key; $fieldlist[] = $key;
$fieldlist[] = $entity::field($field->name, $this->escapeIdentifier($alias)); $fieldlist[] = $entity::field($field->name, $this->escapeIdentifier($alias));
} }
@ -623,26 +625,26 @@ class Repository
$this->joined[$item] = true; $this->joined[$item] = true;
} }
$annotation = $this->entityResolver->searchFieldAnnotation($item, [ Attribute\Property\Join::class ]) ?: $attribute = $this->entityResolver->searchFieldAnnotation($item, [ Join::class ]) ?:
$this->entityResolver->searchFieldAnnotation($item, [ Attribute\Property\Relation::class ]); $this->entityResolver->searchFieldAnnotation($item, [ Relation::class ]);
$isRelation = ( $annotation instanceof Relation ) || ($annotation instanceof Attribute\Property\Relation); $isRelation = ( $attribute instanceof Relation ) || ($attribute instanceof Relation);
if ($isRelation && ( $annotation->isManyToMany() )) { if ($isRelation && ( $attribute->isManyToMany() )) {
throw new \Exception("Many-to-many relation can not be preloaded within joins."); throw new \Exception("Many-to-many relation can not be preloaded within joins.");
} }
if ( $annotation ) { if ( $attribute ) {
$alias = $annotation->alias ?? $item; $alias = $attribute->alias ?? $item;
$entity = $annotation->entity ?? $this->entityResolver->reflectedClass->getProperties(true)[$item]->getTypes()[0]->type; $entity = $attribute->entity ?? $this->entityResolver->reflectedClass->getProperties(true)[$item]->getTypes()[0]->type;
foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) { foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
if ( null === $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Attribute\Property\Relation\Ignore::class ]) ) { if ( null === $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Relation\Ignore::class ]) ) {
$escAlias = $this->escapeIdentifier($alias); $escAlias = $this->escapeIdentifier($alias);
$fieldName = $this->escapeIdentifier($key); $fieldName = $this->escapeIdentifier($key);
$name = $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Attribute\Property\Field::class ])->name ?? $field->name; $name = $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Field::class ])->name ?? $field->name;
$this->select("$escAlias.$fieldName as $alias\${$name}"); $this->select("$escAlias.$fieldName as $alias\${$name}");
@ -652,7 +654,7 @@ class Repository
$this->open(); $this->open();
if ( ! in_array(WithOptionEnum::SkipWhere, $options)) { if ( ! in_array(WithOptionEnum::SkipWhere, $options)) {
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\Where::class ] ) as $condition) { foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ] ) as $condition) {
if ( is_object($condition->field) && ( $condition->field->entityClass !== $entity ) ) { if ( is_object($condition->field) && ( $condition->field->entityClass !== $entity ) ) {
$this->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator); $this->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator);
} }
@ -660,26 +662,26 @@ class Repository
} }
if ( ! in_array(WithOptionEnum::SkipHaving, $options)) { if ( ! in_array(WithOptionEnum::SkipHaving, $options)) {
foreach ($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\Having::class ]) as $condition) { foreach ($this->entityResolver->searchFieldAnnotationList($item, [ Having::class ]) as $condition) {
$this->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator); $this->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator);
} }
} }
if ( ! in_array(WithOptionEnum::SkipFilter, $options)) { if ( ! in_array(WithOptionEnum::SkipFilter, $options)) {
foreach ($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\Filter::class ]) as $filter) { foreach ($this->entityResolver->searchFieldAnnotationList($item, [ Filter::class ]) as $filter) {
call_user_func_array([$this->entityClass, $filter->method], [$this, $item, true]); call_user_func_array([$this->entityClass, $filter->method], [$this, $item, true]);
} }
} }
$this->close(); $this->close();
$key = is_string($annotation->key) ? $this->entityClass::field($annotation->key) : $annotation->key; $key = is_string($attribute->key) ? $this->entityClass::field($attribute->key) : $attribute->key;
$foreignKey = is_string($annotation->foreignKey) ? $entity::field($annotation->foreignKey, $alias) : $annotation->foreignKey; $foreignKey = is_string($attribute->foreignKey) ? $entity::field($attribute->foreignKey, $alias) : $attribute->foreignKey;
$this->join("LEFT", $entity::resolveEntity()->tableName(), $key, $foreignKey, $alias, function($join) use ($item, $entity, $alias, $options) { $this->join("LEFT", $entity::resolveEntity()->tableName(), $key, $foreignKey, $alias, function($join) use ($item, $entity, $alias, $options) {
if ( ! in_array(WithOptionEnum::SkipJoinWhere, $options)) { if ( ! in_array(WithOptionEnum::SkipJoinWhere, $options)) {
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\Where::class ]) as $condition) { foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ]) as $condition) {
if ( ! is_object($condition->field) ) { if ( ! is_object($condition->field) ) {
$field = $this->entityClass::field($condition->field); $field = $this->entityClass::field($condition->field);
} }
@ -697,14 +699,14 @@ class Repository
} }
if ( ! in_array(WithOptionEnum::SkipJoinFilter, $options) ) { if ( ! in_array(WithOptionEnum::SkipJoinFilter, $options) ) {
foreach ($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\FilterJoin::class ]) as $filter) { foreach ($this->entityResolver->searchFieldAnnotationList($item, [ FilterJoin::class ]) as $filter) {
call_user_func_array([$this->entityClass, $filter->method], [$join, $item, true]); call_user_func_array([$this->entityClass, $filter->method], [$join, $item, true]);
} }
} }
}); });
} }
else { else {
throw new \Exception("Referenced field `$item` which do not exist or do not contain a valid @Join or @Relation annotation."); throw new \Exception("Referenced field `$item` which do not exist or do not contain a valid @Join or @Relation attribute.");
} }
} }
@ -724,7 +726,7 @@ class Repository
# Apply FILTER annotation to this too ! # Apply FILTER annotation to this too !
foreach(array_filter((array) $fields) as $item) { foreach(array_filter((array) $fields) as $item) {
if ( $relation = $this->entityResolver->searchFieldAnnotation($item, [ Attribute\Property\Relation::class ]) ) { if ( $relation = $this->entityResolver->searchFieldAnnotation($item, [ Relation::class ]) ) {
$alias = $relation->alias ?? $item; $alias = $relation->alias ?? $item;
if ( $relation->isManyToMany() ) { if ( $relation->isManyToMany() ) {
@ -745,11 +747,11 @@ class Repository
# $relation->isManyToMany() and $repository->selectJsonEntity($relation->bridge, $relation->bridgeField, true); # $relation->isManyToMany() and $repository->selectJsonEntity($relation->bridge, $relation->bridgeField, true);
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\Where::class ]) as $condition) { foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ]) as $condition) {
$repository->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator); $repository->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator);
} }
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Attribute\Property\Having::class ] ) as $condition) { foreach($this->entityResolver->searchFieldAnnotationList($item, [ Having::class ] ) as $condition) {
$repository->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator); $repository->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator);
} }
@ -766,7 +768,7 @@ class Repository
$this->select("(" . $r = Common\Sql::raw($repository->queryBuilder->render() . ") as $item\$collection")); $this->select("(" . $r = Common\Sql::raw($repository->queryBuilder->render() . ") as $item\$collection"));
} }
else { else {
throw new \Exception("You referenced field `$item` which do not exist or do not contain a valid @Join annotation."); throw new \Exception("You referenced field `$item` which do not exist or do not contain a valid @Join attribute.");
} }
} }
@ -776,9 +778,9 @@ class Repository
public function loadCollectionRelation(EntityCollection $collection, array|string $fields) : void public function loadCollectionRelation(EntityCollection $collection, array|string $fields) : void
{ {
foreach ((array)$fields as $name) { foreach ((array)$fields as $name) {
if (null !== ($relation = $this->entityResolver->searchFieldAnnotation($name, [ Attribute\Property\Relation::class ] ))) { if (null !== ($relation = $this->entityResolver->searchFieldAnnotation($name, [ Relation::class ] ))) {
$order = $this->entityResolver->searchFieldAnnotationList($name, [ Attribute\Property\OrderBy::class ]); $order = $this->entityResolver->searchFieldAnnotationList($name, [ OrderBy::class ]);
$where = $this->entityResolver->searchFieldAnnotationList($name, [ Attribute\Property\Where::class ]); $where = $this->entityResolver->searchFieldAnnotationList($name, [ Where::class ]);
$baseEntity = $relation->entity ?? $relation->bridge ?? $this->entityResolver->properties[$name]['type']; $baseEntity = $relation->entity ?? $relation->bridge ?? $this->entityResolver->properties[$name]['type'];
$baseEntityResolver = $baseEntity::resolveEntity(); $baseEntityResolver = $baseEntity::resolveEntity();
@ -789,7 +791,7 @@ class Repository
$repository = $baseEntity::repository(); $repository = $baseEntity::repository();
foreach ($baseEntityResolver->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) { foreach ($baseEntityResolver->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
if (null === $baseEntityResolver->searchFieldAnnotation($field->name, [ Attribute\Property\Relation\Ignore::class ])) { if (null === $baseEntityResolver->searchFieldAnnotation($field->name, [ Relation\Ignore::class ])) {
$repository->select($baseEntityResolver->entityClass::field($key)); $repository->select($baseEntityResolver->entityClass::field($key));
} }
} }

View File

@ -199,20 +199,20 @@ class RelationBuilder
protected function fetchFromDataset($name, ? array $data = null) : object|bool protected function fetchFromDataset($name, ? array $data = null) : object|bool
{ {
$annotation = $this->resolver->searchFieldAnnotation($name, [ Join::class ]) ?: $attribute = $this->resolver->searchFieldAnnotation($name, [ Join::class ]) ?:
$this->resolver->searchFieldAnnotation($name, [ Relation::class ]); $this->resolver->searchFieldAnnotation($name, [ Relation::class ]);
if ( $annotation ) { if ( $attribute ) {
$vars = []; $vars = [];
$len = strlen( $name ) + 1; $len = strlen( $name ) + 1;
$isRelation = $annotation instanceof Relation; $isRelation = $attribute instanceof Relation;
if ( $isRelation && $annotation->isManyToMany() ) { if ( $isRelation && $attribute->isManyToMany() ) {
$entity = $this->relationAnnotations($name, $annotation)['relationRelation']->entity; $entity = $this->relationAnnotations($name, $attribute)['relationRelation']->entity;
} }
else { else {
$entity = $annotation->entity ?? $this->resolver->reflectedClass->getProperties()[$name]->getTypes()[0]->type; $entity = $attribute->entity ?? $this->resolver->reflectedClass->getProperties()[$name]->getTypes()[0]->type;
} }
$name = strtolower($name); $name = strtolower($name);
@ -323,7 +323,7 @@ class RelationBuilder
$relationRelation = $bridgeEntity->searchFieldAnnotation($relation->foreignField, [ Relation::class ]); $relationRelation = $bridgeEntity->searchFieldAnnotation($relation->foreignField, [ Relation::class ]);
if ($relationRelation === null) { if ($relationRelation === null) {
throw new \Exception("@Relation annotation not found for field `{$relation->foreignField}` in entity {$relation->bridge}"); throw new \Exception("@Relation attribute not found for field `{$relation->foreignField}` in entity {$relation->bridge}");
} }
$relationRelation->entity ??= $relation->bridge::resolveEntity()->reflectedClass->getProperties()[$relation->foreignField]->getTypes()[0]->type; $relationRelation->entity ??= $relation->bridge::resolveEntity()->reflectedClass->getProperties()[$relation->foreignField]->getTypes()[0]->type;

View File

@ -18,6 +18,10 @@ trait SearchRequestFromRequestTrait
public function fromRequest(ServerRequestInterface $request) public function fromRequest(ServerRequestInterface $request)
{ {
if (method_exists($this, 'prepare')) {
$this->prepare($request);
}
$queryParams = new \ArrayObject(array_filter($request->getQueryParams(), function($i) { return ! is_null($i) && $i !== ""; })); $queryParams = new \ArrayObject(array_filter($request->getQueryParams(), function($i) { return ! is_null($i) && $i !== ""; }));
$this->page = $queryParams->offsetExists('page') ? $queryParams['page'] : 1; $this->page = $queryParams->offsetExists('page') ? $queryParams['page'] : 1;