Can't get consistant results
I am creating a factory for my new move model. I want to have it, so it can generate x types of moves. In my case I have the seeder generate 3 offensive moves and 3 defensive moves:
MoveSeeder.php
<?php
namespace Database\Seeders;
use App\Models\Move;
use Database\Factories\MoveFactory;
use Illuminate\Database\Seeder;
class MoveSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// reset the counter
MoveFactory::resetInstance();
// Create offensive moves
Move::factory()->count(3)->offensive()->create();
// reset the counter
MoveFactory::resetInstance();
// Create corresponding defensive move
Move::factory()->count(3)->defensive()->create();
}
}
Here is my factory:
<?php
namespace Database\Factories;
use App\Enumerations\MoveType;
use App\Models\Move;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Log;
/**
* @extends Factory<Move>
*/
class MoveFactory extends Factory
{
// static variable to keep track if this is the 1st rank instance seeded
protected static bool $firstInstance = true;
// Static variables to keep track of move order for each type
protected static int $orderCounter = 0;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
// Random offensive move array
$randomOffensiveMoves = ['elbow strike', 'hook', 'jab', 'overhand punch', 'palm strike', 'spear hand', 'uppercut'];
// Random defensive move array
$randomDefensiveMoves = ['cross block', 'inside block', 'knife hand block', 'leg block', 'outside block', 'palm block'];
// Determine the move type
$type = $this->faker->randomElement([MoveType::OFFENSIVE, MoveType::DEFENSIVE]);
// if this is the first rank instance
if(self::$firstInstance) {
Log::info("First Instance = True");
// and set firstInstance to false for the next instance
self::$firstInstance = false;
Log::info("First Instance = Flipped False");
} else {
self::$orderCounter++;
Log::info("First Instance = False");
}
// Set the name based on the move type
if ($type == MoveType::OFFENSIVE) {
$name = $this->faker->randomElement($randomOffensiveMoves);
Log::info("Type = Offensive");
} else {
$name = $this->faker->randomElement($randomDefensiveMoves);
Log::info("Type = Defensive");
}
Log::info("Name = $name");
return [
'name' => $name,
'order' => self::$orderCounter,
'move_type' => $type->value,
];
}
// Method to reset the instance
public static function resetInstance(): void
{
self::$firstInstance = true;
self::$orderCounter = 0;
}
public function offensive(): MoveFactory|Factory
{
return $this->state([
'move_type' => MoveType::OFFENSIVE->value,
]);
}
public function defensive(): MoveFactory|Factory
{
return $this->state([
'move_type' => MoveType::DEFENSIVE->value,
]);
}
}
Here is my MoveType enum:
<?php
namespace App\Enumerations;
// enumerations for the move type
enum MoveType: int
{
/** the move is an offensive one */
case OFFENSIVE = 0;
/** the move is an defensive one */
case DEFENSIVE = 1;
}
Here is my move migration:
<?php
use App\Enumerations\MoveType;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('moves', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->unsignedTinyInteger('order');
$table->unsignedTinyInteger('move_type')->default(MoveType::OFFENSIVE);
$table->timestamps();
});
// get the move types constants
$move_type_offensive = MoveType::OFFENSIVE->value;
$move_type_defensive = MoveType::DEFENSIVE->value;
// Add the CHECK constraint
DB::statement("ALTER TABLE moves ADD CONSTRAINT moves_is_valid_type CHECK (move_type IN ($move_type_offensive, $move_type_defensive))");
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('moves');
}
};
The issue is what I am logging for debugging is different then what the database is showing me:
Log:
[2024-08-19 21:20:04] local.INFO: First Instance = True
[2024-08-19 21:20:04] local.INFO: First Instance = Flipped False
[2024-08-19 21:20:04] local.INFO: Type = Defensive
[2024-08-19 21:20:04] local.INFO: Name = knife hand block
[2024-08-19 21:20:04] local.INFO: First Instance = False
[2024-08-19 21:20:04] local.INFO: Type = Offensive
[2024-08-19 21:20:04] local.INFO: Name = palm strike
[2024-08-19 21:20:04] local.INFO: First Instance = False
[2024-08-19 21:20:04] local.INFO: Type = Offensive
[2024-08-19 21:20:04] local.INFO: Name = spear hand
[2024-08-19 21:20:04] local.INFO: First Instance = True
[2024-08-19 21:20:04] local.INFO: First Instance = Flipped False
[2024-08-19 21:20:04] local.INFO: Type = Offensive
[2024-08-19 21:20:04] local.INFO: Name = jab
[2024-08-19 21:20:04] local.INFO: First Instance = False
[2024-08-19 21:20:04] local.INFO: Type = Offensive
[2024-08-19 21:20:04] local.INFO: Name = palm strike
[2024-08-19 21:20:04] local.INFO: First Instance = False
[2024-08-19 21:20:04] local.INFO: Type = Offensive
[2024-08-19 21:20:04] local.INFO: Name = overhand punch
And what the database shows:
|_. id |_. name |_. order |_. move_type |_. created_at |_. updated_at |
| 1 | knife hand block | 0 | 0 | 2024-08-19 21:20:04 | 2024-08-19 21:20:04 |
| 2 | palm strike | 1 | 0 | 2024-08-19 21:20:04 | 2024-08-19 21:20:04 |
| 3 | spear hand | 2 | 0 | 2024-08-19 21:20:04 | 2024-08-19 21:20:04 |
| 4 | jab | 0 | 1 | 2024-08-19 21:20:04 | 2024-08-19 21:20:04 |
| 5 | palm strike | 1 | 1 | 2024-08-19 21:20:04 | 2024-08-19 21:20:04 |
| 6 | overhand punch | 2 | 1 | 2024-08-19 21:20:04 | 2024-08-19 21:20:04 |
Note how the move_type field is incorrect, knife hand block
is defensive(1), palm strike is offensive(0) which matches the debug log but, not what's in the database. Thanks for your time.
Hey!
As far as I can tell, the main issue here is that the definition()
method in your MoveFactory is randomly determining the move type, which is then being overridden by the offensive()
and defensive()
methods called in the seeder. This leads to inconsistencies between what's logged and what's actually saved to the database.
Here's how you might be able to fix this:
- Remove the random type selection from the
definition()
method. - Make the
offensive()
anddefensive()
methods set both the type and the name. - Use a separate counter for offensive and defensive moves.
Start by first updating your MoveFactory
:
<?php
namespace Database\Factories;
use App\Enumerations\MoveType;
use App\Models\Move;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Log;
/**
* @extends Factory<Move>
*/
class MoveFactory extends Factory
{
protected static int $offensiveCounter = 0;
protected static int $defensiveCounter = 0;
// Random offensive move array
protected static array $randomOffensiveMoves = ['elbow strike', 'hook', 'jab', 'overhand punch', 'palm strike', 'spear hand', 'uppercut'];
// Random defensive move array
protected static array $randomDefensiveMoves = ['cross block', 'inside block', 'knife hand block', 'leg block', 'outside block', 'palm block'];
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => '', // Will be set in offensive() or defensive()
'order' => 0, // Will be set in offensive() or defensive()
'move_type' => MoveType::OFFENSIVE->value, // Default, will be overridden if necessary
];
}
public static function resetInstance(): void
{
self::$offensiveCounter = 0;
self::$defensiveCounter = 0;
}
public function offensive(): MoveFactory|Factory
{
return $this->state(function (array $attributes) {
$name = $this->faker->randomElement(self::$randomOffensiveMoves);
Log::info("Creating Offensive Move: $name, Order: " . self::$offensiveCounter);
return [
'name' => $name,
'order' => self::$offensiveCounter++,
'move_type' => MoveType::OFFENSIVE->value,
];
});
}
public function defensive(): MoveFactory|Factory
{
return $this->state(function (array $attributes) {
$name = $this->faker->randomElement(self::$randomDefensiveMoves);
Log::info("Creating Defensive Move: $name, Order: " . self::$defensiveCounter);
return [
'name' => $name,
'order' => self::$defensiveCounter++,
'move_type' => MoveType::DEFENSIVE->value,
];
});
}
}
Key changes:
- Removed the random type selection from
definition()
. - Created separate counters for offensive and defensive moves.
- Updated
offensive()
anddefensive()
methods to set the name, order, and type. - Added more detailed logging.
After that update your MoveSeeder
so it's using these new methods correctly:
<?php
namespace Database\Seeders;
use App\Models\Move;
use Database\Factories\MoveFactory;
use Illuminate\Database\Seeder;
class MoveSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// reset the counter
MoveFactory::resetInstance();
// Create offensive moves
Move::factory()->count(3)->offensive()->create();
// Create defensive moves
Move::factory()->count(3)->defensive()->create();
}
}
With these changes, you should now see consistent results between your logs and the database. The offensive moves will have move_type = 0
and the defensive moves will have move_type = 1
, and the order will be correct for each type.
To test this, you can run your seeder and check both the logs and the database. The logs should now show something like:
Creating Offensive Move: jab, Order: 0
Creating Offensive Move: palm strike, Order: 1
Creating Offensive Move: uppercut, Order: 2
Creating Defensive Move: cross block, Order: 0
Creating Defensive Move: inside block, Order: 1
Creating Defensive Move: palm block, Order: 2
And your database should reflect these exact moves and orders.
If you're still experiencing issues after making these changes, please let me know, and we can further debug the problem.
- Bobby