This commit is contained in:
Dave M. 2021-10-21 19:32:12 +00:00
commit e4767f7c09
11 changed files with 178 additions and 55 deletions

View File

@ -15,7 +15,9 @@ class Field implements \Ulmus\Annotation\Annotation {
public array $attributes = []; public array $attributes = [];
public bool $nullable; public bool $nullable;
public bool $readonly = 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

@ -115,18 +115,20 @@ class EntityResolver {
return null; return null;
} }
public function searchFieldAnnotation(string $field, Annotation $annotationType) : ? Annotation public function searchFieldAnnotation(string $field, Annotation $annotationType, bool $caseSensitive = true) : ? Annotation
{ {
$found = $this->searchFieldAnnotationList($field, $annotationType); $found = $this->searchFieldAnnotationList($field, $annotationType, $caseSensitive);
return $found ? $found[0] : null; return $found ? $found[0] : null;
} }
public function searchFieldAnnotationList(string $field, Annotation $annotationType) : array public function searchFieldAnnotationList(string $field, Annotation $annotationType, bool $caseSensitive = true) : array
{ {
$list = []; $list = [];
if ( null !== ( $this->properties[$field] ?? null ) ) { $search = $caseSensitive ? $this->properties : array_change_key_case($this->properties, \CASE_LOWER);
foreach($this->properties[$field]['tags'] ?? [] as $tag) {
if ( null !== ( $search[$field] ?? null ) ) {
foreach($search[$field]['tags'] ?? [] as $tag) {
if ( $tag['object'] instanceof $annotationType ) { if ( $tag['object'] instanceof $annotationType ) {
$list[] = $tag['object']; $list[] = $tag['object'];
} }

View File

@ -29,7 +29,7 @@ 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 ]); static::$dump && call_user_func_array(static::$dump, [ $sql, $parameters ]);
# \debogueur([ $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);
@ -42,6 +42,21 @@ class PdoObject extends PDO {
return null; return null;
} }
public function runInsertQuery(array $filter, array $dataset)
{
return $this->runQuery($filter, $dataset);
}
public function runUpdateQuery(array $filter, array $dataset)
{
return $this->runQuery($filter, $dataset);
}
public function runDeleteQuery(string $sql, array $parameters = []): ? PDOStatement
{
return $this->runQuery($sql, $parameters);
}
public function execute(PDOStatement $statement, array $parameters = [], bool $commit = true): ? PDOStatement public function execute(PDOStatement $statement, array $parameters = [], bool $commit = true): ? PDOStatement
{ {
try { try {

View File

@ -33,12 +33,12 @@ trait EntityTrait {
/**entityLoadedDataset /**entityLoadedDataset
* @Ignore * @Ignore
*/ */
public function entityFillFromDataset(iterable $dataset) : self public function entityFillFromDataset(iterable $dataset, bool $overwriteDataset = 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;
@ -69,7 +69,7 @@ trait EntityTrait {
$annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() ); $annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() );
if ( $annotation->length ?? null ) { if ( $annotation->length ?? null ) {
$value = substr($value, 0, $annotation->length); $value = mb_substr($value, 0, $annotation->length);
} }
} }
elseif ( $field['type'] === 'bool' ) { elseif ( $field['type'] === 'bool' ) {
@ -86,7 +86,7 @@ trait EntityTrait {
throw new \Error(sprintf("%s for class '%s' on field '%s'", $e->getMessage(), get_class($this), $field['name'])); 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 # Keeping original data to diff on UPDATE query
if ( ! $loaded /* || $isLoadedDataset */ ) { if ( ! $loaded /* || $isLoadedDataset */ ) {
#if ( $field !== null ) { #if ( $field !== null ) {
@ -95,8 +95,11 @@ trait EntityTrait {
#} #}
$this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER); $this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER);
} }
elseif ($overwriteDataset) {
$this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER) + $this->entityLoadedDataset;
}
} }
return $this; return $this;
} }

View File

@ -4,12 +4,16 @@ namespace Ulmus\Query;
class Insert extends Fragment { class Insert extends Fragment {
const SQL_TOKEN = "INSERT"; const SQL_INSERT_TOKEN = "INSERT";
const SQL_REPLACE_TOKEN = "REPLACE";
public int $order = -100; public int $order = -100;
public bool $quick = false; public bool $quick = false;
public bool $replace = false;
public bool $ignore = false; public bool $ignore = false;
public array $fieldlist = []; public array $fieldlist = [];
@ -19,11 +23,11 @@ class Insert extends Fragment {
public string $table; public string $table;
public ? string $alias = null; public ? string $alias = null;
public function render() : string public function render() : string
{ {
return $this->renderSegments([ return $this->renderSegments([
static::SQL_TOKEN, $this->replace ? static::SQL_REPLACE_TOKEN : static::SQL_INSERT_TOKEN,
( $this->priority ?? false ), ( $this->priority ?? false ),
( $this->ignore ? 'IGNORE' : false ), ( $this->ignore ? 'IGNORE' : false ),
'INTO', $this->table, 'INTO', $this->table,

View File

@ -113,7 +113,7 @@ class Where extends Fragment {
} }
# whitelisting operators # 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() protected function value()

View File

@ -64,7 +64,7 @@ class QueryBuilder implements Query\QueryBuilderInterface
return $this; return $this;
} }
public function insert(array $fieldlist, string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self public function insert(array $fieldlist, string $table, ? string $alias = null, ? string $database = null, ? string $schema = null, bool $replace = false) : self
{ {
if ( null === $this->getFragment(Query\Insert::class) ) { if ( null === $this->getFragment(Query\Insert::class) ) {
if ( $schema ) { if ( $schema ) {
@ -77,7 +77,8 @@ class QueryBuilder implements Query\QueryBuilderInterface
$insert = new Query\Insert(); $insert = new Query\Insert();
$this->push($insert); $this->push($insert);
$insert->replace = $replace;
$insert->fieldlist = $fieldlist; $insert->fieldlist = $fieldlist;
$insert->alias = $alias; $insert->alias = $alias;
$insert->table = $table; $insert->table = $table;

View File

@ -56,6 +56,11 @@ class Repository
return $this->collectionFromQuery(); return $this->collectionFromQuery();
} }
public function loadAllFromField($field, $value) : EntityCollection
{
return $this->loadFromField($field, $value);
}
public function loadFromField($field, $value) : EntityCollection public function loadFromField($field, $value) : EntityCollection
{ {
return $this->where($field, $value)->collectionFromQuery(); return $this->where($field, $value)->collectionFromQuery();
@ -81,12 +86,12 @@ class Repository
protected function deleteOne() protected function deleteOne()
{ {
return $this->limit(1)->deleteSqlQuery()->runQuery(); return $this->limit(1)->deleteSqlQuery()->runDeleteQuery();
} }
protected function deleteAll() protected function deleteAll()
{ {
return $this->deleteSqlQuery()->runQuery(); return $this->deleteSqlQuery()->runDeleteQuery();
} }
public function deleteFromPk($value) : bool public function deleteFromPk($value) : bool
@ -125,7 +130,7 @@ class Repository
} }
} }
public function save(/*object|array*/ $entity) : bool public function save(/*object|array*/ $entity, ? array $fieldsAndValue = null, bool $replace = false) : bool
{ {
if ( is_array($entity) ) { if ( is_array($entity) ) {
$entity = ( new $this->entityClass() )->fromArray($entity); $entity = ( new $this->entityClass() )->fromArray($entity);
@ -140,8 +145,8 @@ class Repository
$primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField(); $primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
if ( ! $entity->isLoaded() ) { if ( ! $entity->isLoaded() ) {
$statement = $this->insertSqlQuery($dataset)->runQuery(); $statement = $this->insertSqlQuery($fieldsAndValue ?? $dataset, $replace)->runInsertQuery();
if ( ( 0 !== $statement->lastInsertId ) && if ( ( 0 !== $statement->lastInsertId ) &&
( null !== $primaryKeyDefinition )) { ( null !== $primaryKeyDefinition )) {
@ -156,14 +161,16 @@ class Repository
throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass)); throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass));
} }
if ( [] !== $diff = $this->generateDatasetDiff($entity) ) { $diff = $fieldsAndValue ?? $this->generateWritableDataset($entity);
if ( [] !== $diff ) {
$pkField = key($primaryKeyDefinition); $pkField = key($primaryKeyDefinition);
$pkFieldName = $primaryKeyDefinition[$pkField]->name ?? $pkField; $pkFieldName = $primaryKeyDefinition[$pkField]->name ?? $pkField;
$this->where($pkFieldName, $dataset[$pkFieldName]); $this->where($pkFieldName, $dataset[$pkFieldName]);
$update = $this->updateSqlQuery($diff)->runQuery(); $update = $this->updateSqlQuery($diff)->runUpdateQuery();
$entity->entityFillFromDataset($dataset); $entity->entityFillFromDataset($dataset, true);
return $update ? (bool) $update->rowCount() : false; return $update ? (bool) $update->rowCount() : false;
} }
@ -182,25 +189,25 @@ class Repository
public function loadCollectionRelation(EntityCollection $collection, /*array|string*/ $fields) : void public function loadCollectionRelation(EntityCollection $collection, /*array|string*/ $fields) : void
{ {
foreach((array) $fields as $name) { foreach((array) $fields as $name) {
if ( null !== ( $relation = $this->entityResolver->searchFieldAnnotation($name, new Annotation\Property\Relation() ) ) ) { if (null !== ($relation = $this->entityResolver->searchFieldAnnotation($name, new Annotation\Property\Relation()))) {
$relationType = strtolower(str_replace(['-', '_', ' '], '', $relation->type)); $relationType = strtolower(str_replace(['-', '_', ' '], '', $relation->type));
$order = $this->entityResolver->searchFieldAnnotationList($name, new Annotation\Property\OrderBy() ); $order = $this->entityResolver->searchFieldAnnotationList($name, new Annotation\Property\OrderBy());
$where = $this->entityResolver->searchFieldAnnotationList($name, new Annotation\Property\Where() ); $where = $this->entityResolver->searchFieldAnnotationList($name, new Annotation\Property\Where());
$baseEntity = $relation->entity ?? $relation->bridge ?? $this->entityResolver->properties[$name]['type']; $baseEntity = $relation->entity ?? $relation->bridge ?? $this->entityResolver->properties[$name]['type'];
$baseEntityResolver = $baseEntity::resolveEntity(); $baseEntityResolver = $baseEntity::resolveEntity();
$property = ( $baseEntityResolver->field($relation->foreignKey, 01, false) ?: $baseEntityResolver->field($relation->foreignKey, 02) )['name']; $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']; $entityProperty = ($this->entityResolver->field($relation->key, 01, false) ?: $this->entityResolver->field($relation->key, 02))['name'];
$repository = $baseEntity::repository(); $repository = $baseEntity::repository();
foreach($where as $condition) { 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); $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) { foreach ($order as $item) {
$repository->orderBy($item->field, $item->order); $repository->orderBy($item->field, $item->order);
} }
@ -210,25 +217,25 @@ class Repository
$key = is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity::field($relation->foreignKey); $key = is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity::field($relation->foreignKey);
foreach($collection as $item) { foreach ($collection as $item) {
$values[] = is_callable($field) ? $field($item) : $item->$entityProperty; $values[] = is_callable($field) ? $field($item) : $item->$entityProperty;
} }
$repository->where($key, $values); $repository->where($key, $values);
switch( $relationType ) { switch ($relationType) {
case 'onetoone': case 'onetoone':
$results = call_user_func([ $repository, "loadOne" ]); $results = call_user_func([$repository, "loadOne"]);
$item->$name = $results ?: new $baseEntity(); $item->$name = $results ?: new $baseEntity();
break; break;
case 'onetomany': case 'onetomany':
$results = call_user_func([ $repository, $relation->function ]); $results = call_user_func([$repository, $relation->function]);
foreach($collection as $item) { foreach ($collection as $item) {
$item->$name = $baseEntity::entityCollection(); $item->$name = $baseEntity::entityCollection();
$item->$name->mergeWith( $results->filtersCollection(fn($e) => $e->$property === $item->$entityProperty ) ); $item->$name->mergeWith($results->filtersCollection(fn($e) => $e->$property === $item->$entityProperty));
} }
break; break;
@ -237,6 +244,18 @@ class Repository
} }
} }
public function replace(/*object|array*/ $entity, ? array $fieldsAndValue = null) : bool
{
return $this->save($entity, $fieldsAndValue, true);
}
public function replaceAll(/*EntityCollection|array*/ $collection) : void
{
foreach($collection as $entity) {
$this->replace($entity);
}
}
public function truncate(? string $table = null, ? string $alias = null, ? string $schema = null) : self public function truncate(? string $table = null, ? string $alias = null, ? string $schema = null) : self
{ {
$schema = $schema ?: $this->entityResolver->schemaName(); $schema = $schema ?: $this->entityResolver->schemaName();
@ -255,9 +274,29 @@ class Repository
return $this->createSqlQuery()->runQuery(); return $this->createSqlQuery()->runQuery();
} }
public function generateDatasetDiff(object $entity) : array public function generateDatasetDiff(object $entity, bool $oldValues = false) : array
{ {
return array_diff_assoc( array_change_key_case($entity->toArray()), array_change_key_case($entity->entityGetDataset(false, true)) ); $array = array_change_key_case($entity->toArray());
$dataset = array_change_key_case($entity->entityGetDataset(false, true));
return array_diff_assoc($oldValues ? $dataset : $array , $oldValues ? $array : $dataset );
}
public function generateWritableDataset(object $entity, bool $oldValues = false) : array
{
$intersect = [];
$dataset = $this->generateDatasetDiff($entity, $oldValues);
foreach($dataset as $field => $value) {
if ( false === ( $this->entityResolver->searchFieldAnnotation($field, new Field, false)->readonly ?? false ) ) {
$intersect[$field] = $field;
}
}
return array_intersect_key($dataset, $intersect);
} }
public function yield() : \Generator public function yield() : \Generator
@ -328,9 +367,9 @@ class Repository
return $this; return $this;
} }
public function insert(array $fieldlist, string $table, string $alias, ? string $schema) : self public function insert(array $fieldlist, string $table, string $alias, ? string $schema, bool $replace = false) : self
{ {
$this->queryBuilder->insert($fieldlist, $this->escapeTable($table), $alias, $this->escapedDatabase(), $schema); $this->queryBuilder->insert($fieldlist, $this->escapeTable($table), $alias, $this->escapedDatabase(), $schema, $replace);
return $this; return $this;
} }
@ -529,6 +568,13 @@ class Repository
$this->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->value, $condition->operator); $this->having(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->value, $condition->operator);
} }
<<<<<<< HEAD
=======
foreach($this->entityResolver->searchFieldAnnotationList($item, new Filter() ) as $filter) {
call_user_func_array([ $this->entityClass, $filter->method ], [ $this, $item, true ]);
}
>>>>>>> dc5e0885512a39387b59e8a2af37c87ff0269931
$this->close(); $this->close();
$key = is_string($annotation->key) ? $this->entityClass::field($annotation->key) : $annotation->key; $key = is_string($annotation->key) ? $this->entityClass::field($annotation->key) : $annotation->key;
@ -550,6 +596,13 @@ class Repository
$join->where(is_object($field) ? $field : $entity::field($field, $alias), $condition->value, $condition->operator); $join->where(is_object($field) ? $field : $entity::field($field, $alias), $condition->value, $condition->operator);
} }
} }
<<<<<<< HEAD
=======
foreach($this->entityResolver->searchFieldAnnotationList($item, new FilterJoin() ) as $filter) {
call_user_func_array([ $this->entityClass, $filter->method ], [ $join, $item, true ]);
}
>>>>>>> dc5e0885512a39387b59e8a2af37c87ff0269931
}); });
} }
else { else {
@ -681,6 +734,27 @@ class Repository
return Ulmus::runQuery($this->queryBuilder, $this->adapter); return Ulmus::runQuery($this->queryBuilder, $this->adapter);
} }
public function runInsertQuery() /* : mixed */
{
$this->finalizeQuery();
return Ulmus::runInsertQuery($this->queryBuilder, $this->adapter);
}
public function runUpdateQuery() /* : mixed */
{
$this->finalizeQuery();
return Ulmus::runUpdateQuery($this->queryBuilder, $this->adapter);
}
public function runDeleteQuery() /* : mixed */
{
$this->finalizeQuery();
return Ulmus::runDeleteQuery($this->queryBuilder, $this->adapter);
}
public function resetQuery() : self public function resetQuery() : self
{ {
$this->queryBuilder->reset(); $this->queryBuilder->reset();
@ -688,10 +762,13 @@ class Repository
return $this; return $this;
} }
protected function insertSqlQuery(array $dataset) : self protected function insertSqlQuery(array $dataset, bool $replace = false) : self
{ {
if ( null === $this->queryBuilder->getFragment(Query\Insert::class) ) { if ( null === $insert = $this->queryBuilder->getFragment(Query\Insert::class) ) {
$this->insert(array_map([ $this, 'escapeField' ] , array_keys($dataset)), $this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName()); $this->insert(array_map([ $this, 'escapeField' ] , array_keys($dataset)), $this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName(), $replace);
}
else {
$insert->replace = $replace;
} }
$this->values($dataset); $this->values($dataset);

View File

@ -53,7 +53,7 @@ trait ConditionTrait
return $this; return $this;
} }
public function notWhere($field, $value, string $operator = Query\Where::OPERATOR_NOT_EQUAL) : self public function notWhere($field, $value, string $operator = Query\Where::OPERATOR_EQUAL) : self
{ {
$this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_AND, true); $this->queryBuilder->where($field, $value, $operator, Query\Where::CONDITION_AND, true);

View File

@ -102,7 +102,7 @@ class RelationBuilder
protected function applyFilter(Repository $repository, string $name) : Repository protected function applyFilter(Repository $repository, string $name) : Repository
{ {
foreach($this->filters ?? [] as $filter) { foreach($this->filters ?? [] as $filter) {
$repository = call_user_func_array([ $this->entity, $filter->method ], [ $repository, $name ]); $repository = call_user_func_array([ $this->entity, $filter->method ], [ $repository, $name, false ]);
} }
return $repository; return $repository;
@ -154,7 +154,8 @@ class RelationBuilder
$name = strtolower($name); $name = strtolower($name);
foreach($data ?: $this->entity->entityLoadedDataset as $key => $value) { foreach($data ?: $this->entity->entityLoadedDataset as $key => $value) {
if ( $key === "{$name}\$collection" ) {
if ( $key === sprintf(static::SUBQUERY_FIELD_SUFFIX, strtolower($name)) ) {
if ($value) { if ($value) {
if ( null === ( $dataset = \json_decode($value, true) ) ) { if ( null === ( $dataset = \json_decode($value, true) ) ) {
throw new \Exception(sprintf("JSON error '%s' from '%s'", \json_last_error_msg(), $value)); throw new \Exception(sprintf("JSON error '%s' from '%s'", \json_last_error_msg(), $value));
@ -166,7 +167,7 @@ class RelationBuilder
return $entity::entityCollection(); return $entity::entityCollection();
} }
} }
elseif ( substr($key, 0, $len ) === "{$name}\$" ) { elseif ( substr($key, 0, $len ) === sprintf(static::JOIN_FIELD_SEPARATOR, strtolower($name)) ) {
$vars[substr($key, $len)] = $value; $vars[substr($key, $len)] = $value;
} }
} }

View File

@ -79,6 +79,24 @@ abstract class Ulmus
return $return; return $return;
} }
public static function runInsertQuery(Query\QueryBuilderInterface $queryBuilder, ? ConnectionAdapter $adapter = null)
{
return static::runQuery($queryBuilder, $adapter);
}
public static function runUpdateQuery(Query\QueryBuilderInterface $queryBuilder, ? ConnectionAdapter $adapter = null)
{
return static::runQuery($queryBuilder, $adapter);
}
public static function runDeleteQuery(Query\QueryBuilderInterface $queryBuilder, ? ConnectionAdapter $adapter = null)
{
$return = static::connector($adapter)->runDeleteQuery($queryBuilder->render(), array_merge($queryBuilder->values ?? [], $queryBuilder->parameters ?? []));
$queryBuilder->reset();
return $return;
}
public static function resolveEntity(string $entityClass) : Common\EntityResolver public static function resolveEntity(string $entityClass) : Common\EntityResolver
{ {
return static::$resolved[$entityClass] ?? static::$resolved[$entityClass] = new Common\EntityResolver($entityClass); return static::$resolved[$entityClass] ?? static::$resolved[$entityClass] = new Common\EntityResolver($entityClass);