0

In my application I have the following migrations:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateGridTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('grid', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedInteger('width');
            $table->unsignedInteger('height');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('grid');
    }
}
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateRoverTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('rover', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->bigInteger('grid_id')->unsigned();
            $table->string('command');
            $table->foreign('grid_id')->references('id')->on('grid');
            $table->smallInteger('last_commandPos')->unsigned()->default(0);
            $table->smallInteger('grid_pos_x')->unsigned();
            $table->smallInteger('grid_pos_y')->unsigned();
            $table->enum('rotation', App\Constants\RoverConstants::ORIENTATIONS);
            $table->string('last_command');

            Schema::enableForeignKeyConstraints();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('rover');
    }
}

Creating the tables grid and rover explicitly. And I want to populate the data via a factory:

/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\Model\Grid;
use App\Model\Rover;
use App\Constants\RoverConstants;
use Faker\Generator as Faker;

/**
 * Random Command Generator based upon:
 * https://stackoverflow.com/a/13212994/4706711
 * @param integer $length How many characters the wommand will contain.
 * @return string
 */
function generateRandomCommand($length = 10): string {
    return substr(str_shuffle(str_repeat($x=implode('',RoverConstants::AVAILABLE_COMMANDS), ceil($length/strlen($x)) )),1,$length);
}

$factory->define(Grid::class,function(Faker $faker){
    return [
        'width'=>rand(1,10),
        'height'=>rand(1,10)
    ];
});

$factory->define(Rover::class, function(Faker $faker) {
    $command = generateRandomCommand(rand(0));
    $commandLength = strlen($command);
    $commandPos = rand(0,$commandLength);
    $lastExecutedCommand = substr($command,$commandPos,$commandPos);

    $randomGrid=Grid::inRandomOrder();

    return [
        'grid_id' => $randomGrid->value('id'),
        'grid_pos_x' => rand(0,$randomGrid->value('width')),
        'grid_pos_y' => rand(0,$randomGrid->value('height')),
        'rotation' => RoverConstants::ORIENTATION_EAST,
        'command' => $command,
        'last_commandPos' => $commandPos,
        'last_command' => $lastExecutedCommand,
    ];
});

But how I can ensure that the $randomGrid=Grid::inRandomOrder(); will always return a Grid? I other words I want to check is there's no grid then call the Grid Factory to make one from the Rover Factory.

Do you know how I can do that?

Dimitrios Desyllas
  • 9,082
  • 15
  • 74
  • 164
  • 1
    I suggest you always make a new grid. If you want to use an existing grid do that when you are generating `Rover` but not in the factory e.g. `factory(Rover::class)->make([ 'grid_id' => x])` – apokryfos Jun 25 '19 at 09:06

1 Answers1

1

Answer not sufficient, just here for reference!

If you want to be sure there is always a grid there, you can create one (call the GridFactory) at that place. If you want to use an existing grid, you can override this attribute. Like so:

$factory->define(Rover::class, function(Faker $faker) {
    $command = generateRandomCommand(rand(0));
    $commandLength = strlen($command);
    $commandPos = rand(0,$commandLength);
    $lastExecutedCommand = substr($command,$commandPos,$commandPos);

    //This will create a second entry in the database.
    $randomGrid = factory(Grid::class)->create();

    return [
        'grid_id' => $randomGrid->id, //no need for the value() method, they are all attributes
        'grid_pos_x' => rand(0,$randomGrid->width), 
        'grid_pos_y' => rand(0,$randomGrid->height),
        'rotation' => RoverConstants::ORIENTATION_EAST,
        'command' => $command,
        'last_commandPos' => $commandPos,
        'last_command' => $lastExecutedCommand,
    ];
});

Above factory shows a grid being created on the fly whenever the RoverFactory is called. Creating a new rover with factory(Rover::class)->create() will also create a new Grid (and save it). Now if you want to use an existing grid, you can do the following:

factory(Rover::class)->create([
    'grid_id' => $existingGrid->id,
    'grid_pos_x' => rand(0, $existingGrid->width),
    'grid_pos_y' => rand(0, $existingGrid->height),
]);

This will create a Rover with an existing grid (which you might have created before with the GridFactory or something. Hope this works for you. Enjoy Laravel!

Edit:

Normally you would do something like this:

$factory->define(Rover::class, function(Faker $faker) {
    $command = generateRandomCommand(rand(0));
    $commandLength = strlen($command);
    $commandPos = rand(0,$commandLength);
    $lastExecutedCommand = substr($command,$commandPos,$commandPos);

    return [
        'grid_id' => function() {
            return factory(Grid::class)->create()->id;
        }
        'grid_pos_x' => '', //But then i got nothing for this.
        'grid_pos_y' => '', //But then i also got nothing for this.
        'rotation' => RoverConstants::ORIENTATION_EAST,
        'command' => $command,
        'last_commandPos' => $commandPos,
        'last_command' => $lastExecutedCommand,
    ];
});
Fjarlaegur
  • 1,395
  • 1
  • 14
  • 33
  • But in this approach won't I have 2 Grids in my database? In this case how I can 'disable' the creation of a grid in a factory? Can I pass ectra flags and parasmeters into the factory? – Dimitrios Desyllas Jul 02 '19 at 07:52
  • You are right. I will edit my answer. I can not think of a proper solution for your problem at the moment. Excuse me. You may want to decline my answer as it does not solve your problem sufficiently. I don't have any knowledge of passing extra parameters and or flags to the factories other than overriding the amount to create and the attributes to override. Maybe checkout the Laravel documentation. – Fjarlaegur Jul 02 '19 at 13:02
  • Also you can pass a callback function that returns the `grid_id` as seen in [documentation](https://stackoverflow.com/q/56749976/4706711). So in case of an ovveride it won't be called. – Dimitrios Desyllas Jul 03 '19 at 09:03
  • That is what i added in my edited part of my answer. There i used the correct way of passing a callback function. But then i dont know how you would pass the `grid_pos_x` and `grid_pos_y` variables. – Fjarlaegur Jul 03 '19 at 09:17
  • What if I let the closure access some variables outside closure's scope in order to temrporary store the generated grid? – Dimitrios Desyllas Jul 03 '19 at 09:21
  • Als as you can see I can also use closures and search for the grid tha thas `grid_id`. As you see in https://laravel.com/docs/5.8/database-testing#using-factories you can access the model's attributed via the array `$rover`. – Dimitrios Desyllas Jul 03 '19 at 09:24