Replicator – jak nejlépe uložit data
- kolsi
- Člen | 131
Ahoj,
mám ve formuláři replikátor:
$contactTypes = $this->contactRepository->getContactTypes();
$replicator = $form->addDynamic("contacts", function (Container $container) use ($contactTypes, $self) {
$container->addHidden('id');
$container->addSelect('type_id', 'Type', $contactTypes);
$container->addText('value', 'Value');
$container->addSubmit('remove', 'Remove')
->setAttribute('class', 'ajax remove')
->setValidationScope(FALSE)
->addRemoveOnClick(function ($companies, $container) use($self) {
$self->invalidateControl('contacts');
});
}, 0);
// tlačítko pro přidání nového kontaktu
$replicator->addSubmit('add', 'Add new contact')
->setAttribute('class', 'ajax add')
->setValidationScope(FALSE)
->addCreateOnClick(function ($replicator, $container) use ($self) {
$self->invalidateControl('contacts');
});
jehož data nyní potřebuji uložit. A tak se ptám, kterou byste doporučili strategii na uložení těchto dat do DB. To, co jsem často viděl, bylo, že položky se vkládaly/odebíraly z DB přímo v handleru daných tlačítek. Což já ale nechci, já je chci uložit až po odeslání formuláře stiskem tlačítka SAVE. V té době však ale už nevím, které položky jsou nové, které smazané a které pouze aktualizované. A tak mě napadají dvě strategie:
- nejdříve všechny existující položky z DB smazat, a pak je tam prostě vložit znovu, tak jak přijdou ve $form->getValues(). Něco jako mám tady:
Nevýhodu vidím v tom, že se u všech položek přečíslujou ID. Výhodou by mohla být rychlost a krátký kód.
// uložení společností
$this->personCompanyRepository->removePersonCompanies($this->id);
foreach ($values->companies as $company) {
$this->personCompanyRepository->addCompanyPerson($company->id, $this->id);
}
- ve „for“ cyklu postupně kontrolovat, jestli je daná položka v DB, pokud ano pak aktualizovat, jinak přidat. V dalším „for“ cyklu zkontrolovat, které položky ve $form->getValues() chybí a ty z DB smazat.
Výhoda je v tom, že ID zůstávají, ale kód je o dost delší, obsahuje 2 cykly a spoustu dotazů do DB:
// uložení kontaktů
$current_contacts = $this->personRepository->getContacts($private_data->id);
foreach ($values->contacts as $index => $contact) {
if (empty($contact->id)) { // přidání nového kontaktu
$this->personRepository->addContact($private_data->id, $contact->type_id, $contact->value);
} else { // editace již existujícího kontaktu
$current_contact = $current_contacts->get($contact->id);
if ($current_contact->type_id != $contact->type_id || $current_contact->value != $contact->value) {
$current_contact->update(array('type_id' => $contact->type_id, 'value' => $contact->value));
}
}
}
// ještě vymažeme smazané kontakty
foreach ($current_contacts as $current_contact) {
// tohle zatím nefunguje dobře, protože klíče ve $values->contacts nemusí odpovídat ID v DB, a tak zřejmě bude chtít další sekvenční vyhledávání :(
if (!isset($values->contacts[$current_contact->id])) {
$current_contact->delete();
}
}
Napadá vás ještě nějaká jiná možnost nebo jak byste řešili vy, že se změny do DB nezapíšou už při stisku Přidat/Odebrat, ale až po odeslání kompletního formuláře?
- voda
- Člen | 561
Ahoj,
načti si nejdřív všechny kontakty z db, porovnej s údaji z formuláře (můžeš použít třeba arrray_diff) a staré položky z db smaž jedním delete dotazem, nové položky (nebo upravené) vlož do db.
- kolsi
- Člen | 131
Díky, takže v podstatě nějak takto?
// uložení kontaktů
$current_contacts = $this->personRepository->getContacts($private_data->id);
$result = array_udiff($current_contacts->fetchPairs('id'), (array)$values->contacts, function($a, $b) { return $a->id - $b->id; });
if(!empty($result)) {
$current_contacts->where('id', $result)->delete();
}
foreach ($values->contacts as $index => $contact) {
if (empty($contact->value))
continue;
if (empty($contact->id)) { // přidání nového kontaktu
$this->personRepository->addContact($private_data->id, $contact->type_id, $contact->value);
} else { // editace již existujícího kontaktu
$current_contacts = $this->personRepository->getContacts($private_data->id); // aby nešlo podstrčit ID cizí osoby
$current_contacts->where('id', $contact->id)->update(array('type_id' => $contact->type_id, 'value' => $contact->value));
}
}
- kolsi
- Člen | 131
Já nevím, jestli jsem to dobře pochopil, ale myslel jsem, že to funguje tak, že ono to stejně žádná data nenačte, ale teprve až když zavolám to update(…), tak se vytvoří dotaz do DB ze všech předchozích zřetězených where(…).
Ale jak koukám na výpis dotazů, tak to skutečně nejdřív udělá SELECT, a pak UPDATE. Zajímavé je, že bez update(…) se nevytvoří ani jeden.
SELECT *
FROM `pd2_crm_person_contact`
WHERE (`person_id` = ?) AND (`id` = ?)
parametry: 3, 40
UPDATE `pd2_crm_person_contact`
SET `type_id`='2', `value`='fgbfgbfgbfg'
WHERE (`person_id` = 3) AND (`id` = '40')
metoda getContacts je jenom
public function getContacts($company_id) {
return $this->connection->table("crm_person_contact")->where("person_id", $company_id);
}
Takže ono se to zřetězí, všechno provede až v době update(…), ale stejně se rozdělí na 2 dotazy SELECT+UPDATE.
Editoval kolsi (11. 7. 2014 9:53)
- kolsi
- Člen | 131
Tak v poho, ten SELECT navíc byl způsoben funkcí v našem v systému.
Zkusil jsem tu funkci udělal víc obecně, abych jí nemusel psát u každého formuláře, kde použiju Replicator.
public function updateDynamicData($currentData, array $newData, $addCallback, $updateCallback = null, $key_name = "id") {
$current = $currentData->fetchPairs($key_name);
$deleted = array_udiff($current, $newData, function($a, $b) use ($key_name) { return ($a->$key_name) - $b->$key_name; });
$added = array_udiff($newData, $current, function($a, $b) use ($key_name) { return ($a->$key_name) - $b->$key_name; });
// přidání nových položek
foreach($added as $newItem) {
$addCallback($newItem);
unset($newData[$newItem->id]); // aby se při aktualizaci zbytečně nepletly nové položky
}
// smazání starých položek
if(!empty($deleted)) {
$_currentData = clone $currentData;
$_currentData->where($key_name, array_keys($deleted))->delete();
}
// aktualizace
if($updateCallback) {
foreach ($newData as $index => $item) {
$_currentData = clone $currentData;
$updateCallback($_currentData->wherePrimary($item->$key_name), $item);
}
}
}
Callbacky na update a insert by asi ani nebyly třeba a určitě by šlo
vkládat rovnou z $newData (a název tabulky vytáhnout z $currentData), ale
takhle to bylo rychlejší na napsání :)
Použití pak např.
// uložení společností
$id = $this->id;
$this->personCompanyRepository->updateDynamicData($this->personCompanyRepository->getPersonCompanies($id, false), (array)$values->companies,
function($added) use ($form, $id) { $form->presenter->context->personCompanyRepository->addCompanyPerson($added->company_id, $id); },
NULL, "company_id"
);
nebo
// uložení kontaktů
$this->personRepository->updateDynamicData($this->personRepository->getContacts($private_data->id), (array)$values->contacts,
function($added) use ($form, $private_data) { $form->presenter->context->personRepository->addContact($private_data->id, $added->type_id, $added->value); },
function($current, $newData) { $current->where("type_id <> ? OR value <> ?", array($newData->type_id, $newData->value))->update(array('type_id' => $newData->type_id, 'value' => $newData->value)); }
);