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

@ -16,6 +16,8 @@ class Field implements \Ulmus\Annotation\Annotation {
public bool $nullable;
public bool $readonly = false;
public function __construct(? string $type = null, ? int $length = null)
{
if ( $type !== null ) {

View File

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

View File

@ -29,7 +29,7 @@ class PdoObject extends PDO {
public function runQuery(string $sql, array $parameters = []): ? PDOStatement
{
static::$dump && call_user_func_array(static::$dump, [ $sql, $parameters ]);
# \debogueur([ $sql, $parameters ]);
try {
if (false !== ( $statement = $this->prepare($sql) )) {
return $this->execute($statement, $parameters, true);
@ -42,6 +42,21 @@ class PdoObject extends PDO {
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
{
try {

View File

@ -33,7 +33,7 @@ trait EntityTrait {
/**entityLoadedDataset
* @Ignore
*/
public function entityFillFromDataset(iterable $dataset) : self
public function entityFillFromDataset(iterable $dataset, bool $overwriteDataset = false) : self
{
$loaded = $this->isLoaded();
@ -69,7 +69,7 @@ trait EntityTrait {
$annotation = $entityResolver->searchFieldAnnotation($field['name'], new Field() );
if ( $annotation->length ?? null ) {
$value = substr($value, 0, $annotation->length);
$value = mb_substr($value, 0, $annotation->length);
}
}
elseif ( $field['type'] === 'bool' ) {
@ -95,6 +95,9 @@ trait EntityTrait {
#}
$this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER);
}
elseif ($overwriteDataset) {
$this->entityLoadedDataset = array_change_key_case($dataset, \CASE_LOWER) + $this->entityLoadedDataset;
}
}
return $this;

View File

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

View File

@ -113,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()

View File

@ -64,7 +64,7 @@ class QueryBuilder implements Query\QueryBuilderInterface
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 ( $schema ) {
@ -78,6 +78,7 @@ class QueryBuilder implements Query\QueryBuilderInterface
$insert = new Query\Insert();
$this->push($insert);
$insert->replace = $replace;
$insert->fieldlist = $fieldlist;
$insert->alias = $alias;
$insert->table = $table;

View File

@ -56,6 +56,11 @@ class Repository
return $this->collectionFromQuery();
}
public function loadAllFromField($field, $value) : EntityCollection
{
return $this->loadFromField($field, $value);
}
public function loadFromField($field, $value) : EntityCollection
{
return $this->where($field, $value)->collectionFromQuery();
@ -81,12 +86,12 @@ class Repository
protected function deleteOne()
{
return $this->limit(1)->deleteSqlQuery()->runQuery();
return $this->limit(1)->deleteSqlQuery()->runDeleteQuery();
}
protected function deleteAll()
{
return $this->deleteSqlQuery()->runQuery();
return $this->deleteSqlQuery()->runDeleteQuery();
}
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) ) {
$entity = ( new $this->entityClass() )->fromArray($entity);
@ -140,7 +145,7 @@ class Repository
$primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
if ( ! $entity->isLoaded() ) {
$statement = $this->insertSqlQuery($dataset)->runQuery();
$statement = $this->insertSqlQuery($fieldsAndValue ?? $dataset, $replace)->runInsertQuery();
if ( ( 0 !== $statement->lastInsertId ) &&
( null !== $primaryKeyDefinition )) {
@ -156,14 +161,16 @@ class Repository
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);
$pkFieldName = $primaryKeyDefinition[$pkField]->name ?? $pkField;
$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;
}
@ -182,25 +189,25 @@ class Repository
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() ) ) ) {
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() );
$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'];
$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 ($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) {
foreach ($order as $item) {
$repository->orderBy($item->field, $item->order);
}
@ -210,25 +217,25 @@ class Repository
$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;
}
$repository->where($key, $values);
switch( $relationType ) {
switch ($relationType) {
case 'onetoone':
$results = call_user_func([ $repository, "loadOne" ]);
$results = call_user_func([$repository, "loadOne"]);
$item->$name = $results ?: new $baseEntity();
break;
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->mergeWith( $results->filtersCollection(fn($e) => $e->$property === $item->$entityProperty ) );
$item->$name->mergeWith($results->filtersCollection(fn($e) => $e->$property === $item->$entityProperty));
}
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
{
$schema = $schema ?: $this->entityResolver->schemaName();
@ -255,9 +274,29 @@ class Repository
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
@ -328,9 +367,9 @@ class Repository
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;
}
@ -529,6 +568,13 @@ class Repository
$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();
$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);
}
}
<<<<<<< HEAD
=======
foreach($this->entityResolver->searchFieldAnnotationList($item, new FilterJoin() ) as $filter) {
call_user_func_array([ $this->entityClass, $filter->method ], [ $join, $item, true ]);
}
>>>>>>> dc5e0885512a39387b59e8a2af37c87ff0269931
});
}
else {
@ -681,6 +734,27 @@ class Repository
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
{
$this->queryBuilder->reset();
@ -688,10 +762,13 @@ class Repository
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) ) {
$this->insert(array_map([ $this, 'escapeField' ] , array_keys($dataset)), $this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName());
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(), $replace);
}
else {
$insert->replace = $replace;
}
$this->values($dataset);

View File

@ -53,7 +53,7 @@ trait ConditionTrait
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);

View File

@ -102,7 +102,7 @@ class RelationBuilder
protected function applyFilter(Repository $repository, string $name) : Repository
{
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;
@ -154,7 +154,8 @@ class RelationBuilder
$name = strtolower($name);
foreach($data ?: $this->entity->entityLoadedDataset as $key => $value) {
if ( $key === "{$name}\$collection" ) {
if ( $key === sprintf(static::SUBQUERY_FIELD_SUFFIX, strtolower($name)) ) {
if ($value) {
if ( null === ( $dataset = \json_decode($value, true) ) ) {
throw new \Exception(sprintf("JSON error '%s' from '%s'", \json_last_error_msg(), $value));
@ -166,7 +167,7 @@ class RelationBuilder
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;
}
}

View File

@ -79,6 +79,24 @@ abstract class Ulmus
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
{
return static::$resolved[$entityClass] ?? static::$resolved[$entityClass] = new Common\EntityResolver($entityClass);