From 7b1c57c4e93f36f25546c2ea6e6a30d282205a84 Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Fri, 27 Sep 2024 11:30:31 +0200 Subject: [PATCH 01/16] Enh --- lib/equal/orm/ModelFactory.class.php | 114 +++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 lib/equal/orm/ModelFactory.class.php diff --git a/lib/equal/orm/ModelFactory.class.php b/lib/equal/orm/ModelFactory.class.php new file mode 100644 index 000000000..d38f85a9c --- /dev/null +++ b/lib/equal/orm/ModelFactory.class.php @@ -0,0 +1,114 @@ +qty = $qty; + + return $this; + } + + /** + * Sets the forced values of models to create for the next `ModelFactory::create()` function call + * + * @link ModelFactory::create() + * @throws Exception + */ + public function values(array $values): ModelFactory { + foreach($values as $field => $value) { + if(!is_string($field)) { + throw new Exception('field_must_be_a_string', EQ_ERROR_INVALID_PARAM); + } + if(is_array($value) || is_object($value)) { + throw new Exception('not_expected_value', EQ_ERROR_INVALID_PARAM); + } + } + + $this->values = $values; + + return $this; + } + + /** + * @param class-string $class + * @see ModelFactory::qty() To set the quantity of objects to create + * @see ModelFactory::values() To set the forced values of objects to create + * @throws Exception + */ + public function create(string $class): array { + /** @var ObjectManager $orm */ + $orm = $this->container->get('orm'); + + $model = $orm->getModel($class); + if(!$model) { + throw new Exception('unknown_entity', EQ_ERROR_INVALID_PARAM); + } + + $entities = []; + $schema = $model->getSchema(); + for($i = 1; $i <= $this->qty; $i++) { + $entities[] = $this->createEntityFromModelSchema($schema, $this->values); + } + + $this->resetProperties(); + + return $entities; + } + + /** + * @throws Exception + */ + private function createEntityFromModelSchema(array $model_schema, array $forced_values = []): array { + $object = []; + foreach($model_schema as $field => $field_descriptor) { + if(array_key_exists($field, $forced_values)) { + $object[$field] = $forced_values[$field]; + continue; + } + elseif(in_array($field, $this->root_fields)) { + continue; + } + + $is_required = $field_descriptor['required'] ?? false; + if(!$is_required && DataGenerator::boolean(0.05)) { + $object[$field] = null; + } + else { + $object[$field] = DataGenerator::generateFromField($field, $field_descriptor); + } + } + + return $object; + } + + private function resetProperties(): void { + $this->qty = 1; + $this->values = []; + } +} From 2f7936b8311f26f34a1155524d34ed5bba290853 Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Fri, 27 Sep 2024 12:27:48 +0200 Subject: [PATCH 02/16] Enhance comments + handle one or multiple object(s) created --- lib/equal/orm/ModelFactory.class.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/equal/orm/ModelFactory.class.php b/lib/equal/orm/ModelFactory.class.php index d38f85a9c..07345f150 100644 --- a/lib/equal/orm/ModelFactory.class.php +++ b/lib/equal/orm/ModelFactory.class.php @@ -7,6 +7,9 @@ use equal\services\Container; use Exception; +/** + * @method static ModelFactory getInstance() + */ class ModelFactory extends Service { private $qty = 1; @@ -56,9 +59,11 @@ public function values(array $values): ModelFactory { } /** + * Returns one or multiple generated object(s) using the given class' model schema + * * @param class-string $class - * @see ModelFactory::qty() To set the quantity of objects to create - * @see ModelFactory::values() To set the forced values of objects to create + * @see ModelFactory::qty() To set the quantity of objects to create (default: `1`) + * @see ModelFactory::values() To set the forced values of objects to create (default: `[]`) * @throws Exception */ public function create(string $class): array { @@ -78,7 +83,7 @@ public function create(string $class): array { $this->resetProperties(); - return $entities; + return count($entities) === 1 ? $entities[0] : $entities; } /** From 03d63b6bd72bc986e841b3176c44db0ba0702f0b Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Fri, 27 Sep 2024 12:54:57 +0200 Subject: [PATCH 03/16] Handle sequences of values to set to created objects --- lib/equal/orm/ModelFactory.class.php | 60 ++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/lib/equal/orm/ModelFactory.class.php b/lib/equal/orm/ModelFactory.class.php index 07345f150..ffac35cdf 100644 --- a/lib/equal/orm/ModelFactory.class.php +++ b/lib/equal/orm/ModelFactory.class.php @@ -16,6 +16,8 @@ class ModelFactory extends Service { private $values = []; + private $sequences = []; + private $root_fields = ['id', 'creator', 'created', 'modifier', 'modified', 'deleted', 'state']; protected function __construct(Container $container) { @@ -44,12 +46,16 @@ public function qty(int $qty): ModelFactory { * @throws Exception */ public function values(array $values): ModelFactory { + if(!empty($this->sequences)) { + throw new Exception('cannot_set_values_and_sequences', EQ_ERROR_INVALID_PARAM); + } + foreach($values as $field => $value) { if(!is_string($field)) { - throw new Exception('field_must_be_a_string', EQ_ERROR_INVALID_PARAM); + throw new Exception('invalid_value_field_must_be_a_string', EQ_ERROR_INVALID_PARAM); } if(is_array($value) || is_object($value)) { - throw new Exception('not_expected_value', EQ_ERROR_INVALID_PARAM); + throw new Exception('invalid_value_not_expected_value', EQ_ERROR_INVALID_PARAM); } } @@ -58,6 +64,46 @@ public function values(array $values): ModelFactory { return $this; } + /** + * Sets sequences of forced values of models to create for the next `ModelFactory::create()` function call + * + * @link ModelFactory::create() + * @throws Exception + */ + public function sequences(array $sequences): ModelFactory { + if(!empty($this->values)) { + throw new Exception('cannot_set_values_and_sequences', EQ_ERROR_INVALID_PARAM); + } + + foreach($sequences as $index => $values) { + if(!is_int($index)) { + throw new Exception('invalid_sequence_index_must_be_integer', EQ_ERROR_INVALID_PARAM); + } + + foreach($values as $field => $value) { + if(!is_string($field)) { + throw new Exception('invalid_sequence_field_must_be_a_string', EQ_ERROR_INVALID_PARAM); + } + if(is_array($value) || is_object($value)) { + throw new Exception('invalid_sequence_not_expected_value', EQ_ERROR_INVALID_PARAM); + } + } + } + + $this->sequences = $sequences; + + return $this; + } + + private function getIncrementedSequenceIndex(int $sequence_index): int { + $sequence_index++; + if(!isset($this->sequences[$sequence_index])) { + $sequence_index = 0; + } + + return $sequence_index; + } + /** * Returns one or multiple generated object(s) using the given class' model schema * @@ -76,9 +122,17 @@ public function create(string $class): array { } $entities = []; + $sequence_index = 0; + $values = !empty($this->sequences) ? $this->sequences[0] : $this->values; + $schema = $model->getSchema(); for($i = 1; $i <= $this->qty; $i++) { - $entities[] = $this->createEntityFromModelSchema($schema, $this->values); + $entities[] = $this->createEntityFromModelSchema($schema, $values); + + if(!empty($this->sequences)) { + $sequence_index = $this->getIncrementedSequenceIndex($sequence_index); + $values = $this->sequences[$sequence_index]; + } } $this->resetProperties(); From 7c7b094c54cd761ed1b21a1d8dd3bc105af1060f Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Fri, 27 Sep 2024 16:41:37 +0200 Subject: [PATCH 04/16] Refacto to simplify --- lib/equal/orm/ModelFactory.class.php | 153 ++++++++------------------- 1 file changed, 47 insertions(+), 106 deletions(-) diff --git a/lib/equal/orm/ModelFactory.class.php b/lib/equal/orm/ModelFactory.class.php index ffac35cdf..296bf79cf 100644 --- a/lib/equal/orm/ModelFactory.class.php +++ b/lib/equal/orm/ModelFactory.class.php @@ -12,107 +12,22 @@ */ class ModelFactory extends Service { - private $qty = 1; - - private $values = []; - - private $sequences = []; - private $root_fields = ['id', 'creator', 'created', 'modifier', 'modified', 'deleted', 'state']; - protected function __construct(Container $container) { - } - - /** - * Sets the quantity of models to create for the next `ModelFactory::create()` function call - * - * @link ModelFactory::create() - * @throws Exception - */ - public function qty(int $qty): ModelFactory { - if($qty < 1) { - throw new Exception('quantity_must_be_greater_than_zero', EQ_ERROR_INVALID_PARAM); - } - - $this->qty = $qty; - - return $this; - } - - /** - * Sets the forced values of models to create for the next `ModelFactory::create()` function call - * - * @link ModelFactory::create() - * @throws Exception - */ - public function values(array $values): ModelFactory { - if(!empty($this->sequences)) { - throw new Exception('cannot_set_values_and_sequences', EQ_ERROR_INVALID_PARAM); - } - - foreach($values as $field => $value) { - if(!is_string($field)) { - throw new Exception('invalid_value_field_must_be_a_string', EQ_ERROR_INVALID_PARAM); - } - if(is_array($value) || is_object($value)) { - throw new Exception('invalid_value_not_expected_value', EQ_ERROR_INVALID_PARAM); - } - } - - $this->values = $values; - - return $this; - } - - /** - * Sets sequences of forced values of models to create for the next `ModelFactory::create()` function call - * - * @link ModelFactory::create() - * @throws Exception - */ - public function sequences(array $sequences): ModelFactory { - if(!empty($this->values)) { - throw new Exception('cannot_set_values_and_sequences', EQ_ERROR_INVALID_PARAM); - } - - foreach($sequences as $index => $values) { - if(!is_int($index)) { - throw new Exception('invalid_sequence_index_must_be_integer', EQ_ERROR_INVALID_PARAM); - } + private $relationship_field_types = ['many2one', 'one2many', 'many2many']; - foreach($values as $field => $value) { - if(!is_string($field)) { - throw new Exception('invalid_sequence_field_must_be_a_string', EQ_ERROR_INVALID_PARAM); - } - if(is_array($value) || is_object($value)) { - throw new Exception('invalid_sequence_not_expected_value', EQ_ERROR_INVALID_PARAM); - } - } - } - - $this->sequences = $sequences; - - return $this; - } - - private function getIncrementedSequenceIndex(int $sequence_index): int { - $sequence_index++; - if(!isset($this->sequences[$sequence_index])) { - $sequence_index = 0; - } - - return $sequence_index; + protected function __construct(Container $container) { } /** * Returns one or multiple generated object(s) using the given class' model schema * * @param class-string $class - * @see ModelFactory::qty() To set the quantity of objects to create (default: `1`) - * @see ModelFactory::values() To set the forced values of objects to create (default: `[]`) + * @param array $options Factory options like qty, values and sequences + * @return array * @throws Exception */ - public function create(string $class): array { + public function create(string $class, array $options = []): array { /** @var ObjectManager $orm */ $orm = $this->container->get('orm'); @@ -122,35 +37,66 @@ public function create(string $class): array { } $entities = []; + $sequence_index = 0; - $values = !empty($this->sequences) ? $this->sequences[0] : $this->values; + $values = !empty($options['sequences']) ? $options['sequences'][0] : ($options['values'] ?? []); + + if(!isset($options['qty'])) { + $qty = 1; + } + elseif(is_array($options['qty'])) { + $qty = mt_rand($options['qty'][0], $options['qty'][1]); + } + else { + $qty = $options['qty']; + } $schema = $model->getSchema(); - for($i = 1; $i <= $this->qty; $i++) { - $entities[] = $this->createEntityFromModelSchema($schema, $values); + for($i = 1; $i <= $qty; $i++) { + $entities[] = $this->createEntityFromModelSchema($schema, $values, $options['relationships'] ?? []); - if(!empty($this->sequences)) { - $sequence_index = $this->getIncrementedSequenceIndex($sequence_index); - $values = $this->sequences[$sequence_index]; + if(!empty($options['sequences'])) { + $sequence_index = isset($options['sequences'][++$sequence_index]) ? $sequence_index : 0; + + $values = $options['sequences'][$sequence_index]; } } - $this->resetProperties(); - - return count($entities) === 1 ? $entities[0] : $entities; + return $entities; } /** * @throws Exception */ - private function createEntityFromModelSchema(array $model_schema, array $forced_values = []): array { + private function createEntityFromModelSchema(array $model_schema, array $forced_values = [], array $relationships = []): array { $object = []; foreach($model_schema as $field => $field_descriptor) { + $field_type = $field_descriptor['result_type'] ?? $field_descriptor['type']; if(array_key_exists($field, $forced_values)) { $object[$field] = $forced_values[$field]; continue; } - elseif(in_array($field, $this->root_fields)) { + elseif( + in_array($field_type, $this->relationship_field_types) + && (in_array($field, $relationships) || array_key_exists($field, $relationships)) + ) { + if($field_type === 'many2one') { + $factory_options = $relationships[$field] ?? []; + if(isset($factory_options['qty']) && $factory_options['qty'] !== 1) { + $factory_options['qty'] = 1; + } + + $object[$field] = $this->create($field_descriptor['foreign_object'], $factory_options)[0]; + } else { + $object[$field] = $this->create($field_descriptor['foreign_object'], $relationships[$field] ?? []); + } + } + + if( + isset($object[$field]) + || in_array($field, $this->root_fields) + || in_array($field_type, $this->relationship_field_types) + ) { continue; } @@ -165,9 +111,4 @@ private function createEntityFromModelSchema(array $model_schema, array $forced_ return $object; } - - private function resetProperties(): void { - $this->qty = 1; - $this->values = []; - } } From 0bef618ea1782d135588c37746eb489876050d2a Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Fri, 27 Sep 2024 17:18:38 +0200 Subject: [PATCH 05/16] Handle computed fields + refacto to simplify + enhance factory options validity checks --- lib/equal/orm/ModelFactory.class.php | 83 +++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/lib/equal/orm/ModelFactory.class.php b/lib/equal/orm/ModelFactory.class.php index 296bf79cf..ce5625fe3 100644 --- a/lib/equal/orm/ModelFactory.class.php +++ b/lib/equal/orm/ModelFactory.class.php @@ -36,33 +36,87 @@ public function create(string $class, array $options = []): array { throw new Exception('unknown_entity', EQ_ERROR_INVALID_PARAM); } + $qty = $this->extractQtyFromOptions($options); + $sequences = $this->extractSequencesFromOptions($options); + $relationships = $this->extractRelationshipsFromOptions($options); + $entities = []; $sequence_index = 0; - $values = !empty($options['sequences']) ? $options['sequences'][0] : ($options['values'] ?? []); + $values = !empty($sequences) ? $sequences[0] : []; + $schema = $model->getSchema(); + for($i = 1; $i <=$qty; $i++) { + $entities[] = $this->createEntityFromModelSchema($schema, $values, $relationships); - if(!isset($options['qty'])) { - $qty = 1; + if(!empty($sequences)) { + $sequence_index = isset($sequences[++$sequence_index]) ? $sequence_index : 0; + + $values = $sequences[$sequence_index]; + } } - elseif(is_array($options['qty'])) { - $qty = mt_rand($options['qty'][0], $options['qty'][1]); + + return $entities; + } + + private function extractQtyFromOptions(array $options): int { + $qty = 1; + + if(isset($options['qty'])) { + if(is_array($options['qty']) && count($options['qty']) === 2 && is_int($options['qty'][0]) && is_int($options['qty'][1])) { + $qty = mt_rand($options['qty'][0], $options['qty'][1]); + } + elseif(is_int($options['qty'])) { + $qty = $options['qty']; + } else { + throw new Exception('invalid_qty', EQ_ERROR_INVALID_PARAM); + } } - else { - $qty = $options['qty']; + + return $qty; + } + + private function extractSequencesFromOptions(array $options): array { + $sequences = []; + + if(!empty($options['sequences'])) { + foreach($options['sequences'] as $index => $values) { + if(!is_int($index)) { + throw new Exception('invalid_sequence_index_must_be_integer', EQ_ERROR_INVALID_PARAM); + } + + foreach($values as $field => $value) { + if(!is_string($field)) { + throw new Exception('invalid_sequence_field_must_be_a_string', EQ_ERROR_INVALID_PARAM); + } + if(is_array($value) || is_object($value)) { + throw new Exception('invalid_sequence_not_expected_value', EQ_ERROR_INVALID_PARAM); + } + } + } + + $sequences = $options['sequences']; } - $schema = $model->getSchema(); - for($i = 1; $i <= $qty; $i++) { - $entities[] = $this->createEntityFromModelSchema($schema, $values, $options['relationships'] ?? []); + return $sequences; + } - if(!empty($options['sequences'])) { - $sequence_index = isset($options['sequences'][++$sequence_index]) ? $sequence_index : 0; + private function extractRelationshipsFromOptions(array $options): array { + $relationships = []; + if(!empty($options['relationships'])) { + foreach($options['relationships'] as $field => $factory_options) { + if(is_int($field) && is_string($factory_options)) { + continue; + } - $values = $options['sequences'][$sequence_index]; + if(!is_string($field) || !is_array($factory_options)) { + throw new Exception('invalid_relationship', EQ_ERROR_INVALID_PARAM); + } } + + $relationships = $options['relationships']; } - return $entities; + return $relationships; } /** @@ -96,6 +150,7 @@ private function createEntityFromModelSchema(array $model_schema, array $forced_ isset($object[$field]) || in_array($field, $this->root_fields) || in_array($field_type, $this->relationship_field_types) + || $field_descriptor['type'] === 'computed' ) { continue; } From a54cc9bedd2d6010f36ed5882580640e28d379cc Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Fri, 27 Sep 2024 17:27:20 +0200 Subject: [PATCH 06/16] Add comment --- lib/equal/orm/ModelFactory.class.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/equal/orm/ModelFactory.class.php b/lib/equal/orm/ModelFactory.class.php index ce5625fe3..00542243d 100644 --- a/lib/equal/orm/ModelFactory.class.php +++ b/lib/equal/orm/ModelFactory.class.php @@ -24,6 +24,14 @@ protected function __construct(Container $container) { * * @param class-string $class * @param array $options Factory options like qty, values and sequences + * - 'qty': int (optional) The quantity of items. + * Example: 2 + * - 'values': array (optional) A list of values to force values of entity.ies. + * Example: ['is_sent' => false] + * -> All entities created will have `is_sent` value to false + * - 'sequences': array (optional) A list of values sequences to force values of multiple entities. + * Example: [['name' => 'Group 1'], ['name' => 'Group 2']] + * -> First entity create will have name to "Group 1" and second will have "Group 2", etc. * @return array * @throws Exception */ From 8944dff74d7e0fb7478f3dbcfbb9ca14d1b17014 Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Fri, 27 Sep 2024 17:32:15 +0200 Subject: [PATCH 07/16] Enhance --- lib/equal/orm/ModelFactory.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/equal/orm/ModelFactory.class.php b/lib/equal/orm/ModelFactory.class.php index 00542243d..651a04c68 100644 --- a/lib/equal/orm/ModelFactory.class.php +++ b/lib/equal/orm/ModelFactory.class.php @@ -26,10 +26,10 @@ protected function __construct(Container $container) { * @param array $options Factory options like qty, values and sequences * - 'qty': int (optional) The quantity of items. * Example: 2 - * - 'values': array (optional) A list of values to force values of entity.ies. + * - 'values': array (optional) A list of values to force for all entities created. * Example: ['is_sent' => false] * -> All entities created will have `is_sent` value to false - * - 'sequences': array (optional) A list of values sequences to force values of multiple entities. + * - 'sequences': array (optional) A list of values sequences to force for one entity. * Example: [['name' => 'Group 1'], ['name' => 'Group 2']] * -> First entity create will have name to "Group 1" and second will have "Group 2", etc. * @return array From 6a5a11a9b1f052dbbcada055f43bce5c61fc811e Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Mon, 30 Sep 2024 10:41:26 +0200 Subject: [PATCH 08/16] Handle unique for model factory, can add unique true option --- lib/equal/orm/ModelFactory.class.php | 92 +++++++++++++++++++++++----- 1 file changed, 76 insertions(+), 16 deletions(-) diff --git a/lib/equal/orm/ModelFactory.class.php b/lib/equal/orm/ModelFactory.class.php index 651a04c68..46dbb0913 100644 --- a/lib/equal/orm/ModelFactory.class.php +++ b/lib/equal/orm/ModelFactory.class.php @@ -32,6 +32,7 @@ protected function __construct(Container $container) { * - 'sequences': array (optional) A list of values sequences to force for one entity. * Example: [['name' => 'Group 1'], ['name' => 'Group 2']] * -> First entity create will have name to "Group 1" and second will have "Group 2", etc. + * - 'unique': bool (optional) Should the entities returned follow unique fields constraints * @return array * @throws Exception */ @@ -47,14 +48,23 @@ public function create(string $class, array $options = []): array { $qty = $this->extractQtyFromOptions($options); $sequences = $this->extractSequencesFromOptions($options); $relationships = $this->extractRelationshipsFromOptions($options); + $should_be_unique = $this->extractUniqueFromOptions($options); $entities = []; $sequence_index = 0; $values = !empty($sequences) ? $sequences[0] : []; - $schema = $model->getSchema(); - for($i = 1; $i <=$qty; $i++) { - $entities[] = $this->createEntityFromModelSchema($schema, $values, $relationships); + for($i = 1; $i <= $qty; $i++) { + if(!$should_be_unique) { + $entities[] = $this->createEntityFromModel($model, $values, $relationships); + } + else { + try { + $entities[] = $this->createUniqueEntityFromModel($entities, $model, $values, $relationships); + } catch(Exception $e) { + trigger_error("PHP::skip creation of $class because not able to create a valid unique entity.", EQ_REPORT_WARNING); + } + } if(!empty($sequences)) { $sequence_index = isset($sequences[++$sequence_index]) ? $sequence_index : 0; @@ -86,6 +96,10 @@ private function extractQtyFromOptions(array $options): int { private function extractSequencesFromOptions(array $options): array { $sequences = []; + if(empty($options['sequences']) && !empty($options['values'])) { + $options['sequences'] = [$options['values']]; + } + if(!empty($options['sequences'])) { foreach($options['sequences'] as $index => $values) { if(!is_int($index)) { @@ -127,15 +141,20 @@ private function extractRelationshipsFromOptions(array $options): array { return $relationships; } - /** - * @throws Exception - */ - private function createEntityFromModelSchema(array $model_schema, array $forced_values = [], array $relationships = []): array { - $object = []; - foreach($model_schema as $field => $field_descriptor) { + private function extractUniqueFromOptions(array $options): bool { + if(isset($options['unique']) && !is_bool($options['unique'])) { + throw new Exception('invalid_unique', EQ_ERROR_INVALID_PARAM); + } + + return $options['unique'] ?? false; + } + + private function createEntityFromModel(Model $model, array $forced_values = [], array $relationships = []): array { + $entity = []; + foreach($model->getSchema() as $field => $field_descriptor) { $field_type = $field_descriptor['result_type'] ?? $field_descriptor['type']; if(array_key_exists($field, $forced_values)) { - $object[$field] = $forced_values[$field]; + $entity[$field] = $forced_values[$field]; continue; } elseif( @@ -148,14 +167,14 @@ private function createEntityFromModelSchema(array $model_schema, array $forced_ $factory_options['qty'] = 1; } - $object[$field] = $this->create($field_descriptor['foreign_object'], $factory_options)[0]; + $entity[$field] = $this->create($field_descriptor['foreign_object'], $factory_options)[0]; } else { - $object[$field] = $this->create($field_descriptor['foreign_object'], $relationships[$field] ?? []); + $entity[$field] = $this->create($field_descriptor['foreign_object'], $relationships[$field] ?? []); } } if( - isset($object[$field]) + isset($entity[$field]) || in_array($field, $this->root_fields) || in_array($field_type, $this->relationship_field_types) || $field_descriptor['type'] === 'computed' @@ -165,13 +184,54 @@ private function createEntityFromModelSchema(array $model_schema, array $forced_ $is_required = $field_descriptor['required'] ?? false; if(!$is_required && DataGenerator::boolean(0.05)) { - $object[$field] = null; + $entity[$field] = null; } else { - $object[$field] = DataGenerator::generateFromField($field, $field_descriptor); + $entity[$field] = DataGenerator::generateFromField($field, $field_descriptor); + } + } + + return $entity; + } + + /** + * @throws Exception + */ + private function createUniqueEntityFromModel(array $other_entities, Model $model, array $forced_values = [], array $relationships = []): array { + $schema = $model->getSchema(); + $model_unique_conf = $model->getConstraints(); + + $count = 10; + while($count > 0) { + $entity = $this->createEntityFromModel($model, $forced_values, $relationships); + + $unique_valid = true; + foreach($other_entities as $ent) { + foreach($schema as $field => $field_descriptor) { + $field_should_be_unique = $field_descriptor['unique'] ?? false; + if(!$field_should_be_unique && !empty($model_unique_conf)) { + foreach($model_unique_conf as $unique_conf) { + if(count($unique_conf) === 1 && $unique_conf[0] === $field) { + $field_should_be_unique = true; + break; + } + } + } + + if($field_should_be_unique && $ent[$field] === $entity[$field]) { + $unique_valid = false; + break 2; + } + } } + + if($unique_valid) { + return $entity; + } + + $count--; } - return $object; + throw new Exception('not_able_to_generate_unique_valid_entity', EQ_ERROR_CONFLICT_OBJECT); } } From 4df1ead9221cb41c6999fc93f85d8a2e75a0ba34 Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Mon, 30 Sep 2024 11:47:13 +0200 Subject: [PATCH 09/16] Fix wrong method called to get constraints --- packages/core/actions/model/generate.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/core/actions/model/generate.php b/packages/core/actions/model/generate.php index d9a803ba5..0797d1563 100644 --- a/packages/core/actions/model/generate.php +++ b/packages/core/actions/model/generate.php @@ -179,10 +179,7 @@ // #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(); -} +$model_unique_conf = $model->getConstraints(); $schema = $model->getSchema(); From 6c9a3ab1118dbc99d2e1661c4e2b294cb2205643 Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Mon, 30 Sep 2024 12:15:06 +0200 Subject: [PATCH 10/16] Add model factory service to container --- eq.lib.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eq.lib.php b/eq.lib.php index 7fc7decca..f2c3102eb 100644 --- a/eq.lib.php +++ b/eq.lib.php @@ -590,6 +590,8 @@ public static function init() { $om = $container->get('orm'); // init collections provider $container->get('equal\orm\Collections'); + // init model factory provider + $container->get('equal\orm\ModelFactory'); spl_autoload_register([$om, 'getModel']); } catch(\Throwable $e) { From 8468217ef6074cfe49bd928aeafdbebc37d038a6 Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Mon, 30 Sep 2024 14:23:15 +0200 Subject: [PATCH 11/16] Fix mistake constraints instead of unique --- lib/equal/orm/ModelFactory.class.php | 2 +- packages/core/actions/model/generate.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/equal/orm/ModelFactory.class.php b/lib/equal/orm/ModelFactory.class.php index 46dbb0913..b952ed794 100644 --- a/lib/equal/orm/ModelFactory.class.php +++ b/lib/equal/orm/ModelFactory.class.php @@ -199,7 +199,7 @@ private function createEntityFromModel(Model $model, array $forced_values = [], */ private function createUniqueEntityFromModel(array $other_entities, Model $model, array $forced_values = [], array $relationships = []): array { $schema = $model->getSchema(); - $model_unique_conf = $model->getConstraints(); + $model_unique_conf = $model->getUnique(); $count = 10; while($count > 0) { diff --git a/packages/core/actions/model/generate.php b/packages/core/actions/model/generate.php index 0797d1563..a898f21f9 100644 --- a/packages/core/actions/model/generate.php +++ b/packages/core/actions/model/generate.php @@ -179,7 +179,7 @@ // #todo: Handle multi columns unique (only single column unique is handled) // #todo: Handle unique for many2one relations -$model_unique_conf = $model->getConstraints(); +$model_unique_conf = $model->getUnique(); $schema = $model->getSchema(); From 58c7dbb0b5f6d874e2a9669b08f545746d196a61 Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Tue, 1 Oct 2024 09:42:33 +0200 Subject: [PATCH 12/16] Make ModelFactory access static + Refacto relationships to relations to harmonize with generate nomenclature + Enhance create method comments --- eq.lib.php | 2 - lib/equal/orm/ModelFactory.class.php | 102 +++++++++++++-------------- 2 files changed, 50 insertions(+), 54 deletions(-) diff --git a/eq.lib.php b/eq.lib.php index f2c3102eb..7fc7decca 100644 --- a/eq.lib.php +++ b/eq.lib.php @@ -590,8 +590,6 @@ public static function init() { $om = $container->get('orm'); // init collections provider $container->get('equal\orm\Collections'); - // init model factory provider - $container->get('equal\orm\ModelFactory'); spl_autoload_register([$om, 'getModel']); } catch(\Throwable $e) { diff --git a/lib/equal/orm/ModelFactory.class.php b/lib/equal/orm/ModelFactory.class.php index b952ed794..ba239b70c 100644 --- a/lib/equal/orm/ModelFactory.class.php +++ b/lib/equal/orm/ModelFactory.class.php @@ -3,52 +3,49 @@ namespace equal\orm; use equal\data\DataGenerator; -use equal\organic\Service; -use equal\services\Container; use Exception; -/** - * @method static ModelFactory getInstance() - */ -class ModelFactory extends Service { +class ModelFactory { - private $root_fields = ['id', 'creator', 'created', 'modifier', 'modified', 'deleted', 'state']; + private static $root_fields = ['id', 'creator', 'created', 'modifier', 'modified', 'deleted', 'state']; - private $relationship_field_types = ['many2one', 'one2many', 'many2many']; - - protected function __construct(Container $container) { - } + private static $relationship_field_types = ['many2one', 'one2many', 'many2many']; /** * Returns one or multiple generated object(s) using the given class' model schema * * @param class-string $class - * @param array $options Factory options like qty, values and sequences - * - 'qty': int (optional) The quantity of items. - * Example: 2 + * @param array $options Factory options like qty, values, sequences, unique and relations + * - 'qty': int|array (optional) The quantity of items (default 1). + * Example: (int): 2 + * (array): [1, 5] (between 1 and 5 included) * - 'values': array (optional) A list of values to force for all entities created. * Example: ['is_sent' => false] - * -> All entities created will have `is_sent` value to false + * -> All entities created will have `is_sent` value to false. * - 'sequences': array (optional) A list of values sequences to force for one entity. - * Example: [['name' => 'Group 1'], ['name' => 'Group 2']] + * Example: [['name' => 'Group 1'], ['name' => 'Group 2']] * -> First entity create will have name to "Group 1" and second will have "Group 2", etc. - * - 'unique': bool (optional) Should the entities returned follow unique fields constraints + * - 'unique': bool (optional) Should the entities returned follow unique fields constraints (default true). + * Example: true + * - 'relations': array (optional) Generate entities for relations. + * Example: ['users_ids' => ['qty' => 2]] + * -> Keys are relation field names and values are Factory options. * @return array * @throws Exception */ - public function create(string $class, array $options = []): array { + public static function create(string $class, array $options = []): array { /** @var ObjectManager $orm */ - $orm = $this->container->get('orm'); + ['orm' => $orm] = \eQual::inject(['orm']); $model = $orm->getModel($class); if(!$model) { throw new Exception('unknown_entity', EQ_ERROR_INVALID_PARAM); } - $qty = $this->extractQtyFromOptions($options); - $sequences = $this->extractSequencesFromOptions($options); - $relationships = $this->extractRelationshipsFromOptions($options); - $should_be_unique = $this->extractUniqueFromOptions($options); + $qty = self::extractQtyFromOptions($options); + $sequences = self::extractSequencesFromOptions($options); + $relations = self::extractRelationsFromOptions($options); + $should_be_unique = self::extractUniqueFromOptions($options); $entities = []; @@ -56,11 +53,11 @@ public function create(string $class, array $options = []): array { $values = !empty($sequences) ? $sequences[0] : []; for($i = 1; $i <= $qty; $i++) { if(!$should_be_unique) { - $entities[] = $this->createEntityFromModel($model, $values, $relationships); + $entities[] = self::createEntityFromModel($model, $values, $relations); } else { try { - $entities[] = $this->createUniqueEntityFromModel($entities, $model, $values, $relationships); + $entities[] = self::createUniqueEntityFromModel($entities, $model, $values, $relations); } catch(Exception $e) { trigger_error("PHP::skip creation of $class because not able to create a valid unique entity.", EQ_REPORT_WARNING); } @@ -76,7 +73,7 @@ public function create(string $class, array $options = []): array { return $entities; } - private function extractQtyFromOptions(array $options): int { + private static function extractQtyFromOptions(array $options): int { $qty = 1; if(isset($options['qty'])) { @@ -85,15 +82,16 @@ private function extractQtyFromOptions(array $options): int { } elseif(is_int($options['qty'])) { $qty = $options['qty']; - } else { - throw new Exception('invalid_qty', EQ_ERROR_INVALID_PARAM); + } + else { + throw new Exception('invalid_option_qty', EQ_ERROR_INVALID_PARAM); } } return $qty; } - private function extractSequencesFromOptions(array $options): array { + private static function extractSequencesFromOptions(array $options): array { $sequences = []; if(empty($options['sequences']) && !empty($options['values'])) { @@ -103,15 +101,15 @@ private function extractSequencesFromOptions(array $options): array { if(!empty($options['sequences'])) { foreach($options['sequences'] as $index => $values) { if(!is_int($index)) { - throw new Exception('invalid_sequence_index_must_be_integer', EQ_ERROR_INVALID_PARAM); + throw new Exception('invalid_option_sequences_index_must_be_integer', EQ_ERROR_INVALID_PARAM); } foreach($values as $field => $value) { if(!is_string($field)) { - throw new Exception('invalid_sequence_field_must_be_a_string', EQ_ERROR_INVALID_PARAM); + throw new Exception('invalid_option_sequences_field_must_be_a_string', EQ_ERROR_INVALID_PARAM); } if(is_array($value) || is_object($value)) { - throw new Exception('invalid_sequence_not_expected_value', EQ_ERROR_INVALID_PARAM); + throw new Exception('invalid_option_sequences_not_expected_value', EQ_ERROR_INVALID_PARAM); } } } @@ -122,34 +120,34 @@ private function extractSequencesFromOptions(array $options): array { return $sequences; } - private function extractRelationshipsFromOptions(array $options): array { - $relationships = []; - if(!empty($options['relationships'])) { - foreach($options['relationships'] as $field => $factory_options) { + private static function extractRelationsFromOptions(array $options): array { + $relations = []; + if(!empty($options['relations'])) { + foreach($options['relations'] as $field => $factory_options) { if(is_int($field) && is_string($factory_options)) { continue; } if(!is_string($field) || !is_array($factory_options)) { - throw new Exception('invalid_relationship', EQ_ERROR_INVALID_PARAM); + throw new Exception('invalid_option_relations', EQ_ERROR_INVALID_PARAM); } } - $relationships = $options['relationships']; + $relations = $options['relations']; } - return $relationships; + return $relations; } - private function extractUniqueFromOptions(array $options): bool { + private static function extractUniqueFromOptions(array $options): bool { if(isset($options['unique']) && !is_bool($options['unique'])) { - throw new Exception('invalid_unique', EQ_ERROR_INVALID_PARAM); + throw new Exception('invalid_option_unique', EQ_ERROR_INVALID_PARAM); } - return $options['unique'] ?? false; + return $options['unique'] ?? true; } - private function createEntityFromModel(Model $model, array $forced_values = [], array $relationships = []): array { + private static function createEntityFromModel(Model $model, array $forced_values = [], array $relations = []): array { $entity = []; foreach($model->getSchema() as $field => $field_descriptor) { $field_type = $field_descriptor['result_type'] ?? $field_descriptor['type']; @@ -158,25 +156,25 @@ private function createEntityFromModel(Model $model, array $forced_values = [], continue; } elseif( - in_array($field_type, $this->relationship_field_types) - && (in_array($field, $relationships) || array_key_exists($field, $relationships)) + in_array($field_type, self::$relationship_field_types) + && (in_array($field, $relations) || array_key_exists($field, $relations)) ) { if($field_type === 'many2one') { - $factory_options = $relationships[$field] ?? []; + $factory_options = $relations[$field] ?? []; if(isset($factory_options['qty']) && $factory_options['qty'] !== 1) { $factory_options['qty'] = 1; } - $entity[$field] = $this->create($field_descriptor['foreign_object'], $factory_options)[0]; + $entity[$field] = self::create($field_descriptor['foreign_object'], $factory_options)[0]; } else { - $entity[$field] = $this->create($field_descriptor['foreign_object'], $relationships[$field] ?? []); + $entity[$field] = self::create($field_descriptor['foreign_object'], $relations[$field] ?? []); } } if( isset($entity[$field]) - || in_array($field, $this->root_fields) - || in_array($field_type, $this->relationship_field_types) + || in_array($field, self::$root_fields) + || in_array($field_type, self::$relationship_field_types) || $field_descriptor['type'] === 'computed' ) { continue; @@ -197,13 +195,13 @@ private function createEntityFromModel(Model $model, array $forced_values = [], /** * @throws Exception */ - private function createUniqueEntityFromModel(array $other_entities, Model $model, array $forced_values = [], array $relationships = []): array { + private static function createUniqueEntityFromModel(array $other_entities, Model $model, array $forced_values = [], array $relations = []): array { $schema = $model->getSchema(); $model_unique_conf = $model->getUnique(); $count = 10; while($count > 0) { - $entity = $this->createEntityFromModel($model, $forced_values, $relationships); + $entity = self::createEntityFromModel($model, $forced_values, $relations); $unique_valid = true; foreach($other_entities as $ent) { From db635c6482e7adda008dea1ee2b1dc64cbfd5b58 Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Tue, 1 Oct 2024 09:46:11 +0200 Subject: [PATCH 13/16] Handle ModelFactory not as a Singleton Service --- lib/equal/orm/ModelFactory.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/equal/orm/ModelFactory.class.php b/lib/equal/orm/ModelFactory.class.php index ba239b70c..ea91084e7 100644 --- a/lib/equal/orm/ModelFactory.class.php +++ b/lib/equal/orm/ModelFactory.class.php @@ -18,7 +18,7 @@ class ModelFactory { * @param array $options Factory options like qty, values, sequences, unique and relations * - 'qty': int|array (optional) The quantity of items (default 1). * Example: (int): 2 - * (array): [1, 5] (between 1 and 5 included) + * (array): [1, 5] (between 1 and 5 included) * - 'values': array (optional) A list of values to force for all entities created. * Example: ['is_sent' => false] * -> All entities created will have `is_sent` value to false. From 6bcc8b2675f9a4d1afdb49951752a7d597e24ade Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Tue, 1 Oct 2024 09:51:07 +0200 Subject: [PATCH 14/16] Handle ModelFactory not as a Singleton Service --- lib/equal/orm/ModelFactory.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/equal/orm/ModelFactory.class.php b/lib/equal/orm/ModelFactory.class.php index ea91084e7..90906bc14 100644 --- a/lib/equal/orm/ModelFactory.class.php +++ b/lib/equal/orm/ModelFactory.class.php @@ -21,10 +21,10 @@ class ModelFactory { * (array): [1, 5] (between 1 and 5 included) * - 'values': array (optional) A list of values to force for all entities created. * Example: ['is_sent' => false] - * -> All entities created will have `is_sent` value to false. + * -> All entities created will have `is_sent` set to false. * - 'sequences': array (optional) A list of values sequences to force for one entity. * Example: [['name' => 'Group 1'], ['name' => 'Group 2']] - * -> First entity create will have name to "Group 1" and second will have "Group 2", etc. + * -> First entity created will have `name` set to "Group 1" and second will have `name` set to "Group 2", etc. * - 'unique': bool (optional) Should the entities returned follow unique fields constraints (default true). * Example: true * - 'relations': array (optional) Generate entities for relations. From 7c7453fc969723e979d6c1cabee620194f3add3a Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Tue, 1 Oct 2024 10:10:13 +0200 Subject: [PATCH 15/16] Skip alias fields --- lib/equal/orm/ModelFactory.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/equal/orm/ModelFactory.class.php b/lib/equal/orm/ModelFactory.class.php index 90906bc14..52783fa22 100644 --- a/lib/equal/orm/ModelFactory.class.php +++ b/lib/equal/orm/ModelFactory.class.php @@ -175,7 +175,7 @@ private static function createEntityFromModel(Model $model, array $forced_values isset($entity[$field]) || in_array($field, self::$root_fields) || in_array($field_type, self::$relationship_field_types) - || $field_descriptor['type'] === 'computed' + || in_array($field_descriptor['type'], ['computed', 'alias']) ) { continue; } From 180e3133f40b551b2afb8c3a4df98e641c80f803 Mon Sep 17 00:00:00 2001 From: Lucas Laurent Date: Tue, 1 Oct 2024 10:24:56 +0200 Subject: [PATCH 16/16] Handle return only one entity if one is wanted else return as array of entities --- lib/equal/orm/ModelFactory.class.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/equal/orm/ModelFactory.class.php b/lib/equal/orm/ModelFactory.class.php index 52783fa22..581d2c459 100644 --- a/lib/equal/orm/ModelFactory.class.php +++ b/lib/equal/orm/ModelFactory.class.php @@ -70,7 +70,9 @@ public static function create(string $class, array $options = []): array { } } - return $entities; + $one_entity_wanted = ($options['qty'] ?? 1) === 1; + + return $one_entity_wanted ? $entities[0] : $entities; } private static function extractQtyFromOptions(array $options): int { @@ -165,9 +167,15 @@ private static function createEntityFromModel(Model $model, array $forced_values $factory_options['qty'] = 1; } - $entity[$field] = self::create($field_descriptor['foreign_object'], $factory_options)[0]; + $entity[$field] = self::create($field_descriptor['foreign_object'], $factory_options); } else { - $entity[$field] = self::create($field_descriptor['foreign_object'], $relations[$field] ?? []); + $relation_entities = self::create($field_descriptor['foreign_object'], $relations[$field] ?? []); + if(!isset($relation_entities[0])) { + // If only one item returned put it in an array + $relation_entities = [$relation_entities]; + } + + $entity[$field] = $relation_entities; } }