在Laravel构造函数中将模型链接到父级

Maybe I'm searching it all wrong but I haven't been able to figure out an answer.. Say I have a model Building, which always has n Floor(s)

I would like to write a constructor for Building, in which I could specify a number of Floor(s) to be created. The problem is that I can't link back a Floor to the Building because when the constructor for Building is being called, it doesn't have a primary key yet...

Basically, my code looks like this but doesn't work:

class Building extends Model {
  public function __construct($nbFloors) {
    for($i=0; $i<$nbFloors; $i++) {
       $foo = new Floor();
       $foo->building_id = $this->id;
       $foo->save();
    }
  }
}

What would be the correct solution to achieve something like that?

The primary key will never be available in the constructor and your constructor's definition is not compatible with Model which expects an array of attributes as the first argument.

You're performing too much logic in your constructor, a constructor is meant to just instantiate an object and its dependencies, not perform business logic. By doing this in your constructor, you're actually going to be attempting to create new floors EVERY time your Model is instantiated which includes when your model is retrieved from the database.

I'd recommend adding a new method like:

public function createWithFloors($n) {
    $this->save();
    ...
}

Now, you can use the model as it's expected and call the create method:

$building = new Building(['name' => 'Empire State']);
$building->createWithFloors(102);

since, you can't bind Floor to a building that is not created yet, you should make the "new floors number" an attribute of the Building instance. Then you overload the save method to create the new floors.

class Building extends Model {
     /** number of floors to be created on save
     * @var int
     */
    private $newFloorsCount;

    /**
     * Building constructor.
     * @param array $attributes
     * @param int $nbFloors
     */
    public function __construct(array $attributes = [], $nbFloors = 0) {
        parent::__construct($attributes);
        $this->newFloorsCount = $nbFloors;
    }

    /**
     * @param array $options
     * @return bool
     */
    public function save(array $options = [])
    {
        $return = parent::save($options);
        for($i=0; $i<$this->newFloorsCount; $i++) {
            $foo = new Floor();
            $foo->building_id = $this->id;
            $foo->save();
        }
        return $return;
    }
}

now you can just do

$building = new Building([],5);
$building->save();

Besides the solutions already suggested, you could create an event that is fired when a Building is created. A listener could then store your Floors. For event reference, have a look at the documentation.

First, create an event called BuildingCreated with php artisan make:event BuildingCreated and use below code:

namespace App\Events;

use App\Building;

use Illuminate\Queue\SerializesModels;

class BuildingCreated extends Event
{
    use SerializesModels;

    public $building;

    public function __construct(Building $building)
    {
        $this->building = $building;
    }
}

Then, register the event within your Building model:

use App\Events\BuildingCreated;

class Building
{
    protected $dispatchesEvents = [
        'created' => BuildingCreated::class,
    ];
}

Next, you will need a listener that creates the floors. Create it with php artisan make:listener AddFloorsToNewBuilding and adapt it as you need:

namespace App\Listeners;

use App\Building;
use App\Events\BuildingCreated;

class AddFloorsToNewBuilding
{
    public function handle(BuildingCreated $event)
    {
        $floors = ...;

        $event->building->floors()->saveMany($floors);
        $event->building->save();
    }
}

Lastly, have the listener listen for the event by adding it to the $listen array in the EventServiceProvider:

class EventServiceProvider
{
    protected $listen = [
        \App\Events\BuildingCreated::class => [
            \App\Listeners\AddFloorsToNewBuilding::class,
        ],
    ];
}