From f719e9ca136ea4faf6090d3a87dda7d9893599c6 Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Wed, 11 Sep 2024 10:37:01 +0200 Subject: [PATCH 01/10] rename add_to_domain_data to set_object_data + simplify model_generate --- packages/core/actions/init/seed.php | 2 +- packages/core/actions/model/generate.php | 140 ++++++++++------------- 2 files changed, 62 insertions(+), 80 deletions(-) diff --git a/packages/core/actions/init/seed.php b/packages/core/actions/init/seed.php index eef32b50f..fc503b22e 100644 --- a/packages/core/actions/init/seed.php +++ b/packages/core/actions/init/seed.php @@ -60,7 +60,7 @@ $generate_params = [ 'entity' => $class['name'], ]; - foreach(['lang', 'fields', 'relations', 'add_to_domain_data'] as $param_key) { + foreach(['lang', 'fields', 'relations', 'set_object_data'] as $param_key) { if(isset($class[$param_key])) { $generate_params[$param_key] = $class[$param_key]; } diff --git a/packages/core/actions/model/generate.php b/packages/core/actions/model/generate.php index be52d083b..523b75e91 100644 --- a/packages/core/actions/model/generate.php +++ b/packages/core/actions/model/generate.php @@ -26,13 +26,14 @@ 'type' => 'array', 'default' => [] ], - 'add_to_domain_data' => [ - 'description' => 'Global domain data.', + 'set_object_data' => [ + 'description' => 'Associative array of the generated object data to add to the object.', + 'help' => 'The object is accessible in domain like this "object.{field}"', 'type' => 'array', 'default' => [] ], - 'domain_data' => [ - 'description' => 'Global domain data.', + 'object_data' => [ + 'description' => 'Current object data.', 'type' => 'array', 'default' => [] ], @@ -64,39 +65,44 @@ * Methods */ -$generateMany2One = function($field_conf, $relation_conf, $lang, $domain_data) { - $model_generate_params = [ - 'entity' => $field_conf['foreign_object'], - 'lang' => $lang - ]; - foreach(['fields', 'relations', 'add_to_domain_data'] as $param_key) { - if(isset($relation_conf[$param_key])) { - $model_generate_params[$param_key] = $relation_conf[$param_key]; - } +$getRelationItemsIds = function($item_class, $relation_domain, $object_data) { + $domain = []; + if(!empty($relation_domain)) { + $domain = (new Domain($relation_domain)) + ->parse($object_data) + ->toArray(); } - if(!empty($domain_data)) { - $model_generate_params['domain_data'] = $domain_data; - } - - return eQual::run('do', 'core_model_generate', $model_generate_params); + return $item_class::search($domain)->ids(); }; -$generateMany2Many = function($qty, $field_conf, $relation_conf, $lang, $domain_data) { +$createGenerateParams = function($field_conf, $relation_conf, $object_data, $lang) { $model_generate_params = [ 'entity' => $field_conf['foreign_object'], 'lang' => $lang ]; - foreach(['fields', 'relations', 'add_to_domain_data'] as $param_key) { + foreach(['fields', 'relations', 'set_object_data'] as $param_key) { if(isset($relation_conf[$param_key])) { $model_generate_params[$param_key] = $relation_conf[$param_key]; } } - if(!empty($domain_data)) { - $model_generate_params['domain_data'] = $domain_data; + if(!empty($object_data)) { + $model_generate_params['object_data'] = $object_data; } + return $model_generate_params; +}; + +$generateMany2One = function($field_conf, $relation_conf, $lang, $object_data) use ($createGenerateParams) { + $model_generate_params = $createGenerateParams($field_conf, $relation_conf, $object_data, $lang); + + return eQual::run('do', 'core_model_generate', $model_generate_params); +}; + +$generateMany2Many = function($qty, $field_conf, $relation_conf, $lang, $object_data) use ($createGenerateParams) { + $model_generate_params = $createGenerateParams($field_conf, $relation_conf, $object_data, $lang); + $results = []; for($i = 0; $i < $qty; $i++) { $results[] = eQual::run('do', 'core_model_generate', $model_generate_params); @@ -105,26 +111,14 @@ return $results; }; -$generateOne2Many = function($id, $qty, $field_conf, $relation_conf, $lang, $domain_data) { - $model_generate_params = [ - 'entity' => $field_conf['foreign_object'], - 'lang' => $lang - ]; - foreach(['fields', 'relations', 'add_to_domain_data'] as $param_key) { - if(isset($relation_conf[$param_key])) { - $model_generate_params[$param_key] = $relation_conf[$param_key]; - } - } +$generateOne2Many = function($id, $qty, $field_conf, $relation_conf, $lang, $object_data) use ($createGenerateParams) { + $model_generate_params = $createGenerateParams($field_conf, $relation_conf, $object_data, $lang); if(!isset($model_generate_params['fields'])) { $model_generate_params['fields'] = []; } $model_generate_params['fields'][$field_conf['foreign_field']] = $id; - if(!empty($domain_data)) { - $model_generate_params['domain_data'] = $domain_data; - } - $results = []; for($i = 0; $i < $qty; $i++) { $results[] = eQual::run('do', 'core_model_generate', $model_generate_params); @@ -137,13 +131,12 @@ * Action */ -$new_entity = []; - $model = $orm->getModel($params['entity']); if(!$model) { throw new Exception("unknown_entity", QN_ERROR_INVALID_PARAM); } +$new_entity = []; $root_fields = ['id', 'creator', 'created', 'modifier', 'modified', 'deleted', 'state']; $model_unique_conf = []; @@ -221,21 +214,20 @@ continue; } + $mode = $relation_conf['mode'] ?? 'use-existing-or-create'; + if($mode !== 'create') { + $ids = $getRelationItemsIds( + $field_conf['foreign_object'], + $relation_conf['domain'] ?? [], + array_merge($new_entity, $params['object_data']) + ); + if($mode === 'use-existing-or-create') { + $mode = empty($ids) || DataGenerator::boolean() ? 'create' : 'use-existing'; + } + } + switch($field_type) { case 'many2one': - $domain = []; - if($relation_conf['domain']) { - $domain = (new Domain($relation_conf['domain'])) - ->parse(array_merge($new_entity, $params['domain_data'] ?? [])) - ->toArray(); - } - - $ids = $field_conf['foreign_object']::search($domain)->ids(); - $mode = $relation_conf['mode'] ?? 'use-existing-or-create'; - if($mode === 'use-existing-or-create') { - $mode = empty($ids) || DataGenerator::boolean() ? 'create' : 'use-existing'; - } - switch($mode) { case 'use-existing': if(!empty($ids)) { @@ -247,34 +239,24 @@ } break; case 'create': - $result = $generateMany2One($field_conf, $relation_conf, $params['lang'], $params['domain_data'] ?? []); + $result = $generateMany2One($field_conf, $relation_conf, $params['lang'], $params['object_data']); $new_entity[$field] = $result['id']; - foreach(array_keys($relation_conf['add_to_domain_data'] ?? []) as $key) { - if(isset($result['domain_data'][$key])) { - $params['domain_data']["$field.$key"] = $result['domain_data'][$key]; + foreach(array_keys($relation_conf['set_object_data'] ?? []) as $key) { + if(isset($result['object_data'][$key])) { + $params['object_data']["$field.$key"] = $result['object_data'][$key]; } } break; } break; case 'many2many': - $mode = $relation_conf['mode'] ?? 'use-existing-or-create'; - - $qty_conf = $relation_conf['qty'] ?? [0, 5]; + $qty_conf = $relation_conf['qty']; $qty = is_array($qty_conf) ? mt_rand($qty_conf[0], $qty_conf[1]) : $qty_conf; switch($mode) { case 'use-existing': - $domain = []; - if($relation_conf['domain']) { - $domain = (new Domain($relation_conf['domain'])) - ->parse(array_merge($new_entity, $params['domain_data'] ?? [])) - ->toArray(); - } - - $ids = $field_conf['foreign_object']::search($domain)->ids(); $random_ids = []; for($i = 0; $i < $qty; $i++) { if(empty($ids)) { @@ -291,7 +273,7 @@ } break; case 'create': - $results = $generateMany2Many($qty, $field_conf, $relation_conf, $params['lang'], $params['domain_data'] ?? []); + $results = $generateMany2Many($qty, $field_conf, $relation_conf, $params['lang'], $params['object_data']); $new_relation_entities_ids = array_column($results, 'id'); if(!empty($new_relation_entities_ids)) { @@ -300,9 +282,9 @@ $i = 0; foreach($results as $result) { - foreach(array_keys($relation_conf['add_to_domain_data'] ?? []) as $key) { - if(isset($result['domain_data'][$key])) { - $params['domain_data']["$field.$i.$key"] = $result['domain_data'][$key]; + foreach(array_keys($relation_conf['set_object_data'] ?? []) as $key) { + if(isset($result['object_data'][$key])) { + $params['object_data']["$field.$i.$key"] = $result['object_data'][$key]; } } $i++; @@ -313,16 +295,16 @@ } } -$field_to_read = array_values($params['add_to_domain_data']); +$field_to_read = array_values($params['set_object_data']); $instance = $params['entity']::create($new_entity, $params['lang']) ->read($field_to_read) ->adapt('json') ->first(true); -foreach($params['add_to_domain_data'] as $key => $field) { +foreach($params['set_object_data'] as $key => $field) { if(isset($instance[$field])) { - $params['domain_data'][$key] = $instance[$field]; + $params['object_data'][$key] = $instance[$field]; } } @@ -333,10 +315,10 @@ continue; } - $qty_conf = $relation_conf['qty'] ?? [0, 3]; + $qty_conf = $relation_conf['qty']; $qty = is_array($qty_conf) ? mt_rand($qty_conf[0], $qty_conf[1]) : $qty_conf; - $results = $generateOne2Many($instance['id'], $qty, $field_conf, $relation_conf, $params['lang'], $params['domain_data']); + $results = $generateOne2Many($instance['id'], $qty, $field_conf, $relation_conf, $params['lang'], $params['object_data']); $new_relation_entities_ids = array_column($results, 'id'); if(!empty($new_relation_entities_ids)) { @@ -345,9 +327,9 @@ $i = 0; foreach($results as $result) { - foreach(array_keys($relation_conf['add_to_domain_data'] ?? []) as $key) { - if(isset($result['domain_data'][$key])) { - $params['domain_data']["$field.$i.$key"] = $result['domain_data'][$key]; + foreach(array_keys($relation_conf['set_object_data'] ?? []) as $key) { + if(isset($result['object_data'][$key])) { + $params['object_data']["$field.$i.$key"] = $result['object_data'][$key]; } } $i++; @@ -357,7 +339,7 @@ $result = [ 'entity' => $params['entity'], 'id' => $instance['id'], - 'domain_data' => $params['domain_data'] + 'object_data' => $params['object_data'] ]; $context->httpResponse() From 9c85717348e584697e38105a935d30d3ae9c0b99 Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Wed, 11 Sep 2024 11:52:46 +0200 Subject: [PATCH 02/10] Add qty param to model_generate action --- packages/core/actions/init/seed.php | 9 +- packages/core/actions/model/generate.php | 376 ++++++++++++----------- 2 files changed, 200 insertions(+), 185 deletions(-) diff --git a/packages/core/actions/init/seed.php b/packages/core/actions/init/seed.php index fc503b22e..6d85e1d7d 100644 --- a/packages/core/actions/init/seed.php +++ b/packages/core/actions/init/seed.php @@ -53,23 +53,20 @@ continue; } foreach($classes as $class) { - if(!isset($class['name'], $class['qty'])) { + if(!isset($class['name'])) { continue; } $generate_params = [ 'entity' => $class['name'], ]; - foreach(['lang', 'fields', 'relations', 'set_object_data'] as $param_key) { + foreach(['qty', 'random_qty', 'fields', 'relations', 'set_object_data', 'lang'] as $param_key) { if(isset($class[$param_key])) { $generate_params[$param_key] = $class[$param_key]; } } - $qty = is_array($class['qty']) ? mt_rand($class['qty'][0], $class['qty'][1]) : $class['qty']; - for($i = 0; $i < $qty; $i++) { - eQual::run('do', 'core_model_generate', $generate_params); - } + eQual::run('do', 'core_model_generate', $generate_params); } } } diff --git a/packages/core/actions/model/generate.php b/packages/core/actions/model/generate.php index 523b75e91..de78df8f3 100644 --- a/packages/core/actions/model/generate.php +++ b/packages/core/actions/model/generate.php @@ -16,6 +16,18 @@ 'type' => 'string', 'required' => true ], + 'qty' => [ + 'description' => 'The quantity of objects to generate.', + 'type' => 'integer', + 'min' => 0, + 'default' => 1 + ], + 'random_qty' => [ + 'description' => 'The random min and max quantity of objects to generate.', + 'help' => 'First value of array is min, the second is max.', + 'type' => 'array', + 'default' => [] + ], 'fields' => [ 'description' => 'Associative array mapping fields to their related values.', 'type' => 'array', @@ -65,7 +77,12 @@ * Methods */ -$getRelationItemsIds = function($item_class, $relation_domain, $object_data) { +$getRelationItemsIds = function(string $entity, array $relation_domain, array $object_data) use ($orm) { + $model = $orm->getModel($entity); + if(!$model) { + throw new Exception("unknown_entity", QN_ERROR_INVALID_PARAM); + } + $domain = []; if(!empty($relation_domain)) { $domain = (new Domain($relation_domain)) @@ -73,15 +90,15 @@ ->toArray(); } - return $item_class::search($domain)->ids(); + return $entity::search($domain)->ids(); }; -$createGenerateParams = function($field_conf, $relation_conf, $object_data, $lang) { +$createGenerateParams = function(array $field_conf, array $relation_conf, array $object_data, string $lang) { $model_generate_params = [ 'entity' => $field_conf['foreign_object'], 'lang' => $lang ]; - foreach(['fields', 'relations', 'set_object_data'] as $param_key) { + foreach(['qty', 'random_qty', 'fields', 'relations', 'set_object_data'] as $param_key) { if(isset($relation_conf[$param_key])) { $model_generate_params[$param_key] = $relation_conf[$param_key]; } @@ -94,24 +111,19 @@ return $model_generate_params; }; -$generateMany2One = function($field_conf, $relation_conf, $lang, $object_data) use ($createGenerateParams) { +$generateMany2One = function(array $field_conf, array $relation_conf, string $lang, array $object_data) use ($createGenerateParams) { $model_generate_params = $createGenerateParams($field_conf, $relation_conf, $object_data, $lang); - return eQual::run('do', 'core_model_generate', $model_generate_params); + return eQual::run('do', 'core_model_generate', $model_generate_params)[0]; }; -$generateMany2Many = function($qty, $field_conf, $relation_conf, $lang, $object_data) use ($createGenerateParams) { +$generateMany2Many = function(array $field_conf, array $relation_conf, string $lang, array $object_data) use ($createGenerateParams) { $model_generate_params = $createGenerateParams($field_conf, $relation_conf, $object_data, $lang); - $results = []; - for($i = 0; $i < $qty; $i++) { - $results[] = eQual::run('do', 'core_model_generate', $model_generate_params); - } - - return $results; + return eQual::run('do', 'core_model_generate', $model_generate_params); }; -$generateOne2Many = function($id, $qty, $field_conf, $relation_conf, $lang, $object_data) use ($createGenerateParams) { +$generateOne2Many = function(int $id, array $field_conf, array $relation_conf, string $lang, array $object_data) use ($createGenerateParams) { $model_generate_params = $createGenerateParams($field_conf, $relation_conf, $object_data, $lang); if(!isset($model_generate_params['fields'])) { @@ -119,12 +131,27 @@ } $model_generate_params['fields'][$field_conf['foreign_field']] = $id; - $results = []; - for($i = 0; $i < $qty; $i++) { - $results[] = eQual::run('do', 'core_model_generate', $model_generate_params); + return eQual::run('do', 'core_model_generate', $model_generate_params); +}; + +$addRelationResultToObjectData = function(array &$object_data, string $field, array $set_object_data, array $relation_result) { + foreach(array_keys($set_object_data) as $key) { + if(isset($relation_result['object_data'][$key])) { + $object_data["$field.$key"] = $relation_result['object_data'][$key]; + } } +}; - return $results; +$addRelationResultsToObjectData = function(array &$object_data, string $field, array $set_object_data, array $relation_results) { + $i = 0; + foreach($relation_results as $relation_result) { + foreach(array_keys($set_object_data) as $key) { + if(isset($relation_result['object_data'][$key])) { + $object_data["$field.$i.$key"] = $relation_result['object_data'][$key]; + } + } + $i++; + } }; /** @@ -139,210 +166,201 @@ $new_entity = []; $root_fields = ['id', 'creator', 'created', 'modifier', 'modified', 'deleted', 'state']; -$model_unique_conf = []; -if(method_exists($params['entity'], 'getUnique')) { - $model_unique_conf = (new $params['entity'])->getUnique(); +$qty = $params['qty']; +if(!empty($params['random_qty']) && count($params['random_qty']) === 2) { + $min = $params['random_qty'][0]; + $max = $params['random_qty'][1]; + + $qty = rand($min, $max); } -$schema = $model->getSchema(); -foreach($schema as $field => $field_conf) { - $field_value_forced = isset($params['fields'][$field]); - $field_has_generate_function = isset($field_conf['generate']) && method_exists($params['entity'], $field_conf['generate']); - - if( - !$field_value_forced - && !$field_has_generate_function - && ( - in_array($field, $root_fields) - || in_array($field_conf['type'], ['alias', 'computed', 'one2many', 'many2one', 'many2many']) - ) - ) { - continue; +$results = []; +for($i = 0; $i < $qty; $i++) { + $model_unique_conf = []; + if(method_exists($params['entity'], 'getUnique')) { + $model_unique_conf = (new $params['entity'])->getUnique(); } - $should_be_unique = $field_conf['unique'] ?? false; - if(!$should_be_unique && !empty($model_unique_conf)) { - foreach($model_unique_conf as $unique_conf) { - if(count($unique_conf) === 1 && $unique_conf[0] === $field) { - $should_be_unique = true; - break; + $schema = $model->getSchema(); + foreach($schema as $field => $field_conf) { + $field_value_forced = isset($params['fields'][$field]); + $field_has_generate_function = isset($field_conf['generate']) && method_exists($params['entity'], $field_conf['generate']); + + if( + !$field_value_forced + && !$field_has_generate_function + && ( + in_array($field, $root_fields) + || in_array($field_conf['type'], ['alias', 'computed', 'one2many', 'many2one', 'many2many']) + ) + ) { + continue; + } + + $should_be_unique = $field_conf['unique'] ?? false; + if(!$should_be_unique && !empty($model_unique_conf)) { + foreach($model_unique_conf as $unique_conf) { + if(count($unique_conf) === 1 && $unique_conf[0] === $field) { + $should_be_unique = true; + break; + } } } - } - $field_value_allowed = false; - $unique_retry_count = 10; - while($unique_retry_count > 0 && !$field_value_allowed) { - $unique_retry_count--; + $field_value_allowed = false; + $unique_retry_count = 10; + while($unique_retry_count > 0 && !$field_value_allowed) { + $unique_retry_count--; - if($field_value_forced) { - $new_entity[$field] = $params['fields'][$field]; - } - elseif($field_has_generate_function) { - $new_entity[$field] = $params['entity']::{$field_conf['generate']}(); - } - else { - $required = $field_conf['required'] ?? false; - if(!$required && DataGenerator::boolean(0.05)) { - $new_entity[$field] = null; + if($field_value_forced) { + $new_entity[$field] = $params['fields'][$field]; + } + elseif($field_has_generate_function) { + $new_entity[$field] = $params['entity']::{$field_conf['generate']}(); } else { - $new_entity[$field] = DataGenerator::generateByFieldConf($field, $field_conf, $params['lang']); + $required = $field_conf['required'] ?? false; + if(!$required && DataGenerator::boolean(0.05)) { + $new_entity[$field] = null; + } + else { + $new_entity[$field] = DataGenerator::generateByFieldConf($field, $field_conf, $params['lang']); + } } - } - if($should_be_unique) { - $ids = $params['entity']::search([$field, '=', $new_entity[$field]])->ids(); - if(empty($ids)) { + if($should_be_unique) { + $ids = $params['entity']::search([$field, '=', $new_entity[$field]])->ids(); + if(empty($ids)) { + $field_value_allowed = true; + } + } + else { $field_value_allowed = true; } } - else { - $field_value_allowed = true; - } - } - if(!$field_value_allowed) { - unset($new_entity[$field]); + if(!$field_value_allowed) { + unset($new_entity[$field]); + } } -} -foreach($params['relations'] as $field => $relation_conf) { - $field_conf = $schema[$field] ?? null; - $field_type = $field_conf['result_type'] ?? $field_conf['type'] ?? null; - if(is_null($field_conf) || !in_array($field_type, ['many2one', 'many2many'])) { - continue; - } + foreach($params['relations'] as $field => $relation_conf) { + $field_conf = $schema[$field] ?? null; + $field_type = $field_conf['result_type'] ?? $field_conf['type'] ?? null; + if(is_null($field_conf) || !in_array($field_type, ['many2one', 'many2many'])) { + continue; + } - $mode = $relation_conf['mode'] ?? 'use-existing-or-create'; - if($mode !== 'create') { - $ids = $getRelationItemsIds( - $field_conf['foreign_object'], - $relation_conf['domain'] ?? [], - array_merge($new_entity, $params['object_data']) - ); - if($mode === 'use-existing-or-create') { - $mode = empty($ids) || DataGenerator::boolean() ? 'create' : 'use-existing'; + $mode = $relation_conf['mode'] ?? 'use-existing-or-create'; + if($mode !== 'create') { + $ids = $getRelationItemsIds( + $field_conf['foreign_object'], + $relation_conf['domain'] ?? [], + array_merge($new_entity, $params['object_data']) + ); + if($mode === 'use-existing-or-create') { + $mode = empty($ids) || DataGenerator::boolean() ? 'create' : 'use-existing'; + } } - } - switch($field_type) { - case 'many2one': - switch($mode) { - case 'use-existing': - if(!empty($ids)) { - if(!($field_conf['required'] ?? false)) { - $ids[] = null; + switch($field_type) { + case 'many2one': + switch($mode) { + case 'use-existing': + if(!empty($ids)) { + if(!($field_conf['required'] ?? false)) { + $ids[] = null; + } + + $new_entity[$field] = $ids[array_rand($ids)]; } + break; + case 'create': + $relation_result = $generateMany2One($field_conf, $relation_conf, $params['lang'], $params['object_data']); - $new_entity[$field] = $ids[array_rand($ids)]; - } - break; - case 'create': - $result = $generateMany2One($field_conf, $relation_conf, $params['lang'], $params['object_data']); + $new_entity[$field] = $relation_result['id']; - $new_entity[$field] = $result['id']; + if(!empty($relation_conf['set_object_data'])) { + $addRelationResultToObjectData($params['object_data'], $field, $relation_conf['set_object_data'], $relation_result); + } + break; + } + break; + case 'many2many': + switch($mode) { + case 'use-existing': + $random_ids = []; + for($j = 0; $j < $qty; $j++) { + if(empty($ids)) { + break; + } - foreach(array_keys($relation_conf['set_object_data'] ?? []) as $key) { - if(isset($result['object_data'][$key])) { - $params['object_data']["$field.$key"] = $result['object_data'][$key]; + $random_index = array_rand($ids); + $random_ids[] = $ids[$random_index]; + array_splice($ids, $random_index, 1); } - } - break; - } - break; - case 'many2many': - $qty_conf = $relation_conf['qty']; - $qty = is_array($qty_conf) ? mt_rand($qty_conf[0], $qty_conf[1]) : $qty_conf; - - switch($mode) { - case 'use-existing': - $random_ids = []; - for($i = 0; $i < $qty; $i++) { - if(empty($ids)) { - break; + + if(!empty($random_ids)) { + $new_entity[$field] = $random_ids; } + break; + case 'create': + $relation_results = $generateMany2Many($field_conf, $relation_conf, $params['lang'], $params['object_data']); - $random_index = array_rand($ids); - $random_ids[] = $ids[$random_index]; - array_splice($ids, $random_index, 1); - } + $new_relation_entities_ids = array_column($relation_results, 'id'); + if(!empty($new_relation_entities_ids)) { + $new_entity[$field] = $new_relation_entities_ids; + } - if(!empty($random_ids)) { - $new_entity[$field] = $random_ids; - } - break; - case 'create': - $results = $generateMany2Many($qty, $field_conf, $relation_conf, $params['lang'], $params['object_data']); - - $new_relation_entities_ids = array_column($results, 'id'); - if(!empty($new_relation_entities_ids)) { - $new_entity[$field] = $new_relation_entities_ids; - } - - $i = 0; - foreach($results as $result) { - foreach(array_keys($relation_conf['set_object_data'] ?? []) as $key) { - if(isset($result['object_data'][$key])) { - $params['object_data']["$field.$i.$key"] = $result['object_data'][$key]; - } + if(!empty($relation_conf['set_object_data'])) { + $addRelationResultsToObjectData($params['object_data'], $field, $relation_conf['set_object_data'], $relation_results); } - $i++; - } - break; - } - break; + break; + } + break; + } } -} -$field_to_read = array_values($params['set_object_data']); + $field_to_read = array_values($params['set_object_data']); -$instance = $params['entity']::create($new_entity, $params['lang']) - ->read($field_to_read) - ->adapt('json') - ->first(true); + $instance = $params['entity']::create($new_entity, $params['lang']) + ->read($field_to_read) + ->adapt('json') + ->first(true); -foreach($params['set_object_data'] as $key => $field) { - if(isset($instance[$field])) { - $params['object_data'][$key] = $instance[$field]; - } -} - -foreach($params['relations'] as $field => $relation_conf) { - $field_conf = $schema[$field] ?? null; - $field_type = $field_conf['result_type'] ?? $field_conf['type'] ?? null; - if(is_null($field_conf) || $field_type !== 'one2many') { - continue; + foreach($params['set_object_data'] as $key => $field) { + if(isset($instance[$field])) { + $params['object_data'][$key] = $instance[$field]; + } } - $qty_conf = $relation_conf['qty']; - $qty = is_array($qty_conf) ? mt_rand($qty_conf[0], $qty_conf[1]) : $qty_conf; + foreach($params['relations'] as $field => $relation_conf) { + $field_conf = $schema[$field] ?? null; + $field_type = $field_conf['result_type'] ?? $field_conf['type'] ?? null; + if(is_null($field_conf) || $field_type !== 'one2many') { + continue; + } - $results = $generateOne2Many($instance['id'], $qty, $field_conf, $relation_conf, $params['lang'], $params['object_data']); + $relation_results = $generateOne2Many($instance['id'], $field_conf, $relation_conf, $params['lang'], $params['object_data']); - $new_relation_entities_ids = array_column($results, 'id'); - if(!empty($new_relation_entities_ids)) { - $new_entity[$field] = $new_relation_entities_ids; - } + $new_relation_entities_ids = array_column($relation_results, 'id'); + if(!empty($new_relation_entities_ids)) { + $new_entity[$field] = $new_relation_entities_ids; + } - $i = 0; - foreach($results as $result) { - foreach(array_keys($relation_conf['set_object_data'] ?? []) as $key) { - if(isset($result['object_data'][$key])) { - $params['object_data']["$field.$i.$key"] = $result['object_data'][$key]; - } + if(!empty($relation_conf['set_object_data'])) { + $addRelationResultsToObjectData($params['object_data'], $field, $relation_conf['set_object_data'], $relation_results); } - $i++; } -} -$result = [ - 'entity' => $params['entity'], - 'id' => $instance['id'], - 'object_data' => $params['object_data'] -]; + $results[] = [ + 'entity' => $params['entity'], + 'id' => $instance['id'], + 'object_data' => $params['object_data'] + ]; +} $context->httpResponse() ->status(201) - ->body($result) + ->body($results) ->send(); From da8a269113f2db07d3deddaaf10f0574945f79fa Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Wed, 11 Sep 2024 12:46:40 +0200 Subject: [PATCH 03/10] Fix qty for many2many relation when use-existing --- packages/core/actions/model/generate.php | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/core/actions/model/generate.php b/packages/core/actions/model/generate.php index de78df8f3..9094947dd 100644 --- a/packages/core/actions/model/generate.php +++ b/packages/core/actions/model/generate.php @@ -77,6 +77,17 @@ * Methods */ +$getQty = function($qty, $random_qty) { + if(count($random_qty) === 2) { + $min = $random_qty[0]; + $max = $random_qty[1]; + + $qty = rand($min, $max); + } + + return $qty; +}; + $getRelationItemsIds = function(string $entity, array $relation_domain, array $object_data) use ($orm) { $model = $orm->getModel($entity); if(!$model) { @@ -166,13 +177,7 @@ $new_entity = []; $root_fields = ['id', 'creator', 'created', 'modifier', 'modified', 'deleted', 'state']; -$qty = $params['qty']; -if(!empty($params['random_qty']) && count($params['random_qty']) === 2) { - $min = $params['random_qty'][0]; - $max = $params['random_qty'][1]; - - $qty = rand($min, $max); -} +$qty = $getQty($params['qty'], $params['random_qty']); $results = []; for($i = 0; $i < $qty; $i++) { @@ -289,8 +294,10 @@ case 'many2many': switch($mode) { case 'use-existing': + $relation_qty = $getQty($relation_conf['qty'] ?? 1, $relation_conf['random_qty'] ?? []); + $random_ids = []; - for($j = 0; $j < $qty; $j++) { + for($j = 0; $j < $relation_qty; $j++) { if(empty($ids)) { break; } From a84a24ae97cc6db518d5ef1f8ae240f4e2c0b2ca Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Wed, 11 Sep 2024 14:55:07 +0200 Subject: [PATCH 04/10] Skip object creation if trying to set a value to a field that as to be unique and is required --- packages/core/actions/model/generate.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/core/actions/model/generate.php b/packages/core/actions/model/generate.php index 9094947dd..050aef8b7 100644 --- a/packages/core/actions/model/generate.php +++ b/packages/core/actions/model/generate.php @@ -174,13 +174,13 @@ throw new Exception("unknown_entity", QN_ERROR_INVALID_PARAM); } -$new_entity = []; $root_fields = ['id', 'creator', 'created', 'modifier', 'modified', 'deleted', 'state']; $qty = $getQty($params['qty'], $params['random_qty']); $results = []; for($i = 0; $i < $qty; $i++) { + $new_entity = []; $model_unique_conf = []; if(method_exists($params['entity'], 'getUnique')) { $model_unique_conf = (new $params['entity'])->getUnique(); @@ -202,6 +202,8 @@ continue; } + $is_required = $field_conf['required'] ?? false; + $should_be_unique = $field_conf['unique'] ?? false; if(!$should_be_unique && !empty($model_unique_conf)) { foreach($model_unique_conf as $unique_conf) { @@ -224,8 +226,7 @@ $new_entity[$field] = $params['entity']::{$field_conf['generate']}(); } else { - $required = $field_conf['required'] ?? false; - if(!$required && DataGenerator::boolean(0.05)) { + if(!$is_required && DataGenerator::boolean(0.05)) { $new_entity[$field] = null; } else { @@ -238,6 +239,10 @@ if(empty($ids)) { $field_value_allowed = true; } + elseif($field_value_forced && $is_required) { + // Skip object creation because not possible + continue 3; + } } else { $field_value_allowed = true; From 0a34ed46ee4dd7b16bf3c5888e0b8e702d359005 Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Wed, 11 Sep 2024 16:04:37 +0200 Subject: [PATCH 05/10] Add skip if not able to generate a valid random value --- packages/core/actions/model/generate.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/core/actions/model/generate.php b/packages/core/actions/model/generate.php index 050aef8b7..e2e3c5771 100644 --- a/packages/core/actions/model/generate.php +++ b/packages/core/actions/model/generate.php @@ -240,7 +240,7 @@ $field_value_allowed = true; } elseif($field_value_forced && $is_required) { - // Skip object creation because not possible + trigger_error("PHP::skip creation of {$params['entity']} because of value $new_entity[$field] for $field field.", QN_REPORT_WARNING); continue 3; } } @@ -250,7 +250,13 @@ } if(!$field_value_allowed) { - unset($new_entity[$field]); + if($is_required) { + trigger_error("PHP::skip creation of {$params['entity']} because not able to generate a unique value for $field field.", QN_REPORT_WARNING); + continue 2; + } + else { + unset($new_entity[$field]); + } } } From 67db59bcd1532bb38f171eeb01f1a3828c6d21e0 Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Thu, 12 Sep 2024 08:55:56 +0200 Subject: [PATCH 06/10] Refacto --- packages/core/actions/model/generate.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/core/actions/model/generate.php b/packages/core/actions/model/generate.php index e2e3c5771..6cd330bff 100644 --- a/packages/core/actions/model/generate.php +++ b/packages/core/actions/model/generate.php @@ -178,15 +178,16 @@ $qty = $getQty($params['qty'], $params['random_qty']); +$model_unique_conf = []; +if(method_exists($params['entity'], 'getUnique')) { + $model_unique_conf = $model->getUnique(); +} + +$schema = $model->getSchema(); + $results = []; for($i = 0; $i < $qty; $i++) { $new_entity = []; - $model_unique_conf = []; - if(method_exists($params['entity'], 'getUnique')) { - $model_unique_conf = (new $params['entity'])->getUnique(); - } - - $schema = $model->getSchema(); foreach($schema as $field => $field_conf) { $field_value_forced = isset($params['fields'][$field]); $field_has_generate_function = isset($field_conf['generate']) && method_exists($params['entity'], $field_conf['generate']); From 72de7da679c2173128422cf4eb53595dc16d0002 Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Thu, 12 Sep 2024 09:09:13 +0200 Subject: [PATCH 07/10] Add remaining todos + Add warnings --- packages/core/actions/model/generate.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/core/actions/model/generate.php b/packages/core/actions/model/generate.php index 6cd330bff..8b992fe33 100644 --- a/packages/core/actions/model/generate.php +++ b/packages/core/actions/model/generate.php @@ -178,6 +178,8 @@ $qty = $getQty($params['qty'], $params['random_qty']); +// TODO: Handle multi columns unique (only single column unique is handled) +// TODO: Handle unique for many2one relations $model_unique_conf = []; if(method_exists($params['entity'], 'getUnique')) { $model_unique_conf = $model->getUnique(); @@ -268,6 +270,9 @@ continue; } + $is_required = $field_conf['required'] ?? false; + + $ids = []; $mode = $relation_conf['mode'] ?? 'use-existing-or-create'; if($mode !== 'create') { $ids = $getRelationItemsIds( @@ -278,19 +283,21 @@ if($mode === 'use-existing-or-create') { $mode = empty($ids) || DataGenerator::boolean() ? 'create' : 'use-existing'; } + elseif($mode === 'use-existing' && empty($ids)) { + $mode = 'create'; + trigger_error("PHP::cannot use 'use-existing' for {$field_conf['foreign_object']} relation of {$params['entity']} because nothing to link, fallback on 'create'.", QN_REPORT_WARNING); + } } switch($field_type) { case 'many2one': switch($mode) { case 'use-existing': - if(!empty($ids)) { - if(!($field_conf['required'] ?? false)) { - $ids[] = null; - } - - $new_entity[$field] = $ids[array_rand($ids)]; + if(!$is_required) { + $ids[] = null; } + + $new_entity[$field] = $ids[array_rand($ids)]; break; case 'create': $relation_result = $generateMany2One($field_conf, $relation_conf, $params['lang'], $params['object_data']); From 6a7009fb6d6d4409a8b421e00cb85bec34b0eec7 Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Thu, 12 Sep 2024 09:36:01 +0200 Subject: [PATCH 08/10] Refacto + Add warning --- packages/core/actions/model/generate.php | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/core/actions/model/generate.php b/packages/core/actions/model/generate.php index 8b992fe33..d6dee1d6b 100644 --- a/packages/core/actions/model/generate.php +++ b/packages/core/actions/model/generate.php @@ -125,7 +125,7 @@ $generateMany2One = function(array $field_conf, array $relation_conf, string $lang, array $object_data) use ($createGenerateParams) { $model_generate_params = $createGenerateParams($field_conf, $relation_conf, $object_data, $lang); - return eQual::run('do', 'core_model_generate', $model_generate_params)[0]; + return eQual::run('do', 'core_model_generate', $model_generate_params); }; $generateMany2Many = function(array $field_conf, array $relation_conf, string $lang, array $object_data) use ($createGenerateParams) { @@ -300,12 +300,17 @@ $new_entity[$field] = $ids[array_rand($ids)]; break; case 'create': - $relation_result = $generateMany2One($field_conf, $relation_conf, $params['lang'], $params['object_data']); - - $new_entity[$field] = $relation_result['id']; + $relation_results = $generateMany2One($field_conf, $relation_conf, $params['lang'], $params['object_data']); + if(count($relation_results) === 1) { + $new_entity[$field] = $relation_results[0]['id']; - if(!empty($relation_conf['set_object_data'])) { - $addRelationResultToObjectData($params['object_data'], $field, $relation_conf['set_object_data'], $relation_result); + if(!empty($relation_conf['set_object_data'])) { + $addRelationResultToObjectData($params['object_data'], $field, $relation_conf['set_object_data'], $relation_results[0]); + } + } + elseif($is_required) { + trigger_error("PHP::skip creation of {$params['entity']} because not able to generate object for $field field.", QN_REPORT_WARNING); + continue 4; } break; } @@ -336,10 +341,10 @@ $new_relation_entities_ids = array_column($relation_results, 'id'); if(!empty($new_relation_entities_ids)) { $new_entity[$field] = $new_relation_entities_ids; - } - if(!empty($relation_conf['set_object_data'])) { - $addRelationResultsToObjectData($params['object_data'], $field, $relation_conf['set_object_data'], $relation_results); + if(!empty($relation_conf['set_object_data'])) { + $addRelationResultsToObjectData($params['object_data'], $field, $relation_conf['set_object_data'], $relation_results); + } } break; } @@ -369,12 +374,7 @@ $relation_results = $generateOne2Many($instance['id'], $field_conf, $relation_conf, $params['lang'], $params['object_data']); - $new_relation_entities_ids = array_column($relation_results, 'id'); - if(!empty($new_relation_entities_ids)) { - $new_entity[$field] = $new_relation_entities_ids; - } - - if(!empty($relation_conf['set_object_data'])) { + if(!empty($relation_conf['set_object_data']) && !empty($relation_results)) { $addRelationResultsToObjectData($params['object_data'], $field, $relation_conf['set_object_data'], $relation_results); } } From 88dca76bd046c183e68d7eb97b59d11c65e361b6 Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Thu, 12 Sep 2024 10:12:27 +0200 Subject: [PATCH 09/10] handle min and max for generate integer and real numbers --- lib/equal/data/DataGenerator.class.php | 18 ++++++++++++++---- lib/equal/orm/usages/UsageAmount.class.php | 6 +++++- lib/equal/orm/usages/UsageNumber.class.php | 12 ++++++++++-- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/lib/equal/data/DataGenerator.class.php b/lib/equal/data/DataGenerator.class.php index cf3aeeae8..5d4d8f810 100644 --- a/lib/equal/data/DataGenerator.class.php +++ b/lib/equal/data/DataGenerator.class.php @@ -67,9 +67,9 @@ public static function generateByFieldConf(string $field, array $field_conf, str case 'boolean': return self::boolean(); case 'integer': - return self::integer(9); + return self::integerByLength(9); case 'float': - return self::realNumber(9, 2); + return self::realNumberByLength(9, 2); } return null; @@ -115,14 +115,18 @@ public static function boolean($probability = 0.5): bool { return mt_rand() / mt_getrandmax() < $probability; } - public static function integer(int $length): int { + public static function integerByLength(int $length): int { $min = (pow(10, $length) - 1) * -1; $max = pow(10, $length) - 1; return mt_rand($min, $max); } - public static function realNumber(int $precision, int $scale): float { + public static function integer(int $min, int $max): int { + return mt_rand($min, $max); + } + + public static function realNumberByLength(int $precision, int $scale): float { $max_int_part = pow(10, $precision) - 1; $min_int_part = -$max_int_part; @@ -135,6 +139,12 @@ public static function realNumber(int $precision, int $scale): float { return round($random_float, $scale); } + public static function realNumber(float $min, float $max, int $decimals): float { + $scale = pow(10, $decimals); + + return mt_rand($min * $scale, $max * $scale) / $scale; + } + public static function hexadecimal(int $length): string { $num_bytes = ceil($length / 2); $random_bytes = random_bytes($num_bytes); diff --git a/lib/equal/orm/usages/UsageAmount.class.php b/lib/equal/orm/usages/UsageAmount.class.php index 340ee0002..706beb4d8 100644 --- a/lib/equal/orm/usages/UsageAmount.class.php +++ b/lib/equal/orm/usages/UsageAmount.class.php @@ -65,7 +65,11 @@ public function generateRandomValue(): float { case 'money': case 'percent': case 'rate': - return DataGenerator::realNumber($this->getPrecision(), $this->getScale()); + if($this->getMin() === 0 && $this->getMax() === 0) { + return DataGenerator::realNumberByLength($this->getLength(), $this->getScale()); + } + + return DataGenerator::realNumber($this->getMin(), $this->getMax(), $this->getScale()); } return 0; } diff --git a/lib/equal/orm/usages/UsageNumber.class.php b/lib/equal/orm/usages/UsageNumber.class.php index 99e6fcfde..164302290 100644 --- a/lib/equal/orm/usages/UsageNumber.class.php +++ b/lib/equal/orm/usages/UsageNumber.class.php @@ -99,9 +99,17 @@ public function generateRandomValue() { case 'boolean': return DataGenerator::boolean(); case 'integer': - return DataGenerator::integer($this->getLength()); + if($this->getMin() === 0 && $this->getMax() === 0) { + return DataGenerator::integerByLength($this->getLength()); + } + + return DataGenerator::integer($this->getMin(), $this->getMax()); case 'real': - return DataGenerator::realNumber($this->getPrecision(), $this->getScale()); + if($this->getMin() === 0 && $this->getMax() === 0) { + return DataGenerator::realNumberByLength($this->getLength(), $this->getScale()); + } + + return DataGenerator::realNumber($this->getMin(), $this->getMax(), $this->getScale()); case 'hexadecimal': return DataGenerator::hexadecimal($this->getLength()); } From 2d43fd6cc1ca51f1447c4046572fe9ad18c1c2bc Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Thu, 12 Sep 2024 10:42:19 +0200 Subject: [PATCH 10/10] Handle negative value for usage min and max --- lib/equal/orm/usages/Usage.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/equal/orm/usages/Usage.class.php b/lib/equal/orm/usages/Usage.class.php index 7022a082b..59a13a8e9 100644 --- a/lib/equal/orm/usages/Usage.class.php +++ b/lib/equal/orm/usages/Usage.class.php @@ -174,7 +174,7 @@ public function getScale(): int { public function __construct(string $usage_str) { // check usage string consistency - if(!preg_match('/([a-z]+)(\[([0-9]+)\])?\/?([-a-z0-9]*)(\.([-a-z0-9.]*))?(:(([-0-9a-z]*)\.?([0-9]*)))?({([0-9]+)(,([0-9]+))?})?/', $usage_str, $matches)) { + if(!preg_match('/([a-z]+)(\[([0-9]+)\])?\/?([-a-z0-9]*)(\.([-a-z0-9.]*))?(:(([-0-9a-z]*)\.?([0-9]*)))?({(-?[0-9]+)(,(-?[0-9]+))?})?/', $usage_str, $matches)) { trigger_error("ORM::invalid usage format $usage_str", QN_REPORT_WARNING); } else {