Merge branch 'master' of https://git.mcnd.ca/mcndave/ulmus
This commit is contained in:
commit
e4767f7c09
|
@ -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 ) {
|
||||
|
|
|
@ -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'];
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue