Doctrine a bezpečný insert unikátních sloupců
- Filip Procházka
- Moderator | 4668
Však to znáte, děláte bezpečnou aplikaci a chcete mít jistotu, že uživatelův nick je unikátní.
Tak mu dáte jednoduchou validaci, ajaxem při psaní a pak ještě jednou, před vložením do databáze, aby se nestalo, že dva uživatelé mají stejný nick. Na sloupec nastavíte unique a máte vyhráno, že? Bohužel nemáte…
Přijde den a on to bude ten, kterej nikdo nebude čekat, že se povede mezi
kontrolou existence záznamu a jeho vložením, nacpat ještě jeden dotaz,
který bude rychlejší, stejného uživatele vytvoří dřív a Doctrine nám
vyhodí PDOException
, Entity Manager se zamkne a je po
prdeli…
Chcete řešení? Tady je
Používá se velice jednoduše
use Kdyby\Doctrine\ORM\Tools\NonLockingUniqueInserter;
$entity = new User();
$entity->email = "filip.prochazka@kdyby.org";
$entity->name = "Filip";
$entity->address = "Starovičky";
$inserter = new NonLockingUniqueInserter($em);
if ($inserter->persist($entity)) {
echo "uživatel byl vložen";
} else {
echo "uživatel s tímto nickem už v databázi existuje";
}
Všechna magie je schována v jedné třídě. Má to ale jedno malinké
ale: neřeší to associace, pokud sloupeček asociace nebude
nullable=TRUE
, tak to prostě umře. Smolík.
Enjoy ;)
// edit:
@**MartinSadovy** poznamenal, že by to chtělo ještě update… upřímně, už se mi nechce :D tak třeba někdy? Nebo kdyby se někomu povedlo mě hecnout :P No a vždycky můžete poslat pull request :)
Ale kdyby tam někdo našel chybku tak tu rád opravím :)
Abych vysvětlil to s těmi asociacemi, protože spousta lidí to vidí jako problém…
Myslel jsem si, že Doctrine u vztahů, jako je @OneToOne
, atd.
vkládá do tabulky sloupec, pro který vyžaduje hodnotu. To by znamenalo, že
asociaci
/** @OneToOne(targetEntity="Info") */
public $info;
by bylo potřeba upravit na
/** @OneToOne(targetEntity="Info") @JoinColumn(nullable=TRUE) */
public $info;
Ale teď jsem si to zkontroloval a Doctríně je to jedno, protože to tak dělá automaticky. Je to v té anotaci jako výchozí hodnota.
Žádný problém s asociacemi tu tedy není (pokud ho nevytvoříte tím,
že nastavíte @JoinColumn(nullable=FALSE)
).
Editoval HosipLan (24. 10. 2011 11:23)
- Patrik Votoček
- Člen | 2221
Nelíbí…
Když jsem vyděl ukázku použití říkal jsem si WOW. Pak přišlo WTF a pak jsem se kouknul na implementaci a řekl no to né…
- spouštíš přímo SQL (testované pouze na MySQL možná na SQLite) když už tak bych použil nějaké to DBAL skládání SQLka.
PDOException::getCode() == 23000
je poněkud obecnější než jenom UNIQUE fail (stejný kód vyhazuje i NOT NULL)
S bodem 2 se pojí problém o kterém jsem s tebou už mluvil ocitují část textu z mého blogpostu:
Pro upřesnění kódem myslím hodnotu:
$e instanceof \PDOException;
$e->getCode() == $e->errorInfo[0] == 23000; // fail
$e->errorInfo[1] == 19; // unique fail
- kód 19 vrací SQLite v MySQL je to 1062, u jiné DB to zase bude jinak
- nestačí ověřovat chybový kód, ale musí se ověřovat i typ DB (v databázi A může chybový kód 19 znamenat něco jíného, než v databázi B)
- Filip Procházka
- Moderator | 4668
- Ano, to jsem si taky říkal. Než jsem se prokousal k tomu, jak entity ukládají persistery. Čili tento způsob je validní.
- Vím o tom, nechtělo mi to fungovat, tak jsem to odstranil. Ty chybové kódy opravím.