Cake PHP选择下拉菜单

I'm newbie with CakePHP and need some help. I baked my first application but I'm not able to make it behave as I want.

I have two tables, players and teams, and a join table players_teams. The form displays fine. But I would like to change the select area to a drop down menu. The player could have many teams over time (I added a from and a to date columns to the join table for that), but I would like the player to be assigned to one team at the moment it is created.

Here is what I have tried:

<?php
echo $this->Form->input('player_name');
        echo $this->Form->input('player_last_name');
        echo $this->Form->input('player_number');
        echo $this->Form->input('player_birthday');
        echo $this->Form->input('teams._ids', [ 'multiple' => false, 'options' => $teams,]);
    ?>

It does not change to a dropdow. I have also tried this:

echo $this->Form->input('teams._ids',  ['type' => 'select', 'multiple' => false, 'options' => $teams, 'empty' => true]);

This last one change the select to a dropdown, but whatever I select doesn't go to the database.

Heres the Controller for Players:

public function add()
{
    $player = $this->Players->newEntity();
    if ($this->request->is('post')) {
        $player = $this->Players->patchEntity($player, $this->request->data);
        if ($this->Players->save($player)) {
            $this->Flash->success(__('The player has been saved.'));
            return $this->redirect(['action' => 'index']);
        } else {
            $this->Flash->error(__('The player could not be saved. Please, try again.'));
        }
    }
    $teams = $this->Players->Teams->find('list', ['limit' => 200]);
    $this->set(compact('player', 'teams'));
    $this->set('_serialize', ['player']);
}

Here's the PlayersTeamController:

public function add()
{
    $playersTeam = $this->PlayersTeams->newEntity();
    if ($this->request->is('post')) {
        $playersTeam = $this->PlayersTeams->patchEntity($playersTeam, $this->request->data);
        if ($this->PlayersTeams->save($playersTeam)) {
            $this->Flash->success(__('The players team has been saved.'));
            return $this->redirect(['action' => 'index']);
        } else {
            $this->Flash->error(__('The players team could not be saved. Please, try again.'));
        }
    }
    $players = $this->PlayersTeams->Players->find('list', ['limit' => 200]);
    $teams = $this->PlayersTeams->Teams->find('list', ['limit' => 200]);
    $this->set(compact('playersTeam', 'players', 'teams'));
    $this->set('_serialize', ['playersTeam']);
}

Any help will be really appreciated. Thanks!

The Cakebook: 3.0

Have you tried:

echo $this->Form->select(
  'teams'
  ,$teams->toArray()
  ,[
    'multiple' => false,
    'class' => '_ids',
    'empty' => true
  ]
);

Just resemble the required data structure yourself, that is

[
    'teams' => [
        '_ids' => [
            // single id here
        ]
    ]
]

Cookbook > Database Access & ORM > Saving Data > Converting BelongsToMany Data

Proper, array-ish element name

You can do that by for example specifying the proper element name attribute value, like

echo $this->Form->input(
    'teams',
    [
        'name' => 'teams[_ids][0]'
        //'options' => $teams // this is optional when following the conventions
    ]
);

which will automatically choose a select type, based on the options.

You will need some additional logic to prevent further selections to be injected, as an array-ish name like teams[_ids][0] would allow that without the Security component being able to detect such tampering. This can be done using validation, or by preparing the data in the Model.beforeMarshal event.

Here's a validation example

public function validationCreate(Validator $validator) {
    $validator = $this->validationDefault($validator);

    $teamsValidator = (new Validator())
        ->requirePresence('_ids')
        ->notEmpty('_ids')
        ->add('_ids', 'valid', [
            'rule' => function($value) {
                return is_array($value) && count($value) === 1;
            }
        ]);

    $validator
        ->requirePresence('teams')
        ->notEmpty('teams')
        ->addNested('teams', $tagsValidator);

    return $validator;
}
$player = $this->Players->patchEntity($player, $this->request->data, [
    'validate' => 'create'
]);

Custom, single value element name

Another options would be to specify a non array-ish name, combined with preparing the data in the Model.beforeMarshal event, like

echo $this->Form->input('teams', ['name' => 'initial_team']);
public function beforeMarshal(Event $event, \ArrayObject $data, \ArrayObject $options)
{
    if (isset($data['initial_team'])) {
        $data['teams'] = [
            '_ids' => [
                $data['initial_team']
            ]
        ];
    }
}

Cookbook > Database Access & ORM > Saving Data > Modifying Request Data Before Building Entities

Note

Both will retain the CakePHP magic like the marshaller converting the passed _ids into entities so that the associations are being saved properly, automatically picking up the input options, automatically choosing the input type, etc...

If you have made proper associations, you can save directly from the PlayersController. PlayersTeamController isn't needed at all. When you patch the newly created player entity with the request data, associated table data will be automatically get into it and saved by the entity save() command as its one level of association only.