How To Design Form To Handle Junction Tables

I have a relation which utilizes a junction table (many to many relationship). For example, take the following models:




Players can play on many teams and teams can have many players.

Goal: I want to display a player form where-by the person can edit all the normal player information (name, age, etc), but also select which teams the player plays for. It should also reflect in the list of teams, which teams they already are shown to play for.

I am struggling with displaying the listBox correctly to accomplish this. I am looking for help on how to code the _form.php file as well as any needed supporting code in the models and controllers.

I think I can figure out the controller portion when posting, but if it is easy, please include that part as well.


I would add this to your form model using viaTable()

See - section "Relations with a junction table"

Post your code that you have so far.

Don’t get too hung up on many:many relationships. They are really no different than one:many except that your relation in the model needs to use the viaTable() construct. A many:many with junction table conceptually is just a pair of hasMany relationships.

So… how do you want to select teams?

I have used a multiselect dropdown checkbox widget, select2, etc. Need more information on what you want.

No matter what solution you end up using, you will end up pulling two sets of data for this widget:

  • A list of selected (saved) items from the database

  • A list of possible items

Player Model (excerpt):

public function getTeams() {

    return $this->hasMany(Team::className(), ['id' => 'team_id'])->viaTable('player_team', ['player_id' => 'id']);


Player Controller (excerpt):

public function actionUpdate($id) {

    $player = $this->findModel($id);

    if ($player->load(Yii::$app->request->post()) && $player->save()) {

        return $this->redirect(['view', 'id' => $player->id]);

    } else {

        $teams = $player->getTeams();

        return $this->render('update', [

            'player' => $player,

            'teams ' => $teams ,




Player Update View (excerpt):

echo $form->field($teams , 'name')->listBox(ArrayHelper::map($teams ->all(), 'id', 'name'), ['size' => 5]);

This all "works" in that the page displays a multi-select field with a list of teams, but the team the viewed player is associated with does not show as selected (highlighted) in the list. That is the ONE piece I am missing.


I haven’t used listbox before …but looking at the API it looks like you are not fully implementing the function sig. The 2nd parameter is $selected which would hold any ‘preselected’ choices.

Thank you, but I am using ActiveForm::ActiveField::listBox, which does not have that parameter. It does have options for setting attributes for each OPTION, but it ignores SELECTED.

I would really appreciate it if someone has a quick model, controller and view example of solving my problem. I’m not sure if there is a “natural” way to get Yii to auto-select the OPTIONs based on the junction table or if you have to jump through some hoops to get them selected.

Here is what I have been able to get working. I have to think there is a better, more elegant way of doing it that is handled by Yii. If anyone knows of a better way of doing this I’d love to hear it!

Player controller – for view (excerpt):

$player     = $this->findModel($id);

$team       = new Team;

$team['id'] = $player->getTeamSelected();

$team_list  = Team::find()->where(['active' => 1])->orderBy('name')->all();

return $this->render('update', [

    'player' => $player,

    'team' => $team,

    'team_list' => $team_list,


Player model (excerpt):

public function getTeamSelected() {

    $selected = [];


    foreach ($this->getPlayerTeam()->all() as $item) {

        $selected[] = $item->team_id;



    return $selected;


Player view _form (excerpt):

$form->field($team, 'id')->listBox(ArrayHelper::map($team_list, 'id', 'name'), ['multiple' => true, 'size' => 5])

Player controller – for update (excerpt):

$player = $this->findModel($id);

if ($player->load(Yii::$app->request->post()) && $player->save()) {

    // Remove all junction table entries so we can repopulate them.

    PlayerTeam::deleteAll('player_id = :id', [':id' => $id]);


    // Get the posted IDs for the link table.

    $teamIds = Yii::$app->request->post()['Team']['id'];

    if (is_array($teamIds)) {

        // Loop through each ID, load the link table record, link to the model and save.

        foreach ($teamIds as $tid) {

            $player_team = PlayerTeam::find()->where(['player_id' => $id, 'team_id' => $tid])->one();

            if (is_null($player_team)) {

                $player_team  = new PlayerTeam;


            $player_team->load(['PlayerTeam' => ['player_id' => $id, 'team_id' => $tid]]);