- Work done on Ulmus which were added within the Bottin de suppléance app

This commit is contained in:
Dave M. 2021-10-21 19:02:21 +00:00
parent 7fb591e1f5
commit 9ad2e94d12
9 changed files with 219 additions and 127 deletions

View File

@ -6,9 +6,10 @@ use Ulmus\Annotation\Annotation;
use Ulmus\Migration\FieldDefinition;
use Ulmus\Ulmus,
Ulmus\Adapter\AdapterInterface,
Ulmus\Annotation\Property\Field;
Ulmus\Annotation\Property\Field,
Ulmus\Query\WhereRawParameter;
class EntityField
class EntityField implements WhereRawParameter
{
public string $name;

View File

@ -83,16 +83,6 @@ class EntityResolver {
throw new \InvalidArgumentException("Given `fieldKey` is unknown to the EntityResolver");
}
if ($escape) {
if ( isset($tag['object']->name) ) {
$tag['object']->name = 2;
}
if ( isset($item['name']) ) {
$item['name'] = 2;
}
}
$fieldList[$key] = $item;
break;

View File

@ -2,11 +2,13 @@
namespace Ulmus\Common;
use Ulmus\Query\WhereRawParameter;
abstract class Sql {
public static function function($name, ...$arguments)
{
return new class($name, ...$arguments) {
return new class($name, ...$arguments) implements WhereRawParameter {
protected string $as = "";
@ -20,19 +22,23 @@ abstract class Sql {
$this->parseArguments();
}
public function __toString() {
public function __toString() : string
{
return implode(' ', array_filter([
"{$this->name}(" . implode(", ", $this->arguments) . ")",
$this->as ? "AS {$this->as}" : false,
]));
}
public function as($fieldName) {
public function as($fieldName) : self
{
$this->as = $fieldName;
return $this;
}
protected function parseArguments() {
protected function parseArguments() : void
{
foreach($this->arguments as &$item) {
$item = Sql::escape($item);
}
@ -42,7 +48,7 @@ abstract class Sql {
public static function identifier(string $identifier) : object
{
return new class($identifier) {
return new class($identifier) implements WhereRawParameter {
protected string $identifier;

View File

@ -87,6 +87,13 @@ class EntityCollection extends \ArrayObject {
return $removed;
}
public function clear() : self
{
$this->exchangeArray([]);
return $this;
}
public function search($value, string $field, bool $strict = true) : Generator
{
foreach($this->filters(fn($v) => isset($v->$field) ? ( $strict ? $v->$field === $value : $v->$field == $value ) : false) as $key => $item) {
@ -134,6 +141,11 @@ class EntityCollection extends \ArrayObject {
return $obj;
}
public function searchInstances(string $className) : self
{
return $this->filtersCollection(fn($obj) => is_a($obj, $className));
}
public function column($field, bool $unique = false) : array
{
$list = [];
@ -146,8 +158,8 @@ class EntityCollection extends \ArrayObject {
$value = $item->$field;
}
if ($unique && in_array($value, $list)) {
break;
if ($unique && in_array($value, $list, true)) {
continue;
}
$list[] = $value;
@ -279,6 +291,13 @@ class EntityCollection extends \ArrayObject {
}
}
public function push($value) : self
{
$this->append($value);
return $this;
}
public function mergeWith( /*array|EntityCollection*/ $datasets ) : self
{
if ( is_object($datasets) ) {
@ -331,6 +350,11 @@ class EntityCollection extends \ArrayObject {
return $this;
}
public function rsort(callable $callback, $function = "uasort") : self
{
return $this->sort(...func_get_args())->reverse();
}
public function pop() /* : mixed */
{
$arr = $this->getArrayCopy();

View File

@ -8,7 +8,7 @@ use Ulmus\Repository,
Ulmus\Common\EntityField;
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, Filter, Relation, OrderBy, Where, 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\Relation\{ Ignore as RelationIgnore };
@ -30,36 +30,7 @@ trait EntityTrait {
*/
public array $entityLoadedDataset = [];
/**
* @Ignore
*/
public function __get(string $name)
{
$relation = new Repository\RelationBuilder($this);
if ( false !== $data = $relation->searchRelation($name) ) {
return $this->$name = $data;
}
throw new \Exception(sprintf("[%s] - Undefined variable: %s", static::class, $name));
}
/**
* @Ignore
*/
public function __isset(string $name) : bool
{
#if ( null !== $relation = static::resolveEntity()->searchFieldAnnotation($name, new Relation() ) ) {
# return isset($this->{$relation->key});
#}
if ( $this->isLoaded() && static::resolveEntity()->searchFieldAnnotation($name, new Relation() ) ) {
return true;
}
return isset($this->$name);
}
/**
/**entityLoadedDataset
* @Ignore
*/
public function entityFillFromDataset(iterable $dataset) : self
@ -71,9 +42,7 @@ trait EntityTrait {
foreach($dataset as $key => $value) {
$field = $entityResolver->field(strtolower($key), EntityResolver::KEY_COLUMN_NAME, false) ?? null;
if ( $field === null ) {
$field = $entityResolver->field(strtolower($key), EntityResolver::KEY_LC_ENTITY_NAME, false);
}
$field ??= $entityResolver->field(strtolower($key), EntityResolver::KEY_LC_ENTITY_NAME, false);
if ( $field === null ) {
if ($this->entityStrictFieldsDeclaration ) {
@ -104,17 +73,22 @@ trait EntityTrait {
}
}
elseif ( $field['type'] === 'bool' ) {
$this->{$field['name']} = (bool) $value;
$value = (bool) $value;
}
$this->{$field['name']} = $value;
}
elseif ( ! $field['builtin'] ) {
$this->{$field['name']} = Ulmus::instanciateObject($field['type'], [ $value ]);
try {
$this->{$field['name']} = Ulmus::instanciateObject($field['type'], [ $value ]);
}
catch(\Error $e) {
throw new \Error(sprintf("%s for class '%s' on field '%s'", $e->getMessage(), get_class($this), $field['name']));
}
}
# Keeping original data to diff on UPDATE query
if ( ! $loaded || $isLoadedDataset ) {
if ( ! $loaded /* || $isLoadedDataset */ ) {
#if ( $field !== null ) {
# $annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() );
# $this->entityLoadedDataset[$annotation ? $annotation->name : $field['name']] = $dataset; # <--------- THIS TO FIX !!!!!!
@ -247,6 +221,35 @@ trait EntityTrait {
return isset($this->$key);
}
/**
* @Ignore
*/
public function __get(string $name)
{
$relation = new Repository\RelationBuilder($this);
if ( false !== $data = $relation->searchRelation($name) ) {
return $this->$name = $data;
}
throw new \Exception(sprintf("[%s] - Undefined variable: %s", static::class, $name));
}
/**
* @Ignore
*/
public function __isset(string $name) : bool
{
#if ( null !== $relation = static::resolveEntity()->searchFieldAnnotation($name, new Relation() ) ) {
# return isset($this->{$relation->key});
#}
if ( $this->isLoaded() && static::resolveEntity()->searchFieldAnnotation($name, new Relation() ) ) {
return true;
}
return isset($this->$name);
}
/**
* @Ignore
*/
@ -306,6 +309,15 @@ trait EntityTrait {
return $collection;
}
/**
* @Ignore
*/
public static function queryBuilder() : QueryBuilder
{
return Ulmus::queryBuilder(static::class);
}
/**
* @Ignore
*/

View File

@ -2,8 +2,7 @@
namespace Ulmus\Query;
use Ulmus\Common\EntityField,
Ulmus\Common\Sql;
use Ulmus\Common\Sql;
class Where extends Fragment {
const OPERATOR_LIKE = "LIKE";
@ -114,7 +113,7 @@ class Where extends Fragment {
}
# whitelisting operators
return in_array(strtoupper($this->operator), [ '=', '!=', '<>', 'LIKE', 'IS', 'IS NOT' ]) ? $this->operator : "=";
return in_array(strtoupper($this->operator), [ '=', '!=', '<>', '>', '<', '>=', '<=', 'LIKE', 'IS', 'IS NOT' ]) ? $this->operator : "=";
}
protected function value()
@ -136,10 +135,11 @@ class Where extends Fragment {
{
if ( $value === null ) {
$this->operator = in_array($this->operator, [ '!=', '<>' ]) ? Where::COMPARISON_IS . " " . Where::CONDITION_NOT : Where::COMPARISON_IS;
return Where::COMPARISON_NULL;
}
elseif ( is_object($value) && ( $value instanceof EntityField ) ) {
return $value->name();
elseif (is_object($value) && ( $value instanceof WhereRawParameter ) ) {
return (string) $value;
}
else {
return $this->queryBuilder->addParameter($value);

View File

@ -0,0 +1,7 @@
<?php
namespace Ulmus\Query;
interface WhereRawParameter {}

View File

@ -2,7 +2,7 @@
namespace Ulmus;
use Ulmus\Annotation\Property\{ Field, Filter, FilterJoin, Where, Having, Relation, Join, WithJoin, Relation\Ignore as RelationIgnore };
use Ulmus\Annotation\Property\{Field, Where, Having, Relation, Join, WithJoin, Relation\Ignore as RelationIgnore};
use Ulmus\Common\EntityResolver;
class Repository
@ -63,7 +63,7 @@ class Repository
public function count() : int
{
$this->removeQueryFragment(Query\Select::class);
$this->removeQueryFragment($this->queryBuilder->getFragment(Query\Select::class));
if ( $this->queryBuilder->getFragment(Query\GroupBy::class) ) {
$this->select( "DISTINCT COUNT(*) OVER ()" );
@ -79,12 +79,12 @@ class Repository
return Ulmus::runSelectQuery($this->queryBuilder, $this->adapter)->fetchColumn(0);
}
public function deleteOne()
protected function deleteOne()
{
return $this->limit(1)->deleteSqlQuery()->runQuery();
}
public function deleteAll()
protected function deleteAll()
{
return $this->deleteSqlQuery()->runQuery();
}
@ -125,8 +125,12 @@ class Repository
}
}
public function save(object $entity, ? array $fieldsAndValue = null) : bool
public function save(/*object|array*/ $entity) : bool
{
if ( is_array($entity) ) {
$entity = ( new $this->entityClass() )->fromArray($entity);
}
if ( ! $this->matchEntity($entity) ) {
throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`");
}
@ -136,7 +140,7 @@ class Repository
$primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
if ( ! $entity->isLoaded() ) {
$statement = $this->insertSqlQuery($fieldsAndValue ?? $dataset)->runQuery();
$statement = $this->insertSqlQuery($dataset)->runQuery();
if ( ( 0 !== $statement->lastInsertId ) &&
( null !== $primaryKeyDefinition )) {
@ -152,9 +156,7 @@ class Repository
throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass));
}
$diff = $fieldsAndValue ?? $this->generateDatasetDiff($entity);
if ( [] !== $diff ) {
if ( [] !== $diff = $this->generateDatasetDiff($entity) ) {
$pkField = key($primaryKeyDefinition);
$pkFieldName = $primaryKeyDefinition[$pkField]->name ?? $pkField;
$this->where($pkFieldName, $dataset[$pkFieldName]);
@ -170,13 +172,71 @@ class Repository
return false;
}
public function saveAll(EntityCollection $collection) : void
public function saveAll(/*EntityCollection|array*/ $collection) : void
{
foreach($collection as $entity) {
$this->save($entity);
}
}
public function loadCollectionRelation(EntityCollection $collection, /*array|string*/ $fields) : void
{
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() );
$baseEntity = $relation->entity ?? $relation->bridge ?? $this->entityResolver->properties[$name]['type'];
$baseEntityResolver = $baseEntity::resolveEntity();
$property = ( $baseEntityResolver->field($relation->foreignKey, 01, false) ?: $baseEntityResolver->field($relation->foreignKey, 02) )['name'];
$entityProperty = ( $this->entityResolver->field($relation->key, 01, false) ?: $this->entityResolver->field($relation->key, 02) )['name'];
$repository = $baseEntity::repository();
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);
}
foreach($order as $item) {
$repository->orderBy($item->field, $item->order);
}
$field = $relation->key;
$values = [];
$key = is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity::field($relation->foreignKey);
foreach($collection as $item) {
$values[] = is_callable($field) ? $field($item) : $item->$entityProperty;
}
$repository->where($key, $values);
switch( $relationType ) {
case 'onetoone':
$results = call_user_func([ $repository, "loadOne" ]);
$item->$name = $results ?: new $baseEntity();
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;
}
}
}
}
public function truncate(? string $table = null, ? string $alias = null, ? string $schema = null) : self
{
$schema = $schema ?: $this->entityResolver->schemaName();
@ -213,6 +273,13 @@ class Repository
}
}
public function removeQueryFragment(? Query\Fragment $fragment) : self
{
$fragment && $this->queryBuilder->removeFragment($fragment);
return $this;
}
public function selectEntity(string $entity, string $alias, string $prependField = "") : self
{
$prependField and ($prependField .= "$");
@ -430,6 +497,7 @@ class Repository
$this->select("{$this->alias}.*");
}
# @TODO Apply FILTER annotation to this too !
foreach(array_filter((array) $fields) as $item) {
$annotation = $this->entityResolver->searchFieldAnnotation($item, new Join) ?:
$this->entityResolver->searchFieldAnnotation($item, new Relation);
@ -461,10 +529,6 @@ class Repository
$this->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->value, $condition->operator);
}
foreach($this->entityResolver->searchFieldAnnotationList($item, new Filter() ) as $filter) {
call_user_func_array([ $this->entityClass, $filter->method ], [ $this, $item ]);
}
$this->close();
$key = is_string($annotation->key) ? $this->entityClass::field($annotation->key) : $annotation->key;
@ -486,10 +550,6 @@ class Repository
$join->where(is_object($field) ? $field : $entity::field($field, $alias), $condition->value, $condition->operator);
}
}
foreach($this->entityResolver->searchFieldAnnotationList($item, new FilterJoin() ) as $filter) {
call_user_func_array([ $this->entityClass, $filter->method ], [ $join, $item ]);
}
});
}
else {
@ -562,6 +622,30 @@ class Repository
return $this;
}
public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest, bool $count = true) : self
{
if ($count) {
$searchRequest->count = $searchRequest->filter($this->serverRequestCountRepository())
->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND)
->likes($searchRequest->likes(), Query\Where::CONDITION_OR)
->groups($searchRequest->groups())
->count();
}
return $searchRequest->filter($this)
->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND)
->likes($searchRequest->likes(), Query\Where::CONDITION_OR)
->orders($searchRequest->orders())
->groups($searchRequest->groups())
->offset($searchRequest->offset())
->limit($searchRequest->limit());
}
protected function serverRequestCountRepository() : Repository
{
return new Repository\ServerRequestCountRepository($this->entityClass, $this->alias, $this->adapter);
}
public function collectionFromQuery(? string $entityClass = null) : EntityCollection
{
$class = $entityClass ?: $this->entityClass;
@ -655,7 +739,7 @@ class Repository
public function createSqlQuery() : self
{
if ( null === $this->queryBuilder->getFragment(Query\Create::class) ) {
$this->queryBuilder->create($this->escapeFieldList($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true)), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName());
$this->queryBuilder->create($this->escapeFieldList($this->entityResolver->fieldList()), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName());
}
if ( null === $this->queryBuilder->getFragment(Query\Engine::class) ) {
@ -667,16 +751,6 @@ class Repository
return $this;
}
public function alterSqlQuery(array $fields) : self
{
if ( null === $this->queryBuilder->getFragment(Query\Alter::class) ) {
$this->queryBuilder->create($this->escapeFieldList($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true)), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName());
}
return $this;
}
protected function fromRow($row) : self
{
@ -758,29 +832,4 @@ class Repository
}
protected function finalizeQuery() : void {}
public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest, bool $count = true) : self
{
if ($count) {
# @TODO Must be placed inside an event instead of directly there !
$searchRequest->count = $searchRequest->filter($this->serverRequestCountRepository())
->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND)
->likes($searchRequest->likes(), Query\Where::CONDITION_OR)
->groups($searchRequest->groups())
->count();
}
return $searchRequest->filter($this)
->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND)
->likes($searchRequest->likes(), Query\Where::CONDITION_OR)
->orders($searchRequest->orders())
->groups($searchRequest->groups())
->offset($searchRequest->offset())
->limit($searchRequest->limit());
}
protected function serverRequestCountRepository() : Repository
{
return new Repository\ServerRequestCountRepository($this->entityClass, $this->alias, $this->adapter);
}
}

View File

@ -9,10 +9,6 @@ use Closure;
class RelationBuilder
{
const SUBQUERY_FIELD_SUFFIX = "%s\$collection";
const JOIN_FIELD_SEPARATOR = "%s\$";
protected Repository $repository;
protected /*object|string*/ $entity;
@ -118,7 +114,7 @@ class RelationBuilder
$this->repository->open();
foreach($this->wheres as $condition) {
$this->repository->where($condition->field, $condition->value, $condition->operator);
$this->repository->where($condition->field, is_callable($condition->value) ? call_user_func_array($condition->value, [ $this->entity ]) : $condition->value, $condition->operator);
}
$this->repository->close();
@ -155,8 +151,10 @@ class RelationBuilder
$entity = $annotation->entity ?? $this->resolver->properties[$name]['type'];
}
$name = strtolower($name);
foreach($data ?: $this->entity->entityLoadedDataset as $key => $value) {
if ( $key === sprintf(static::SUBQUERY_FIELD_SUFFIX, $name) ) {
if ( $key === "{$name}\$collection" ) {
if ($value) {
if ( null === ( $dataset = \json_decode($value, true) ) ) {
throw new \Exception(sprintf("JSON error '%s' from '%s'", \json_last_error_msg(), $value));
@ -168,7 +166,7 @@ class RelationBuilder
return $entity::entityCollection();
}
}
elseif ( substr($key, 0, $len ) === sprintf(static::JOIN_FIELD_SEPARATOR, $name) ) {
elseif ( substr($key, 0, $len ) === "{$name}\$" ) {
$vars[substr($key, $len)] = $value;
}
}
@ -227,6 +225,11 @@ class RelationBuilder
})->where( $this->entity::field($bridgeRelation->foreignKey, $relationAlias), is_string($this->entity) ? $this->entity::field($bridgeRelation->foreignKey) : $this->entity->{$bridgeRelation->foreignKey} );
$this->applyWhere();
$this->applyOrderBy();
if ($selectBridgeField && $relation->bridgeField) {
$this->repository->selectEntity($relation->bridge, $bridgeAlias, $bridgeAlias);
}