Ask your Symfony questions! Pay money and get answers fast! (more info)

Saving drops the relations that are not in the list Symfony

  • SOLVED

Hello!

First of all, sorry for my English. I'm a beginner in Sf, so...

I create a Customer Relationship Management System (as a beginner :D) and I have a problem with relations. There are companies and users in this system.

Some companies have a special flag, they are providers! It means, they can provide some services to 'plain' companies. Therefore I created a many-many relation between companies. In this case when i open the page of a company, I can see a company checkbox list, where I can check off which company is a provider for this company. I made a trick to remove non-provider companies from the list:

$choices = User::getProviders();
$this->widgetSchema['company_list'] = new sfWidgetFormChoice(array('choices' => $choices));


It works perfectly, only providers are shown in the list. BUT! User::getProviders() only returns with those providers where the user and the provider have a relation! It's a but difficult to understand so here is an example:

<strong>SUPERADMIN</strong>
When superadmin opens the page of XY company, User::getProviders() returns with ALL THE PROVIDERS:
PROV1
PROV2
PROV3
Superadmin check them off, so all three providers are checked.

<strong>USER</strong>
When a plain user opens the page of XY company, User::getProviders() returns with only one provider:
PROV1
It is checked, because superadmin checked it off. If now user click on save, PROV2, PROV3 relations will be deleted in the background.

How can I solve this? Every user should be able to modify only those relations that are shown in the list. If User::getProviders() doesn't return with PROV2, PROV3, the system shouldn't deal with them.

Thank you so much in advance!

Answers (4)

2010-07-28

Loban Rahman answers:

Your attached image explained nicely what you are trying to do. Company has a many-to-many relation with it self. But instead of showing the entire list of companies to every user, some users get a limited list. However, you don't want the relations between unshown companies to be removed.

The reason this is happening is because of how symfony forms automatically handle many-to-many relations. When you have a widget for many-to-many relations, processing the form follows the following steps:

(1) Remove all relations
(2) Add back in those relations that have been selected
(3) Save

That's why, if you show only companies 3, 4, and 5, then any relation with 2 will be removed, if it had existed.

What you need to do is customize this code. Open your base form file and look for the doSave() function.


protected function doSave($con = null)
{
$this->saveUsersList($con);
parent::doSave($con);
}

public function saveUsersList($con = null)
{
if (!$this->isValid())
{
throw $this->getErrorSchema();
}

if (!isset($this->widgetSchema['users_list']))
{
// somebody has unset this widget
return;
}

if (null === $con)
{
$con = $this->getConnection();
}

$existing = $this->object->Users->getPrimaryKeys();
$values = $this->getValue('users_list');
if (!is_array($values))
{
$values = array();
}

$unlink = array_diff($existing, $values);
if (count($unlink))
{
$this->object->unlink('Users', array_values($unlink));
}

$link = array_diff($values, $existing);
if (count($link))
{
$this->object->link('Users', array_values($link));
}
}


You'll notice that the doSave method calls a separate saveXXXList() method for every many-to-many widget in that form. And the structure of this method is always around the same as what you see above. Check out the end code. It's getting an array_diff of the exising list and the new list, and unlinking and linking the appropriate relations.

You need to override this function with one that does the exact same thing, but does NOT unlink relations that are not being shown.


Loban Rahman comments:

Suppose u have $shownValues, which is an array of id's that are being shown. So the following:


$unlink = array_diff($existing, $values);


should be changed to


$unlink = array_intersect($shownValues, array_diff($existing, $values));


That way, the list of id's to unlink is restricted to those that are being shown.

2010-07-28

Ben answers:

I have no idea what you mean.


saki comments:

Okay, I try to explain shortly:

There is a modell (company) with a many-many relation to itself. This relation is shown in the forms as a checkbox list. OK. There are two users, A and B. When A logged in, he must be able to see all companies in the checkbox list. However B has a lower permission, so B should be able to see only few companies in the list. So I want to hide/remove some options from the checkbox list when B looks the page. Unfortunately, symfony will delete the relations that are not shown in the list. I attached a picture, perhaps it helps you to understand my problem.

Thank you so much!