Compare commits

...

2 Commits

Author SHA1 Message Date
Dave Mc Nicoll
f74d1907d9 Merge branch 'master' of https://git.mcnd.ca/mcndave/ulmus 2020-11-27 12:21:10 -05:00
Dave Mc Nicoll
30a0a80cbe - Added some new method to EntityCollection (replaceWith, mergeWith, randomize, sort)
- WIP on virtual fields
- Some minor bugfixes
2020-11-27 12:09:15 -05:00
9 changed files with 212 additions and 48 deletions

View File

@ -7,17 +7,15 @@ class Field implements \Ulmus\Annotation\Annotation {
public string $type; public string $type;
public string $name; public string $name;
# public string $column;
public int $length; public int $length;
public int $precision; public int $precision;
public array $attributes = []; public array $attributes = [];
public bool $nullable; public bool $nullable = false;
public function __construct(? string $type = null, ? int $length = null) public function __construct(? string $type = null, ? int $length = null)
{ {
if ( $type !== null ) { if ( $type !== null ) {

View File

@ -0,0 +1,7 @@
<?php
namespace Ulmus\Annotation\Property;
class Virtual extends Field {
public Closure $closure;
}

View File

@ -6,6 +6,7 @@ use Ulmus\Ulmus,
Ulmus\Annotation\Annotation, Ulmus\Annotation\Annotation,
Ulmus\Annotation\Classes\Table, Ulmus\Annotation\Classes\Table,
Ulmus\Annotation\Property\Field, Ulmus\Annotation\Property\Field,
Ulmus\Annotation\Property\Virtual,
Ulmus\Annotation\Property\Relation; Ulmus\Annotation\Property\Relation;
class EntityResolver { class EntityResolver {
@ -51,13 +52,17 @@ class EntityResolver {
return null; return null;
} }
public function fieldList($fieldKey = self::KEY_ENTITY_NAME) : array public function fieldList($fieldKey = self::KEY_ENTITY_NAME, bool $skipVirtual = false) : array
{ {
$fieldList = []; $fieldList = [];
foreach($this->properties as $item) { foreach($this->properties as $item) {
foreach($item['tags'] ?? [] as $tag) { foreach($item['tags'] ?? [] as $tag) {
if ( $tag['object'] instanceof Field ) { if ( $tag['object'] instanceof Field ) {
if ( $skipVirtual && ($tag['object'] instanceof Virtual )) {
break;
}
switch($fieldKey) { switch($fieldKey) {
case static::KEY_ENTITY_NAME: case static::KEY_ENTITY_NAME:
$key = $item['name']; $key = $item['name'];

View File

@ -6,10 +6,13 @@ use PDO,
PDOStatement; PDOStatement;
class PdoObject extends PDO { class PdoObject extends PDO {
public static ? string $dump = null;
public function select(string $sql, array $parameters = []): PDOStatement public function select(string $sql, array $parameters = []): PDOStatement
{ {
//dump($sql, $parameters); static::$dump && call_user_func_array(static::$dump, [ $sql, $parameters ]);
try { try {
if (false !== ( $statement = $this->prepare($sql) )) { if (false !== ( $statement = $this->prepare($sql) )) {
$statement = $this->execute($statement, $parameters, false); $statement = $this->execute($statement, $parameters, false);
@ -25,6 +28,8 @@ class PdoObject extends PDO {
public function runQuery(string $sql, array $parameters = []): ? PDOStatement public function runQuery(string $sql, array $parameters = []): ? PDOStatement
{ {
static::$dump && call_user_func_array(static::$dump, [ $sql, $parameters ]);
try { try {
if (false !== ( $statement = $this->prepare($sql) )) { if (false !== ( $statement = $this->prepare($sql) )) {
return $this->execute($statement, $parameters, true); return $this->execute($statement, $parameters, true);
@ -63,7 +68,9 @@ class PdoObject extends PDO {
throw $e; throw $e;
} }
catch (\Throwable $e) { catch (\Throwable $e) {
debogueur($statement, $parameters, $commit); if ( function_exists("debogueur") ) {
debogueur($statement, $parameters, $commit);
}
throw $e; throw $e;
} }

View File

@ -8,7 +8,7 @@ class EntityCollection extends \ArrayObject {
public ? string $entityClass = null; public ? string $entityClass = null;
public function filters(Callable $callback) : Generator public function filters(Callable $callback, bool $yieldValueOnly = false) : Generator
{ {
$idx = 0; $idx = 0;
@ -16,31 +16,41 @@ class EntityCollection extends \ArrayObject {
if ( $callback($item, $key, $idx) ) { if ( $callback($item, $key, $idx) ) {
$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(); $collection = new static();
foreach($this->filters($callback) as $item) { foreach($this->filters($callback, $yieldValueOnly) as $item) {
$collection->append($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) { foreach($this as $item) {
$results[] = $callback($item); $callback($item);
} }
return $results; return $this;
} }
public function removeOne($value, string $field, bool $strict = true) : ? object public function removeOne($value, string $field, bool $strict = true) : ? object
@ -68,6 +78,13 @@ class EntityCollection extends \ArrayObject {
return $removed; 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 public function searchOne($value, string $field, bool $strict = true) : ? object
{ {
# Returning first value only # Returning first value only
@ -78,24 +95,83 @@ class EntityCollection extends \ArrayObject {
return null; 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) { $obj = new static();
yield $key => $item;
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 = []; $list = [];
foreach($this as $item) { 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; 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 public function buildArray(string $keyColumn, /* string|callable|null */ $value = null) : array
{ {
$list = []; $list = [];
@ -143,7 +219,7 @@ class EntityCollection extends \ArrayObject {
return $list; 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) ) { if ( ! ($this->entityClass || $entityClass) ) {
throw new \Exception("An entity class name must be provided to be instanciated and populated before insertion into this collection."); 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; 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;
}
} }

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, Relation, OrderBy, Where, Join }; use Ulmus\Annotation\Property\{ Field, Relation, OrderBy, Where, Join, Virtual };
use Ulmus\Annotation\Property\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, }; use Ulmus\Annotation\Property\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, };
trait EntityTrait { trait EntityTrait {
@ -70,7 +70,7 @@ trait EntityTrait {
$repository = $baseEntity->repository(); $repository = $baseEntity->repository();
foreach($where as $condition) { 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) { foreach($order as $item) {
@ -81,10 +81,11 @@ trait EntityTrait {
} }
switch( $relationType ) { switch( $relationType ) {
case 'onetoone': case 'onetoone':
$repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity->field($relation->foreignKey), $this->$field ); if ($relation->foreignKey) {
$repository->limit(1); $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); $this->eventExecute(Event\EntityRelationLoadInterface::class, $name, $repository);
$result = call_user_func([$repository, $relation->function]); $result = call_user_func([$repository, $relation->function]);
@ -96,7 +97,9 @@ trait EntityTrait {
return $this->$name = $result[0]; return $this->$name = $result[0];
case 'onetomany': 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); $this->eventExecute(Event\EntityRelationLoadInterface::class, $name, $repository);
return $this->$name = call_user_func([$repository, $relation->function]); return $this->$name = call_user_func([$repository, $relation->function]);
@ -183,12 +186,12 @@ trait EntityTrait {
/** /**
* @Ignore * @Ignore
*/ */
public function entityFillFromDataset(iterable $dataset) : self public function entityFillFromDataset(iterable $dataset, bool $isLoadedDataset = false) : self
{ {
$loaded = $this->isLoaded(); $loaded = $this->isLoaded();
$entityResolver = $this->resolveEntity(); $entityResolver = $this->resolveEntity();
foreach($dataset as $key => $value) { foreach($dataset as $key => $value) {
$field = $entityResolver->field(strtolower($key), EntityResolver::KEY_COLUMN_NAME, false) ?? null; $field = $entityResolver->field(strtolower($key), EntityResolver::KEY_COLUMN_NAME, false) ?? null;
@ -232,7 +235,7 @@ trait EntityTrait {
} }
# Keeping original data to diff on UPDATE query # Keeping original data to diff on UPDATE query
if ( ! $loaded ) { if ( ! $loaded || $isLoadedDataset ) {
#if ( $field !== null ) { #if ( $field !== null ) {
# $annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() ); # $annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() );
# $this->entityLoadedDataset[$annotation ? $annotation->name : $field['name']] = $dataset; # <--------- THIS TO FIX !!!!!! # $this->entityLoadedDataset[$annotation ? $annotation->name : $field['name']] = $dataset; # <--------- THIS TO FIX !!!!!!
@ -280,7 +283,7 @@ trait EntityTrait {
$entityResolver = $this->resolveEntity(); $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() ); $annotation = $entityResolver->searchFieldAnnotation($key, new Field() );
if ( isset($this->$key) ) { if ( isset($this->$key) ) {
@ -304,7 +307,6 @@ trait EntityTrait {
} }
} }
# @TODO Must fix recursive bug !
if ($includeRelations) { if ($includeRelations) {
foreach($entityResolver->properties as $name => $field){ foreach($entityResolver->properties as $name => $field){
$relation = $entityResolver->searchFieldAnnotation($name, new Relation() ); $relation = $entityResolver->searchFieldAnnotation($name, new Relation() );
@ -315,13 +317,13 @@ trait EntityTrait {
$list = []; $list = [];
foreach($value as $entity) { foreach($value as $entity) {
$list[] = $entity->entityGetDataset(true); $list[] = $entity->entityGetDataset(false);
} }
$dataset[$name] = $list; $dataset[$name] = $list;
} }
elseif ( is_object($value) ) { elseif ( is_object($value) ) {
$dataset[$name] = $value->entityGetDataset(true); $dataset[$name] = $value->entityGetDataset(false);
} }
} }
} }

View File

@ -36,7 +36,7 @@ class Where extends Fragment {
$this->condition = $condition; $this->condition = $condition;
$this->parent = $queryBuilder->where ?? null; $this->parent = $queryBuilder->where ?? null;
} }
public function add($field, $value, string $operator, string $condition, bool $not = false) : self public function add($field, $value, string $operator, string $condition, bool $not = false) : self
{ {
$this->conditionList[] = [ $this->conditionList[] = [

View File

@ -29,6 +29,23 @@ class QueryBuilder
protected array $queryStack = []; 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 public function select($field) : self
{ {
if ( null !== ( $select = $this->getFragment(Query\Select::class) ) ) { if ( null !== ( $select = $this->getFragment(Query\Select::class) ) ) {

View File

@ -29,6 +29,11 @@ class Repository
$this->adapter = $adapter ?? $this->entityResolver->databaseAdapter(); $this->adapter = $adapter ?? $this->entityResolver->databaseAdapter();
$this->queryBuilder = new QueryBuilder(); $this->queryBuilder = new QueryBuilder();
} }
public function __clone()
{
#$this->queryBuilder = clone $this->queryBuilder;
}
public function loadOne() : ? object public function loadOne() : ? object
{ {
@ -138,10 +143,10 @@ class Repository
( null !== $primaryKeyDefinition )) { ( null !== $primaryKeyDefinition )) {
$pkField = key($primaryKeyDefinition); $pkField = key($primaryKeyDefinition);
$entity->$pkField = $statement->lastInsertId; $dataset[$pkField] = $statement->lastInsertId;
} }
return true; $entity->entityFillFromDataset($dataset, true);
} }
else { else {
if ( $primaryKeyDefinition === null ) { if ( $primaryKeyDefinition === null ) {
@ -155,6 +160,8 @@ class Repository
$update = $this->updateSqlQuery($diff)->runQuery(); $update = $this->updateSqlQuery($diff)->runQuery();
$entity->entityFillFromDataset($dataset);
return $update ? (bool) $update->rowCount() : false; return $update ? (bool) $update->rowCount() : false;
} }
} }
@ -205,7 +212,7 @@ class Repository
} }
} }
public function select($fields) : self public function select(/*array|Stringable*/ $fields) : self
{ {
$this->queryBuilder->select($fields); $this->queryBuilder->select($fields);
@ -318,6 +325,14 @@ class Repository
return $this; 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 public function orders(array $orderList) : self
{ {
foreach($orderList as $field => $direction) { foreach($orderList as $field => $direction) {
@ -363,6 +378,7 @@ class Repository
return $this->where($primaryKeyField[$pkField]->name ?? $pkField, $value); return $this->where($primaryKeyField[$pkField]->name ?? $pkField, $value);
} }
public function withJoin(/*string|array*/ $fields) : self public function withJoin(/*string|array*/ $fields) : self
{ {
@ -373,9 +389,10 @@ class Repository
foreach((array) $fields as $item) { foreach((array) $fields as $item) {
if ( null !== $join = $this->entityResolver->searchFieldAnnotation($item, new Annotation\Property\Join) ) { if ( null !== $join = $this->entityResolver->searchFieldAnnotation($item, new Annotation\Property\Join) ) {
$alias = $join->alias ?? $item; $alias = $join->alias ?? $item;
$entity = $join->entity ?? $this->entityResolver->properties[$item]['type']; $entity = $join->entity ?? $this->entityResolver->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']}"); $this->select("$alias.$key as {$alias}\${$field['name']}");
} }
@ -392,6 +409,7 @@ class Repository
return $this; return $this;
} }
public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest) : self public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest) : self
{ {
$searchRequest->count = $searchRequest->filter( clone $this ) $searchRequest->count = $searchRequest->filter( clone $this )
@ -443,7 +461,14 @@ class Repository
return Ulmus::runQuery($this->queryBuilder, $this->adapter); return Ulmus::runQuery($this->queryBuilder, $this->adapter);
} }
public function resetQuery() : self
{
$this->queryBuilder->reset();
return $this;
}
protected function insertSqlQuery(array $dataset) : self protected function insertSqlQuery(array $dataset) : self
{ {
if ( null === $this->queryBuilder->getFragment(Query\Insert::class) ) { if ( null === $this->queryBuilder->getFragment(Query\Insert::class) ) {
@ -525,10 +550,10 @@ class Repository
return $result; return $result;
} }
public function instanciateEntityCollection() : EntityCollection public function instanciateEntityCollection(...$arguments) : EntityCollection
{ {
return $this->entityClass::entityCollection(); return $this->entityClass::entityCollection(...$arguments);
} }
public function instanciateEntity() : object public function instanciateEntity() : object