Doctrine a bezpečný insert unikátních sloupců

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Filip Procházka
Moderator | 4668
+
0
-

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
+
0
-

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é…

  1. 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.
  2. 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
+
0
-
  1. Ano, to jsem si taky říkal. Než jsem se prokousal k tomu, jak entity ukládají persistery. Čili tento způsob je validní.
  2. Vím o tom, nechtělo mi to fungovat, tak jsem to odstranil. Ty chybové kódy opravím.