diff --git a/src/Annotation/Property/Field.php b/src/Annotation/Property/Field.php index 1a4efcc..05938ca 100644 --- a/src/Annotation/Property/Field.php +++ b/src/Annotation/Property/Field.php @@ -7,15 +7,13 @@ class Field implements \Ulmus\Annotation\Annotation { public string $type; public string $name; - - # public string $column; - + public int $length; public array $attributes = []; public bool $nullable = false; - + public function __construct(? string $type = null, ? int $length = null) { if ( $type !== null ) { diff --git a/src/Annotation/Property/Virtual.php b/src/Annotation/Property/Virtual.php new file mode 100644 index 0000000..e6a1d78 --- /dev/null +++ b/src/Annotation/Property/Virtual.php @@ -0,0 +1,7 @@ +properties as $item) { foreach($item['tags'] ?? [] as $tag) { if ( $tag['object'] instanceof Field ) { + if ( $skipVirtual && ($tag['object'] instanceof Virtual )) { + break; + } + switch($fieldKey) { case static::KEY_ENTITY_NAME: $key = $item['name']; diff --git a/src/Common/PdoObject.php b/src/Common/PdoObject.php index ba20a73..3023b62 100644 --- a/src/Common/PdoObject.php +++ b/src/Common/PdoObject.php @@ -6,9 +6,13 @@ use PDO, PDOStatement; class PdoObject extends PDO { + + public static ? string $dump = null; public function select(string $sql, array $parameters = []): PDOStatement { + static::$dump && call_user_func_array(static::$dump, [ $sql, $parameters ]); + try { if (false !== ( $statement = $this->prepare($sql) )) { $statement = $this->execute($statement, $parameters, false); @@ -24,6 +28,8 @@ class PdoObject extends PDO { public function runQuery(string $sql, array $parameters = []): ? PDOStatement { + static::$dump && call_user_func_array(static::$dump, [ $sql, $parameters ]); + try { if (false !== ( $statement = $this->prepare($sql) )) { return $this->execute($statement, $parameters, true); @@ -62,7 +68,9 @@ class PdoObject extends PDO { throw $e; } catch (\Throwable $e) { - debogueur($statement, $parameters, $commit); + if ( function_exists("debogueur") ) { + debogueur($statement, $parameters, $commit); + } throw $e; } diff --git a/src/EntityCollection.php b/src/EntityCollection.php index 3e11387..5f94cfe 100644 --- a/src/EntityCollection.php +++ b/src/EntityCollection.php @@ -8,7 +8,7 @@ class EntityCollection extends \ArrayObject { public ? string $entityClass = null; - public function filters(Callable $callback) : Generator + public function filters(Callable $callback, bool $yieldValueOnly = false) : Generator { $idx = 0; @@ -16,31 +16,41 @@ class EntityCollection extends \ArrayObject { if ( $callback($item, $key, $idx) ) { $idx++; - yield $key => $item; + if ( $yieldValueOnly ) { + yield $item; + } + else { + yield $key => $item; + } } } } - public function filtersCollection(Callable $callback) : self + public function filtersCollection(Callable $callback, bool $yieldValueOnly = false, bool $replaceCollection = false) : self { $collection = new static(); - foreach($this->filters($callback) as $item) { + foreach($this->filters($callback, $yieldValueOnly) as $item) { $collection->append($item); } - return $collection; + if ($replaceCollection) { + $this->exchangeArray(array_values($collection->getArrayCopy())); + + return $this; + } + else { + return $collection; + } } - public function iterate(Callable $callback) : array + public function iterate(Callable $callback) : self { - $results = []; - foreach($this as $item) { - $results[] = $callback($item); + $callback($item); } - return $results; + return $this; } public function removeOne($value, string $field, bool $strict = true) : ? object @@ -68,6 +78,13 @@ class EntityCollection extends \ArrayObject { return $removed; } + 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) { + yield $key => $item; + } + } + public function searchOne($value, string $field, bool $strict = true) : ? object { # Returning first value only @@ -78,24 +95,83 @@ class EntityCollection extends \ArrayObject { return null; } - public function search($value, string $field, bool $strict = true) : Generator + public function searchAll($value, string $field, bool $strict = true) : self { - foreach($this->filters(fn($v) => $strict ? $v->$field === $value : $v->$field == $value) as $key => $item) { - yield $key => $item; + $obj = new static(); + + foreach($this->search($value, $field, $strict) as $item) { + $obj->append($item); } + + return $obj; } - public function column($field) : array + public function column($field, bool $unique = false) : array { $list = []; foreach($this as $item) { - $list[] = $item->$field; + if ( is_callable($field) ) { + $value = call_user_func_array($field, [ $item ]); + } + else { + $value = $item->$field; + } + + if ($unique && in_array($value, $list)) { + break; + } + + $list[] = $value; } return $list; } + public function unique(/*stringable|callable */ $field, bool $strict = false) : self + { + $list = []; + $obj = new static(); + + foreach($this as $item) { + if ( $field === null) { + $value = $this; + } + if ( is_callable($field) ) { + $value = call_user_func_array($field, [ $item ]); + } + else { + $value = $item->$field; + } + + if ( ! in_array($value, $list, $strict) ) { + $list[] = $value; + + $obj->append($item); + } + } + + return $obj; + } + + public function first() : ? object + { + foreach($this as $item) { + return $item; + } + + return null; + } + + public function last() : ? object + { + foreach($this as $item) { + $return = $item; + } + + return $return ?? null; + } + public function buildArray(string $keyColumn, /* string|callable|null */ $value = null) : array { $list = []; @@ -143,7 +219,7 @@ class EntityCollection extends \ArrayObject { return $list; } - public function fromArray(array $datasets, ? string $entityClass = null) : self + public function fromArray(array $datasets, ? string /*stringable*/ $entityClass = null) : self { if ( ! ($this->entityClass || $entityClass) ) { throw new \Exception("An entity class name must be provided to be instanciated and populated before insertion into this collection."); @@ -168,4 +244,31 @@ class EntityCollection extends \ArrayObject { return $this; } + + public function replaceWith( /*array|EntityCollection*/ $datasets ) : self + { + if ( is_object($datasets) ) { + $datasets = $datasets->getArrayCopy(); + } + + $this->exchangeArray( $datasets ); + + return $this; + } + + public function randomize() : self + { + $arr = $this->getArrayCopy(); + shuffle($arr); + $this->exchangeArray($arr); + + return $this; + } + + public function sort(callable $callback, $function = "uasort") : self + { + call_user_func_array([ $this, $function ], [ $callback ]); + + return $this; + } } diff --git a/src/EntityTrait.php b/src/EntityTrait.php index 4521f5f..51acd5f 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -8,7 +8,7 @@ use Ulmus\Repository, Ulmus\Common\EntityField; use Ulmus\Annotation\Classes\{ Method, Table, Collation, }; -use Ulmus\Annotation\Property\{ Field, Relation, OrderBy, Where, Join }; +use Ulmus\Annotation\Property\{ Field, Relation, OrderBy, Where, Join, Virtual }; use Ulmus\Annotation\Property\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, }; trait EntityTrait { @@ -71,7 +71,7 @@ trait EntityTrait { $repository = $baseEntity->repository(); foreach($where as $condition) { - $repository->where($condition->field, $condition->value, $condition->operator); + $repository->where($condition->field, is_callable($condition->value) ? $f = call_user_func_array($condition->value, [ $this ]) : $condition->value, $condition->operator); } foreach($order as $item) { @@ -82,9 +82,11 @@ trait EntityTrait { } switch( $relationType ) { - case 'onetoone': - $repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity->field($relation->foreignKey), $this->$field ); - + case 'onetoone': + if ($relation->foreignKey) { + $repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity->field($relation->foreignKey), is_callable($field) ? $field($this) : $this->$field ); + } + $this->eventExecute(Event\EntityRelationLoadInterface::class, $name, $repository); $result = call_user_func([$repository, $relation->function]); @@ -96,7 +98,9 @@ trait EntityTrait { return $this->$name = $result[0]; case 'onetomany': - $repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity->field($relation->foreignKey), $this->$field ); + if ($relation->foreignKey) { + $repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity->field($relation->foreignKey), is_callable($field) ? $field($this) : $this->$field ); + } $this->eventExecute(Event\EntityRelationLoadInterface::class, $name, $repository); return $this->$name = call_user_func([$repository, $relation->function]); @@ -186,12 +190,12 @@ trait EntityTrait { /** * @Ignore */ - public function entityFillFromDataset(iterable $dataset) : self + public function entityFillFromDataset(iterable $dataset, bool $isLoadedDataset = false) : self { $loaded = $this->isLoaded(); $entityResolver = $this->resolveEntity(); - + foreach($dataset as $key => $value) { $field = $entityResolver->field(strtolower($key), EntityResolver::KEY_COLUMN_NAME, false) ?? null; @@ -235,7 +239,7 @@ trait EntityTrait { } # Keeping original data to diff on UPDATE query - if ( ! $loaded ) { + if ( ! $loaded || $isLoadedDataset ) { #if ( $field !== null ) { # $annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() ); # $this->entityLoadedDataset[$annotation ? $annotation->name : $field['name']] = $dataset; # <--------- THIS TO FIX !!!!!! @@ -268,7 +272,7 @@ trait EntityTrait { $entityResolver = $this->resolveEntity(); - foreach($entityResolver->fieldList() as $key => $field) { + foreach($entityResolver->fieldList(Common\EntityResolver::KEY_ENTITY_NAME, true) as $key => $field) { $annotation = $entityResolver->searchFieldAnnotation($key, new Field() ); if ( isset($this->$key) ) { @@ -292,7 +296,6 @@ trait EntityTrait { } } - # @TODO Must fix recursive bug ! if ($includeRelations) { foreach($entityResolver->properties as $name => $field){ $relation = $entityResolver->searchFieldAnnotation($name, new Relation() ); @@ -303,13 +306,13 @@ trait EntityTrait { $list = []; foreach($value as $entity) { - $list[] = $entity->entityGetDataset(true); + $list[] = $entity->entityGetDataset(false); } $dataset[$name] = $list; } elseif ( is_object($value) ) { - $dataset[$name] = $value->entityGetDataset(true); + $dataset[$name] = $value->entityGetDataset(false); } } } diff --git a/src/Query/Where.php b/src/Query/Where.php index 78ee819..7b24b73 100644 --- a/src/Query/Where.php +++ b/src/Query/Where.php @@ -36,7 +36,7 @@ class Where extends Fragment { $this->condition = $condition; $this->parent = $queryBuilder->where ?? null; } - + public function add($field, $value, string $operator, string $condition, bool $not = false) : self { $this->conditionList[] = [ diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index c158d9d..2c4fc22 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -29,6 +29,23 @@ class QueryBuilder protected array $queryStack = []; + public function __clone() + { + if ($this->where ?? false) { + #$this->where = clone $this->where; + #$this->where->queryBuilder = $this; + } + + if ($this->having ?? false) { + #$this->having = clone $this->having; + #$this->having->queryBuiler = $this; + } + + if ($this->parent ?? false) { + #$this->parent = clone $this->parent; + } + } + public function select($field) : self { if ( null !== ( $select = $this->getFragment(Query\Select::class) ) ) { diff --git a/src/Repository.php b/src/Repository.php index b093bbb..2c019b3 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -29,6 +29,11 @@ class Repository $this->adapter = $adapter ?? $this->entityResolver->databaseAdapter() ?? Ulmus::$defaultAdapter; $this->queryBuilder = new QueryBuilder(); } + + public function __clone() + { + #$this->queryBuilder = clone $this->queryBuilder; + } public function loadOne() : ? object { @@ -137,9 +142,11 @@ class Repository if ( ( 0 !== $statement->lastInsertId ) && ( null !== $primaryKeyDefinition )) { $pkField = key($primaryKeyDefinition); - $entity->$pkField = $statement->lastInsertId; + $dataset[$pkField] = $statement->lastInsertId; } + $entity->entityFillFromDataset($dataset, true); + return true; } else { @@ -153,6 +160,7 @@ class Repository $this->where($pkFieldName, $dataset[$pkFieldName]); $update = $this->updateSqlQuery($diff)->runQuery(); + $entity->entityFillFromDataset($dataset); return $update ? (bool) $update->rowCount() : false; } @@ -199,7 +207,7 @@ class Repository } } - public function select($fields) : self + public function select(/*array|Stringable*/ $fields) : self { $this->queryBuilder->select($fields); @@ -313,6 +321,14 @@ class Repository return $this; } + # @UNTESTED + public function randomizeOrder() : self + { + $this->queryBuilder->orderBy(Common\Sql::function('RAND', Sql::identifier('CURDATE()+0'))); + + return $this; + } + public function orders(array $orderList) : self { foreach($orderList as $field => $direction) { @@ -361,7 +377,7 @@ class Repository return $this->where($primaryKeyField[$pkField]->name ?? $pkField, $value); } - public function withJoin(/*string|array*/ $fields) : self + public function withJoin(/*stringable|array*/ $fields) : self { $resolvedEntity = Ulmus::resolveEntity($this->entityClass); @@ -374,7 +390,7 @@ class Repository $alias = $join->alias ?? $item; $entity = $join->entity ?? $resolvedEntity->properties[$item]['type']; - foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME) as $key => $field) { + foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) { $this->select("$alias.$key as {$alias}\${$field['name']}"); } @@ -397,17 +413,19 @@ class Repository $wheres = $searchRequest->wheres(); $groups = $searchRequest->groups(); - $searchRequest->count = $searchRequest->skipCount ? 0 : $searchRequest->filter( clone $this ) + $searchRequest->filter( $this ) ->wheres($wheres, Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND) ->likes($likes, Query\Where::CONDITION_OR) - ->groups($groups) - ->count(); + ->groups($groups); - return $searchRequest->filter($this) + $searchRequest->count = $searchRequest->skipCount ? 0 : (clone $this)->count(); + + return $searchRequest + ->filter($this) ->wheres($wheres, Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND) ->likes($likes, Query\Where::CONDITION_OR) - ->orders($searchRequest->orders()) ->groups($groups) + ->orders($searchRequest->orders()) ->offset($searchRequest->offset()) ->limit($searchRequest->limit()); } @@ -447,6 +465,13 @@ class Repository return Ulmus::runQuery($this->queryBuilder, $this->adapter); } + public function resetQuery() : self + { + $this->queryBuilder->reset(); + + return $this; + } + protected function insertSqlQuery(array $dataset) : self { if ( null === $this->queryBuilder->getFragment(Query\Insert::class) ) { @@ -505,9 +530,9 @@ class Repository } - public function instanciateEntityCollection() : EntityCollection + public function instanciateEntityCollection(...$arguments) : EntityCollection { - return $this->entityClass::entityCollection(); + return $this->entityClass::entityCollection(...$arguments); } public function escapeTable(string $identifier) : string