Skip to content

Commit 0111117

Browse files
authored
feat: Improve generation of valid factory data (#970)
1 parent 22a278e commit 0111117

File tree

2 files changed

+206
-5
lines changed

2 files changed

+206
-5
lines changed

src/Generators/FactoryGenerator.php

Lines changed: 204 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace InfyOm\Generator\Generators;
44

5+
use Illuminate\Support\Str;
56
use InfyOm\Generator\Common\CommandData;
67
use InfyOm\Generator\Utils\FileUtil;
78
use InfyOm\Generator\Utils\GeneratorFieldsInputUtil;
@@ -17,6 +18,8 @@ class FactoryGenerator extends BaseGenerator
1718
private $path;
1819
/** @var string */
1920
private $fileName;
21+
/** @var array */
22+
private $relations = [];
2023

2124
/**
2225
* FactoryGenerator constructor.
@@ -28,6 +31,28 @@ public function __construct(CommandData $commandData)
2831
$this->commandData = $commandData;
2932
$this->path = $commandData->config->pathFactory;
3033
$this->fileName = $this->commandData->modelName.'Factory.php';
34+
//setup relations if available
35+
//assumes relation fields are tailed with _id if not supplied
36+
if (property_exists($this->commandData, 'relations')) {
37+
foreach ($this->commandData->relations as $r) {
38+
if ($r->type == 'mt1') {
39+
$relation = (isset($r->inputs[0])) ? $r->inputs[0] : null;
40+
$field = false;
41+
if (isset($r->inputs[1])) {
42+
$field = $r->inputs[1];
43+
} else {
44+
$field = Str::snake($relation).'_id';
45+
}
46+
if ($field) {
47+
$rel = $relation;
48+
$this->relations[$field] = [
49+
'relation' => $rel,
50+
'model_class' => $this->commandData->config->nsModel.'\\'.$relation,
51+
];
52+
}
53+
}
54+
}
55+
}
3156
}
3257

3358
public function generate()
@@ -53,7 +78,21 @@ private function fillTemplate($templateData)
5378

5479
$templateData = str_replace(
5580
'$FIELDS$',
56-
implode(','.infy_nl_tab(1, 2), $this->generateFields()),
81+
implode(','.infy_nl_tab(1, 3), $this->generateFields()),
82+
$templateData
83+
);
84+
85+
$extra = $this->getRelationsBootstrap();
86+
87+
$templateData = str_replace(
88+
'$RELATION_USES$',
89+
$extra['uses'],
90+
$templateData
91+
);
92+
93+
$templateData = str_replace(
94+
'$RELATIONS$',
95+
$extra['text'],
5796
$templateData
5897
);
5998

@@ -67,28 +106,72 @@ private function generateFields()
67106
{
68107
$fields = [];
69108

109+
//get model validation rules
110+
$class = $this->commandData->config->nsModel.'\\'.$this->commandData->modelName;
111+
$rules = $class::$rules;
112+
$relations = array_keys($this->relations);
113+
70114
foreach ($this->commandData->fields as $field) {
71115
if ($field->isPrimary) {
72116
continue;
73117
}
74118

75119
$fieldData = "'".$field->name."' => ".'$this->faker->';
120+
$rule = null;
121+
if (isset($rules[$field->name])) {
122+
$rule = $rules[$field->name];
123+
}
76124

77125
switch ($field->fieldType) {
78126
case 'integer':
127+
case 'smallinteger':
79128
case 'float':
80-
$fakerData = 'randomDigitNotNull';
129+
$fakerData = in_array($field->name, $relations) ? ':relation' : $this->getValidNumber($rule, 999);
130+
break;
131+
case 'long':
132+
case 'biginteger':
133+
case 'double':
134+
case 'decimal':
135+
$fakerData = $this->getValidNumber($rule);
81136
break;
82137
case 'string':
83-
$fakerData = 'word';
138+
case 'char':
139+
$lower = strtolower($field->name);
140+
$firstChar = substr($lower, 0, 1);
141+
if (strpos($lower, 'email') !== false) {
142+
$fakerData = 'email';
143+
} elseif ($firstChar == 'f' && strpos($lower, 'name') !== false) {
144+
$fakerData = 'firstName';
145+
} elseif (($firstChar == 's' || $firstChar == 'l') && strpos($lower, 'name') !== false) {
146+
$fakerData = 'lastName';
147+
} elseif (strpos($lower, 'phone') !== false) {
148+
$fakerData = "numerify('0##########')";
149+
} elseif (strpos($lower, 'password') !== false) {
150+
$fakerData = "lexify('1???@???A???')";
151+
} elseif (strpos($lower, 'address')) {
152+
$fakerData = 'address';
153+
} else {
154+
if (!$rule) {
155+
$rule = 'max:255';
156+
}
157+
$fakerData = $this->getValidText($rule);
158+
}
84159
break;
85160
case 'text':
86-
$fakerData = 'text';
161+
$fakerData = $rule ? $this->getValidText($rule) : 'text(500)';
162+
case 'boolean':
163+
$fakerData = 'boolean';
164+
break;
165+
case 'date':
166+
$fakerData = "date('Y-m-d')";
87167
break;
88168
case 'datetime':
89169
case 'timestamp':
90170
$fakerData = "date('Y-m-d H:i:s')";
91171
break;
172+
case 'time':
173+
$fakerData = "date('H:i:s')";
174+
break;
92175
case 'enum':
93176
$fakerData = 'randomElement('.
94177
GeneratorFieldsInputUtil::prepareValuesArrayStr($field->htmlValues).
@@ -98,14 +181,130 @@ private function generateFields()
98181
$fakerData = 'word';
99182
}
100183

101-
$fieldData .= $fakerData;
184+
if ($fakerData == ':relation') {
185+
$fieldData = $this->getValidRelation($field->name);
186+
} else {
187+
$fieldData .= $fakerData;
188+
}
102189

103190
$fields[] = $fieldData;
104191
}
105192

106193
return $fields;
107194
}
108195

196+
/**
197+
* Generates a valid number based on applicable model rule.
198+
*
199+
* @param string $rule The applicable model rule
200+
* @param int $max The maximum number to generate.
201+
*
202+
* @return string
203+
*/
204+
public function getValidNumber($rule = null, $max = PHP_INT_MAX)
205+
{
206+
if ($rule) {
207+
$max = $this->extractMinMax($rule, 'max') ?? $max;
208+
$min = $this->extractMinMax($rule, 'min') ?? 0;
209+
210+
return "numberBetween($min, $max)";
211+
} else {
212+
return 'randomDigitNotNull';
213+
}
214+
}
215+
216+
/**
217+
* Generates a valid relation if applicable
218+
* This method assumes the related field primary key is id.
219+
*
220+
* @param string $fieldName The field name
221+
*
222+
* @return string
223+
*/
224+
public function getValidRelation($fieldName)
225+
{
226+
$relation = $this->relations[$fieldName]['relation'];
227+
$variable = Str::camel($relation);
228+
229+
return "'".$fieldName."' => ".'$'.$variable.'->id';
230+
}
231+
232+
/**
233+
* Generates a valid text based on applicable model rule.
234+
*
235+
* @param string $rule The applicable model rule.
236+
*
237+
* @return string
238+
*/
239+
public function getValidText($rule = null)
240+
{
241+
if ($rule) {
242+
$max = $this->extractMinMax($rule, 'max') ?? 4096;
243+
$min = $this->extractMinMax($rule, 'min') ?? 5;
244+
245+
if ($max < 5) {
246+
//faker text requires at least 5 characters
247+
return "lexify('?????')";
248+
}
249+
if ($min < 5) {
250+
//faker text requires at least 5 characters
251+
$min = 5;
252+
}
253+
254+
return 'text('.'$this->faker->numberBetween('.$min.', '.$max.'))';
255+
} else {
256+
return 'text';
257+
}
258+
}
259+
260+
/**
261+
* Extracts min or max rule for a laravel model.
262+
*/
263+
public function extractMinMax($rule, $t = 'min')
264+
{
265+
$i = strpos($rule, $t);
266+
$e = strpos($rule, '|', $i);
267+
if ($e === false) {
268+
$e = strlen($rule);
269+
}
270+
if ($i !== false) {
271+
$len = $e - ($i + 4);
272+
$result = substr($rule, $i + 4, $len);
273+
274+
return $result;
275+
}
276+
277+
return null;
278+
}
279+
280+
/**
281+
* Generate valid model so we can use the id where applicable
282+
* This method assumes the model has a factory.
283+
*/
284+
public function getRelationsBootstrap()
285+
{
286+
$text = '';
287+
$uses = '';
288+
foreach ($this->relations as $field => $data) {
289+
$relation = $data['relation'];
290+
$qualifier = $data['model_class'];
291+
$variable = Str::camel($relation);
292+
$model = Str::studly($relation);
293+
$text .= infy_nl_tab(1, 2).'$'.$variable.' = '.$model.'::first();'.
294+
infy_nl_tab(1, 2).
295+
'if (!$'.$variable.') {'.
296+
infy_nl_tab(1, 3).
297+
'$'.$variable.' = '.$model.'::factory()->create();'.
298+
infy_nl_tab(1, 2).'}'.infy_nl();
299+
$uses .= infy_nl()."use $qualifier;";
300+
}
301+
302+
return [
303+
'text' => $text,
304+
'uses' => $uses,
305+
];
306+
}
307+
109308
public function rollback()
110309
{
111310
if ($this->rollbackFile($this->path, $this->fileName)) {

templates/factories/model_factory.stub

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace $NAMESPACE_FACTORY$;
44

55
use $NAMESPACE_MODEL$\$MODEL_NAME$;
66
use Illuminate\Database\Eloquent\Factories\Factory;
7+
$RELATION_USES$
78

89
class $MODEL_NAME$Factory extends Factory
910
{
@@ -21,6 +22,7 @@ class $MODEL_NAME$Factory extends Factory
2122
*/
2223
public function definition()
2324
{
25+
$RELATIONS$
2426
return [
2527
$FIELDS$
2628
];

0 commit comments

Comments
 (0)