Search code examples
phpdoctrinesymfony-formssymfony-1.4doctrine-1.2

sfDoctrineForm - How can i check if the object in an embedded form exists and relate it to the new parent object instead of creating a new one?


Im working on a user registration form that involves 3 different objects, The user, member profile, and member organization. Im trying to embed all these into a single registration form like User > Member > School. The basic implementation of this works just fine with the typical sfDoctrineForm::embedRealtion procedure.

The problem is the organization has a unique index on its member_number, the only time the the supplied value for this wont be in the database is when that user is first person to sign up from their organization. So in this case we will get a vlaidation error (or a key constraint violation if we turn the validation off).

What I want to happen instead, is that i check for the existence of the MemberOrganization with the same member_number in the database (either in a pre/post validator or in updateObject or wherever is appropriate). If it the member number already exists then i want to relate the new MemberProfile to this existing organization instead of linking it to the new one that was submitted, throwing out all the new values for that organization.

Ive tried modifying the object in the organization form via validation but this always results in a organization_id constraint violation coming from the profile:

$object = $this->getObject();
$table = $object->getTable();
$existing = $table->findOneByMemberNumber($values['member_number']);
if($existing)
{
  $members = clone $object->Members;
  $object->assignIdentifier($existing->identifier());
  $object->fromArray($existing->toArray(false), false);

  foreach($members as $member)
  {
    $member->link($object);
  }

  $values = $object->toArray(false); // return only the direct values
}

return $values;

The schema looks something like this:

User:
  columns:
  username: {type: string(100)}
  email: {type: string(255), unique: true}

MemberProfile:
  columns:
    # some none authentication related user details
    organization_id: {type: integer, notull: true}
    user_id: {type: integer, notnull: true}
  relations:
    User:
      local: user_id
      type: one
      foreign: id
      foreignType: one
    MemberOrganization:
      local: orgainization_id
      type: one
      foreign: id
      foreignType: many
      foreignAlias: Members

MemberOrganization:
  columns:
    membership_number: {type: string(255), unique: true}
    # other organization data

Solution

  • So what i ended up doing was overriding bind on the top level form (the one for the User). In this method i check for the existence of the Orgainization and if it exists i attach it to the Member Profile object and then re-embed all the subforms.

    Ideally i would actually do this in the Member form but since the values are only bound at the top level form and then just cascade through the error schema for validation this seems to be a no go. Complete re-embedding seems to be required to get the object associations correct.

    Some sample code (less some sanitizing code on the member number before issuing the query):

      public function linkOrganizationIfExists(array $orgValues)
      {
        $defaultOrg = $this->embeddedForms['member_profile']->embeddedForms['organization']->getObject();
        $table = $defaultOrg->getTable();
    
        if(isset($orgValues['member_number']) 
          && ($existingOrg = $table->findOneByMemberNumber($orgValues['member_number'])))
        {
          $user = $this->getObject();
          $profile = $user->getMemberProfile();
          $profile->Organization = $existingOrg;
    
          // prepare the current values from the db to return
          $orgValues = array_merge($orgValues, array_intersect_key($existingOrg->toArray(false), $orgValues));
    
          $this->embedRelation('MemberProfile as member_profile', 'MemberProfileRegisttrationForm');
        }
    
        return $orgValues;
      }
    
      public function bind(array $taintedValues = null, array $taintedFiles = null)
      {
        if(isset($taintedValues['member_profile']['organization']))
        {
          $taintedValues['member_profile']['organization'] = $this->linkOrganizationIfExists($taintedValues['member_profile']['organization']);
        }
    
        parent::bind($taintedValues, $taintedFiles);
      }