diff --git a/src/Adapter/SQLite.php b/src/Adapter/SQLite.php index 7026e14..8edb808 100644 --- a/src/Adapter/SQLite.php +++ b/src/Adapter/SQLite.php @@ -7,7 +7,7 @@ use Ulmus\Common\PdoObject; use Ulmus\Entity\Sqlite\Table; use Ulmus\Exception\AdapterConfigurationException; use Ulmus\Migration\FieldDefinition; -use Ulmus\{ Repository, QueryBuilder }; +use Ulmus\{Repository, QueryBuilder, Ulmus}; class SQLite implements AdapterInterface { use DefaultAdapterTrait; @@ -38,6 +38,8 @@ class SQLite implements AdapterInterface { $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); $pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC); + + $this->exportFunctions($pdo); } catch(PDOException $ex){ throw $ex; @@ -126,7 +128,23 @@ class SQLite implements AdapterInterface { } } - return $typeOnly ? $type : $type . ( $length ? "($length" . ( $precision ? ",$precision" : "" ) . ")" : "" ); + return $typeOnly ? $type : $type . ( $length ? "($length" . ( isset($precision) ? ",$precision" : "" ) . ")" : "" ); + } + + public function writableValue(/* mixed */ $value) /*: mixed*/ + { + switch (true) { + case is_object($value): + return Ulmus::convertObject($value); + + case is_array($value): + return json_encode($value); + + case is_bool($value): + return (int) $value; + } + + return $value; } public function tableSyntax() : array @@ -147,4 +165,28 @@ class SQLite implements AdapterInterface { { return QueryBuilder\SqliteQueryBuilder::class; } + + public function exportFunctions(PdoObject $pdo) : void + { + $pdo->sqliteCreateFunction('lcase', fn($string) => strtolower($string), 1); + $pdo->sqliteCreateFunction('ucase', fn($string) => strtoupper($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); + $pdo->sqliteCreateFunction('lpad', fn($string, $length, $pad) => str_pad($string, $length, $pad, STR_PAD_LEFT), 3); + $pdo->sqliteCreateFunction('rpad', fn($string, $length, $pad) => str_pad($string, $length, $pad, STR_PAD_RIGHT), 3); + $pdo->sqliteCreateFunction('concat', fn(...$args) => implode('', $args), -1); + $pdo->sqliteCreateFunction('concat_ws', fn($separator, ...$args) => implode($separator, $args), -1); + $pdo->sqliteCreateFunction('find_in_set', function($string, $string_list) { + if ( $string === null || $string_list === null ) { + return null; + } + + if ($string_list === "") { + return 0; + } + + return (int) in_array($string, explode(',', $string_list)); + }, 2); + } } diff --git a/src/Annotation/Property/Field.php b/src/Annotation/Property/Field.php index 30e9519..25e4df6 100644 --- a/src/Annotation/Property/Field.php +++ b/src/Annotation/Property/Field.php @@ -16,6 +16,8 @@ class Field implements \Ulmus\Annotation\Annotation { public bool $nullable; + public /* mixed */ $default; + public bool $readonly = false; public function __construct(? string $type = null, ? int $length = null) diff --git a/src/Annotation/Property/Relation.php b/src/Annotation/Property/Relation.php index a7a28e9..15fadf7 100644 --- a/src/Annotation/Property/Relation.php +++ b/src/Annotation/Property/Relation.php @@ -69,6 +69,18 @@ class Relation implements \Ulmus\Annotation\Annotation { return $this->normalizeType() === 'manytomany'; } + public function function() : string + { + if ($this->function) { + return $this->function; + } + elseif ($this->isOneToOne()) { + return 'load'; + } + + return 'loadAll'; + } + public function hasBridge() : bool { return isset($this->bridge); diff --git a/src/Common/EntityField.php b/src/Common/EntityField.php index 40fc164..c1df7ab 100644 --- a/src/Common/EntityField.php +++ b/src/Common/EntityField.php @@ -15,11 +15,11 @@ class EntityField implements WhereRawParameter public string $entityClass; - public string $alias; + public ? string $alias; protected EntityResolver $entityResolver; - public function __construct(string $entityClass, string $name, string $alias, EntityResolver $resolver) + public function __construct(string $entityClass, string $name, ? string $alias, EntityResolver $resolver) { $this->entityClass = $entityClass; $this->name = $name; diff --git a/src/Common/ObjectReflection.php b/src/Common/ObjectReflection.php index c17ff38..bda150c 100644 --- a/src/Common/ObjectReflection.php +++ b/src/Common/ObjectReflection.php @@ -6,7 +6,7 @@ use Ulmus\Ulmus; use Ulmus\Annotation\AnnotationReader; -use Reflector, ReflectionClass, ReflectionProperty, ReflectionMethod; +use Reflector, ReflectionClass, ReflectionProperty, ReflectionMethod, ReflectionUnionType, ReflectionNamedType, ReflectionParameter; class ObjectReflection { @@ -139,8 +139,8 @@ class ObjectReflection { 'null' => $parameter->allowsNull(), 'position' => $parameter->getPosition(), 'type' => $parameter->hasType() ? $parameter->getType()->getName() : false, - 'array' => $parameter->isArray(), - 'callable' => $parameter->isCallable(), + 'array' => \Notes\ObjectReflection::isType('array', $parameter), + 'callable' => \Notes\ObjectReflection::isType('callable', $parameter), 'optional' => $parameter->isOptional(), 'byReference' => $parameter->isPassedByReference(), ]; @@ -205,6 +205,7 @@ class ObjectReflection { break; case T_STRING: + case T_NAME_QUALIFIED: if ( $isNamespace && $latestString ) { $statement[] = $latestString; } diff --git a/src/EntityCollection.php b/src/EntityCollection.php index 44fc72d..3b09c40 100644 --- a/src/EntityCollection.php +++ b/src/EntityCollection.php @@ -70,7 +70,6 @@ class EntityCollection extends \ArrayObject { public function removeOne($value, string $field, bool $strict = true) : ? object { foreach($this->search($value, $field, $strict) as $key => $item) { - $this->offsetUnset($key); return $item; @@ -79,7 +78,7 @@ class EntityCollection extends \ArrayObject { return null; } - public function remove(/* mixed */ $values, string $field, bool $strict = true) : array + public function remove(/* mixed */ $value, string $field, bool $strict = true) : array { $removed = []; @@ -161,7 +160,7 @@ class EntityCollection extends \ArrayObject { $list = []; foreach($this as $item) { - if ( is_callable($field) ) { + if ( ! is_string($field) && is_callable($field) ) { $value = call_user_func_array($field, [ $item ]); } else { @@ -173,7 +172,7 @@ class EntityCollection extends \ArrayObject { } if ( $keyField !== null ) { - if ( is_callable($keyField) ) { + if ( ! is_string($field) && is_callable($keyField) ) { $key = call_user_func_array($keyField, [ $item ]); } else { diff --git a/src/EntityTrait.php b/src/EntityTrait.php index 7e3edb8..a999a9e 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -9,7 +9,7 @@ use Ulmus\Repository, use Ulmus\Annotation\Classes\{ Method, Table, Collation, }; 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, Blob, Mediumblob, Longblob }; use Ulmus\Annotation\Property\Relation\{ Ignore as RelationIgnore }; trait EntityTrait { @@ -317,7 +317,7 @@ trait EntityTrait { /** * @Ignore */ - public static function field($name, ? string $alias = null) : EntityField + public static function field($name, ? string $alias = Repository::DEFAULT_ALIAS) : EntityField { return new EntityField(static::class, $name, $alias ? Ulmus::repository(static::class)->adapter->adapter()->escapeIdentifier($alias, Adapter\AdapterInterface::IDENTIFIER_FIELD) : Repository::DEFAULT_ALIAS, Ulmus::resolveEntity(static::class)); } @@ -325,7 +325,7 @@ trait EntityTrait { /** * @Ignore */ - public static function fields(array $fields, ? string $alias = null) : string + public static function fields(array $fields, ? string $alias = Repository::DEFAULT_ALIAS) : string { return implode(', ', array_map(function($item) use ($alias){ return static::field($item, $alias); diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 0ec401e..dc70f27 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -463,7 +463,7 @@ class QueryBuilder implements Query\QueryBuilderInterface public function getFragments(/*? Query\Fragment|array*/ $fragment = null) : array { - return $fragment === null ? array_filter($this->queryStack, fn($e) => get_class($e) === $fragment) : $this->queryStack; + return $fragment !== null ? array_filter($this->queryStack, fn($e) => is_a($e, $fragment, true) ) : $this->queryStack; } public function __toString() : string diff --git a/src/Repository.php b/src/Repository.php index f1a619e..085a3bf 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -692,8 +692,6 @@ class Repository { foreach ((array)$fields as $name) { if (null !== ($relation = $this->entityResolver->searchFieldAnnotation($name, new Annotation\Property\Relation()))) { - $relationType = strtolower(str_replace(['-', '_', ' '], '', $relation->type)); - $order = $this->entityResolver->searchFieldAnnotationList($name, new Annotation\Property\OrderBy()); $where = $this->entityResolver->searchFieldAnnotationList($name, new Annotation\Property\Where()); @@ -705,6 +703,12 @@ class Repository $repository = $baseEntity::repository(); + foreach ($baseEntityResolver->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) { + if (null === $baseEntityResolver->searchFieldAnnotation($field['name'], new RelationIgnore)) { + $repository->select($baseEntityResolver->entityClass::field($key)); + } + } + foreach ($where as $condition) { $repository->where($condition->field, is_callable($condition->value) ? call_user_func_array($condition->value, [$this]) : $condition->value, $condition->operator, $condition->condition); } @@ -725,22 +729,16 @@ class Repository $repository->where($key, $values); - switch ($relationType) { - case 'onetoone': - $results = call_user_func([$repository, "loadOne"]); - $item->$name = $results ?: new $baseEntity(); + $results = call_user_func([ $repository, $relation->function() ]); - break; - - case 'onetomany': - $results = call_user_func([$repository, $relation->function]); - - foreach ($collection as $item) { - $item->$name = $baseEntity::entityCollection(); - $item->$name->mergeWith($results->filtersCollection(fn($e) => $e->$property === $item->$entityProperty)); - } - - break; + if ($relation->isOneToOne()) { + $item->$name = $results ?: new $baseEntity(); + } + elseif ($relation->isOneToMany()) { + foreach ($collection as $item) { + $item->$name = $baseEntity::entityCollection(); + $item->$name->mergeWith($results->filtersCollection(fn($e) => $e->$property === $item->$entityProperty)); + } } } } diff --git a/src/Repository/RelationBuilder.php b/src/Repository/RelationBuilder.php index 5c28a79..2768b46 100644 --- a/src/Repository/RelationBuilder.php +++ b/src/Repository/RelationBuilder.php @@ -47,8 +47,13 @@ class RelationBuilder return $this->resolveRelation($name) ?: $this->resolveVirtual($name); } - elseif ( $relation = $this->resolver->searchFieldAnnotation($name, new Relation() ) ) { - return $this->instanciateEmptyObject($name, $relation); + else { + if ( $relation = $this->resolver->searchFieldAnnotation($name, new Relation() ) ) { + return $this->instanciateEmptyObject($name, $relation); + } + elseif ($virtual = $this->resolveVirtual($name)) { + return $virtual; + } } return false;