Symfony Generator Forms, Doctrine, and M:N Relatio

2019-03-04 10:39发布

问题:

I have a basic M:N setup with three tables: candidate, position, and candidate_position.

Here's a screenshot of the ERD from MySQL Workbench

Now, moving on from that let's talk about forms. In the default world of symfony generator, you'd have a separate CRUD interface for all three of these tables. However, I don't want to have a CRUD interface for candidate_position.

What I want, is for the create and edit actions of the Candidate interface to contain a multi-choice field (select list, checkbox array, whatever) that would create the CandidatePosition records as part of the Candidate actions.

On to the code

config/doctrine/schema.yml (Note: the is not the entire schema.yml, but only the tables discussed here)

---
detect_relations: true
options:
  type: InnoDB

candidate:
  columns:
    id:
      type: integer(4)
      primary: true
      unsigned: true
      notnull: true
      autoincrement: true
    first_name:
      type: string(45)
      notnull: true
    last_name:
      type: string(45)
      notnull: true
    created_at:
      type: integer(4)
      unsigned: true
  relations:
    Positions:
      class: Position
      refClass: CandidatePosition
      local: candidate_id
      foreign: position_id

position:
  columns:
    id:
      type: integer(4)
      primary: true
      unsigned: true
      notnull: true
      autoincrement: true
    name:
      type: string(45)
  relations:
    Candidates:
      class: Candidate
      refClass: CandidatePosition
      local: position_id
      foreign: candidate_id


candidatePosition:
  tableName: candidate_position
  columns:
    candidate_id:
      type: integer(4)
      primary: true
      unsigned: true
      notnull: true
    position_id:
      type: integer(4)
      primary: true
      unsigned: true
      notnull: true
  indexes:
    fk_candidate_position_candidate1:
      fields: [candidate_id]
    fk_candidate_position_position1:
      fields: [position_id]

apps/backend/modules/candidate/config/generator.yml

generator:
  class: sfDoctrineGenerator
  param:
    model_class:           Candidate
    theme:                 admin
    non_verbose_templates: true
    with_show:             false
    singular:              ~
    plural:                ~
    route_prefix:          candidate
    with_doctrine_route:   true
    actions_base_class:    sfActions

    config:
      actions: ~
      fields:  
        first_name: { label: First Name }
        last_name:  { label: Last Name }
        created_at: { label: Created On }
        candidate_positions:  {label: Positions}
      list:    
        sort:  [last_name, asc]
      filter:  ~
      form:    
        display:
          "User": [first_name, last_name]
          "Applying For": [candidate_positions]
        fields :
          hide:  [created_at]
      edit:    ~
      new:     ~

lib/form/doctrine/candidateForm.class.php

class candidateForm extends BasecandidateForm
{
  public function configure()
  {
    unset( $this['created_at'] );

    $this->widgetSchema['candidate_positions'] = new sfWidgetFormDoctrineChoice(
      array( 'multiple' => true, 'model' => 'Position', 'renderer_class' => 'sfWidgetFormSelectCheckbox' )
    );

    $this->validatorSchema['candidate_positions'] = new sfValidatorDoctrineChoice(
      array( 'multiple' => true, 'model' => 'Position', 'min' => 1 )
    );
  }
}

This all works, except when it comes to actually saving the data. This is the point where I get stuck.

I clearly need to do something to create/edit/delete the CandidatePosition records, but I'm not sure where to start working. In candidateActions? Override Basecandidate::save()?

Let me know if there's any other data you might need to see.

  • PHP 5.2.x
  • symfony 1.4.3

回答1:

About 10 month ago I had similar trouble with M:N relationships and meta-data in the join table.

I found those blog entries of melikedev very useful! This is not exactly the same as your use case, but it might give you some hints:

http://melikedev.com/2009/12/09/symfony-w-doctrine-saving-many-to-many-mm-relationships/

http://melikedev.com/2009/12/06/symfony-sfformextraplugin-select-double-list-maintain-order/

http://melikedev.com/2010/04/06/symfony-saving-metadata-during-form-save-sort-ids/



回答2:

first of all I can suggest an upgrade of symfony version - I use 1.4.11 where you'll have this functionality working from scratch.

If this is not possible in your case, the best place to this will be to override the base doSave() method in candidateForm like this:

protected function doSave($con = null)
{
    $existing = $this->object->Position->getPrimaryKeys();
    $values = $this->getValue('candidate_positions');

    $unlink = array_diff($existing, $values);
    $this->object->unlink('Position', array_values($unlink));

    $link = array_diff($values, $existing);
    $this->object->link('Position', array_values($link));

    parent::doSave($con);
}

and also you'll probably need to manually set the selected link object on form load like this:

public function updateDefaultsFromObject()
{
    parent::updateDefaultsFromObject();

    if (isset($this->widgetSchema['candidate_positions']))
    {
        $this->setDefault('candidate_positions', $this->object->Position->getPrimaryKeys());
    }
}

This should do the trick.

UPDATE

I think that since the update to 1.4.11 doesn't help, there is some problem with the schema definition and my guess is that you need to add relation definition to link table 'candidatePosition' like this:

  relations:
    Candidate:
      class: Candidate
      local: candidate_id
      foreign: id
      foreignAlias: Candidates
    Position:
      class: Position
      local: position_id
      foreign: id
      foreignAlias: Positions

Hope this helps.

Regards.