diff --git a/src/Adapter/SQLite.php b/src/Adapter/SQLite.php index f603d7f..d8f94a8 100644 --- a/src/Adapter/SQLite.php +++ b/src/Adapter/SQLite.php @@ -159,6 +159,14 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface $pdo->sqliteCreateFunction('length', fn($string) => strlen($string), 1); $pdo->sqliteCreateFunction('lcase', fn($string) => strtolower($string), 1); $pdo->sqliteCreateFunction('ucase', fn($string) => strtoupper($string), 1); + $pdo->sqliteCreateFunction('md5', fn($string) => md5($string), 1); + $pdo->sqliteCreateFunction('sha1', fn($string) => sha1($string), 1); + $pdo->sqliteCreateFunction('sha256', fn($string) => hash('sha256', $string), 1); + $pdo->sqliteCreateFunction('sha512', fn($string) => hash('sha512', $string), 1); + $pdo->sqliteCreateFunction('whirlpool', fn($string) => hash('whirlpool', $string), 1); + $pdo->sqliteCreateFunction('murmur3a', fn($string) => hash('murmur3a', $string), 1); + $pdo->sqliteCreateFunction('murmur3c', fn($string) => hash('murmur3c', $string), 1); + $pdo->sqliteCreateFunction('murmur3f', fn($string) => hash('murmur3f', $string), 1); $pdo->sqliteCreateFunction('left', fn($string, $length) => substr($string, 0, $length), 2); $pdo->sqliteCreateFunction('right', fn($string, $length) => substr($string, -$length), 2); $pdo->sqliteCreateFunction('strcmp', fn($s1, $s2) => strcmp($s1, $s2), 2); diff --git a/src/Adapter/SqlAdapterTrait.php b/src/Adapter/SqlAdapterTrait.php index 6f73d8e..a1860de 100644 --- a/src/Adapter/SqlAdapterTrait.php +++ b/src/Adapter/SqlAdapterTrait.php @@ -134,7 +134,7 @@ trait SqlAdapterTrait case is_object($value): return Ulmus::convertObject($value); - case is_array($value): + case is_array($value): return json_encode($value); case is_bool($value): diff --git a/src/Attribute/Property/Field/ForeignKey.php b/src/Attribute/Property/Field/ForeignKey.php index 11970a9..0c5deeb 100644 --- a/src/Attribute/Property/Field/ForeignKey.php +++ b/src/Attribute/Property/Field/ForeignKey.php @@ -13,7 +13,7 @@ use Ulmus\Attribute\ConstrainActionEnum; class ForeignKey extends PrimaryKey { public function __construct( public ? string $name = null, - public ? string $type = 'bigint', + public ? string $type = null, public null|int|string $length = null, public ? int $precision = null, public array $attributes = [ diff --git a/src/EntityCollection.php b/src/EntityCollection.php index fb412cc..30b1858 100644 --- a/src/EntityCollection.php +++ b/src/EntityCollection.php @@ -360,7 +360,7 @@ class EntityCollection extends \ArrayObject implements \JsonSerializable { public function jsonSerialize(): mixed { - return $this->toArray(); + return $this->toArray(true); } public function append($value) : void diff --git a/src/EntityTrait.php b/src/EntityTrait.php index 7ce9392..0f98799 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -66,7 +66,7 @@ trait EntityTrait { $data = json_decode($value, true); if (json_last_error() !== \JSON_ERROR_NONE) { - throw new \Exception(sprintf("JSON error while decoding in EntityTrait : '%s' given %s with field %s", json_last_error_msg(), $value, json_encode($field))); + throw new \Exception(sprintf("JSON error while decoding in EntityTrait : '%s' given %s", json_last_error_msg(), $value)); } $this->{$field['name']} = $data; @@ -116,7 +116,7 @@ trait EntityTrait { $this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER); } elseif ($overwriteDataset) { - $this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER) + $this->entityLoadedDataset; + $this->entityLoadedDataset = iterator_to_array(array_change_key_case($dataset, \CASE_LOWER)) + $this->entityLoadedDataset; } } @@ -127,11 +127,9 @@ trait EntityTrait { public function resetVirtualProperties() : self { foreach($this->resolveEntity()->properties as $prop => $property) { - if ( empty($property['builtin']) ) { - foreach($property['tags'] as $tag) { - if ( in_array(strtolower($tag['tag']), [ 'relation', 'join', 'virtual' ] ) ) { - unset($this->$prop); - } + foreach($property['tags'] as $tag) { + if ( in_array(strtolower($tag['tag']), [ 'relation', 'join', 'virtual' ] ) ) { + unset($this->$prop); } } } @@ -145,7 +143,7 @@ trait EntityTrait { return $this->entityFillFromDataset($dataset); } - public function entityGetDataset(bool $includeRelations = false, bool $returnSource = false) : array + public function entityGetDataset(bool $includeRelations = false, bool $returnSource = false, bool $rewriteValue = true) : array { if ( $returnSource ) { return $this->entityLoadedDataset; @@ -159,7 +157,10 @@ trait EntityTrait { $annotation = $entityResolver->searchFieldAnnotation($key, [ Attribute\Property\Field::class, Field::class ]); if ( isset($this->$key) ) { - $dataset[$annotation->name ?? $key] = static::repository()->adapter->adapter()->writableValue($this->$key); + $dataset[$annotation->name ?? $key] = $rewriteValue? + static::repository()->adapter->adapter()->writableValue($this->$key) + : + $this->$key; } elseif ( $field['nullable'] ) { $dataset[$annotation->name ?? $key] = null; @@ -169,7 +170,7 @@ trait EntityTrait { # @TODO Must fix recursive bug ! if ($includeRelations) { foreach($entityResolver->properties as $name => $field){ - $relation = $entityResolver->searchFieldAnnotation($key, [ Attribute\Property\Relation::class. Relation::class ] ); + $relation = $entityResolver->searchFieldAnnotation($name, [ Attribute\Property\Relation::class, Relation::class ] ); if ( $relation && isset($this->$name) && ($relation->entity ?? $relation->bridge) !== static::class ) { if ( null !== $value = $this->$name ?? null ) { @@ -196,7 +197,7 @@ trait EntityTrait { #[Ignore] public function toArray($includeRelations = false, array $filterFields = null) : array { - $dataset = $this->entityGetDataset($includeRelations); + $dataset = $this->entityGetDataset($includeRelations, false, false); return $filterFields ? array_intersect_key($dataset, array_flip($filterFields)) : $dataset; } @@ -265,10 +266,6 @@ trait EntityTrait { #[Ignore] public function __clone() { - foreach($this as $prop) { - - } - if ( null !== $pkField = $this->resolveEntity()->getPrimaryKeyField($this) ) { $key = key($pkField); @@ -279,7 +276,7 @@ trait EntityTrait { #[Ignore] public function jsonSerialize() : mixed { - return $this->entityGetDataset(); + return $this->entityGetDataset(true, false, false); } #[Ignore] @@ -310,9 +307,12 @@ trait EntityTrait { } #[Ignore] - public static function field($name, null|string|bool $alias = Repository::DEFAULT_ALIAS) : EntityField + public static function field($name, null|string|false $alias = Repository::DEFAULT_ALIAS) : EntityField { - return new EntityField(static::class, $name, $alias ? Ulmus::repository(static::class)->adapter->adapter()->escapeIdentifier($alias, Adapter\AdapterInterface::IDENTIFIER_FIELD) : ( $alias === false ? '' : Repository::DEFAULT_ALIAS ), Ulmus::resolveEntity(static::class)); + + $default = ( $alias === false ? '' : Repository::DEFAULT_ALIAS ); # bw compatibility, to be deprecated + + return new EntityField(static::class, $name, $alias ? Ulmus::repository(static::class)->adapter->adapter()->escapeIdentifier($alias, Adapter\AdapterInterface::IDENTIFIER_FIELD) : $default, Ulmus::resolveEntity(static::class)); } #[Ignore] diff --git a/src/Query/Where.php b/src/Query/Where.php index 6f7e0ec..fd37cc6 100644 --- a/src/Query/Where.php +++ b/src/Query/Where.php @@ -16,7 +16,7 @@ class Where extends Fragment { const COMPARISON_IS = "IS"; const COMPARISON_NULL = "NULL"; - const SQL_TOKEN = "WHERE"; + const SQL_TOKEN = "WHERE"; public int $order = 50; @@ -35,7 +35,7 @@ class Where extends Fragment { $this->parent = $queryBuilder->where ?? null; } - public function add($field, $value, string $operator, string $condition, bool $not = false) : self + public function add($field, mixed $value, string $operator, string $condition, bool $not = false) : self { $this->validateFieldType($field); # $this->validateValueType($value); @@ -73,7 +73,7 @@ class Where extends Fragment { ]); } - protected function whereCondition($field, $value, string $operator = self::OPERATOR_EQUAL, string $condition = self::CONDITION_AND, bool $not = false) { + protected function whereCondition($field, mixed $value, string $operator = self::OPERATOR_EQUAL, string $condition = self::CONDITION_AND, bool $not = false) { return new class($this->queryBuilder, $field, $value, $operator, $condition, $not) { public mixed $value; @@ -123,7 +123,7 @@ class Where extends Fragment { $stack = []; if ($this->value) { - foreach ($this->value as $item) { + foreach($this->value as $item) { $stack[] = $this->filterValue($item); } } diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 1f43f81..c8745ec 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -198,9 +198,9 @@ class QueryBuilder implements Query\QueryBuilderInterface public function where(string|\Stringable $field, mixed $value, string $operator = Query\Where::OPERATOR_EQUAL, string $condition = Query\Where::CONDITION_AND, bool $not = false) : self { # Empty IN case - #if ( [] === $value ) { - # return $this; - #} + # if ( [] === $value ) { + # return $this; + # } if ( $this->where ?? false ) { $where = $this->where; @@ -259,6 +259,11 @@ class QueryBuilder implements Query\QueryBuilderInterface if ( null === $offset = $this->getFragment(Query\Offset::class) ) { $offset = new Query\Offset(); $this->push($offset); + + # A limit is required to match an offset + if ( null === $limit = $this->getFragment(Query\Limit::class) ) { + $this->limit(\PHP_INT_MAX); + } } $offset->set($value); diff --git a/src/Repository.php b/src/Repository.php index 9fc7f2b..1dcb8b4 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -114,7 +114,7 @@ class Repository throw new Exception\EntityPrimaryKeyUnknown("A primary key value has to be defined to delete an item."); } - return (bool) $this->wherePrimaryKey($value)->deleteOne()->rowCount; + return (bool) $this->wherePrimaryKey($value, null)->deleteOne()->rowCount; } public function destroy(object $entity) : bool @@ -599,7 +599,7 @@ class Repository return $this; } - public function wherePrimaryKey(mixed $value) : self + public function wherePrimaryKey(mixed $value, null|string|bool $alias = self::DEFAULT_ALIAS) : self { if ( null === $primaryKeyField = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField() ) { throw new Exception\EntityPrimaryKeyUnknown("Entity has no field containing attributes 'primary_key'"); @@ -607,7 +607,7 @@ class Repository $pkField = key($primaryKeyField); - return $this->where($this->entityClass::field($primaryKeyField[$pkField]->name ?? $pkField), $value); + return $this->where($this->entityClass::field($primaryKeyField[$pkField]->name ?? $pkField, false), $value); } public function withJoin(string|array $fields, array $options = []) : self diff --git a/src/Repository/RelationBuilder.php b/src/Repository/RelationBuilder.php index a9cdc39..796da0d 100644 --- a/src/Repository/RelationBuilder.php +++ b/src/Repository/RelationBuilder.php @@ -47,7 +47,7 @@ class RelationBuilder } } - public function searchRelation(string $name) : object|bool + public function searchRelation(string $name) : mixed { # Resolve relations here if one is called if ( $this->entity->isLoaded() ) { @@ -55,7 +55,13 @@ class RelationBuilder return $dataset; } - return $this->resolveRelation($name) ?: $this->resolveVirtual($name) ?: false; + if ( false !== $value = $this->resolveRelation($name) ) { + return $value; + } + elseif ( false !== $value = $this->resolveVirtual($name) ) { + return $value; + } + } else { if ( $relation = $this->resolver->searchFieldAnnotation($name, [ Attribute\Property\Relation::class , Relation::class ] ) ) { @@ -69,7 +75,7 @@ class RelationBuilder return false; } - protected function resolveVirtual(string $name) : bool|object + protected function resolveVirtual(string $name) : mixed { if (null !== ($virtual = $this->resolver->searchFieldAnnotation($name, [ Attribute\Property\Virtual::class, Annotation\Property\Virtual::class ]))) { if ($virtual->closure ?? false) { @@ -82,7 +88,7 @@ class RelationBuilder return false; } - protected function resolveRelation(string $name) : bool|object + protected function resolveRelation(string $name) : mixed { if ( null !== ( $relation = $this->resolver->searchFieldAnnotation($name, [ Attribute\Property\Relation::class, Relation::class ] ) ) ) { $this->orders = $this->resolver->searchFieldAnnotationList($name, [ Attribute\Property\OrderBy::class, OrderBy::class ] ); @@ -109,14 +115,14 @@ class RelationBuilder $this->entity->eventExecute(Event\EntityRelationLoadInterface::class, $name, $this->repository); - return call_user_func([ $this->repository, $relation->function() ]); +return call_user_func([ $this->repository, $relation->function() ]); case $relation->isManyToMany(): $this->manyToMany($name, $relation, $relationRelation); $this->entity->eventExecute(Event\EntityRelationLoadInterface::class, $name, $this->repository); - $results = call_user_func([ $this->repository, 'loadAll' ]); + $results = call_user_func([ $this->repository, $relationRelation->function() ]); if ($relation->bridgeField ?? false) { $collection = $relation->bridge::entityCollection();