Ověření, zda username již existuje

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Maxell92
Člen | 38
+
0
-

Zdravím, potřebuji ověřit, zda zadaný email uživatele v db již neexistuje. Nette Database mi ale kumuluje podmínky a nevím, jak to obejít. Zkrácený kód:

<?php
//zpracovani formuláře
public function adminUpdateFormSubmitted(Form $form) {
 $this->admins->updateAdmin($form->getValues(), $this->admin->id);
 ...
}

//model
public function updateAdmin($data, $id) {
 if ( $this->isExist($data->email, $id) ) {
  return "Tento e-mail je již registrovaný!";
 }
...
}

public function isExist($email, $id) {
 $this->where(array("email" => $email))->where("NOT id", $id)->fetch();
...
}
?>

Problém je, že se spustí tento SQL kód:
SELECT * FROM admin WHERE (id = ?) AND (email = ?) AND (NOT id = ?)

První podmínka na id zůstala od chvíle, kdy jsem vybíral záznam k editaci.

Poradíte?

redhead
Člen | 1313
+
0
-

Vypadá to na špatně navržený model – od čeho ten model dědíš?

David Matějka
Moderator | 6445
+
0
-

vypada to, ze dedis od Nette\database\table\selection, ze? a pouzivas stale jednu instanci.
nejlepsi by bylo, kdyby to nededilo od nette\database\selection, ale model mel jako parametr connection s pripojenim na databazi (nette\database\connection) a metodu getSelection, ktera vytvori novou instanci selection s prislusnym spojenim a nazvem tabulky. a pak budes pouzivat treba $this->getSelection()->where…
pripadne, pokud se ti to nechce moc predelavat, tak upravit tu metodu isExist na zpusob jako $this->connection->table(„nazev_tabulky“)->where… coz taky vytvori novou instanci selection

castamir
Člen | 629
+
0
-

To, co používáš je služba – ta si vytvoří jednu instanci a tu pak použije při dalším volání. To, co potřebuješ, je továrnička, která si vytváří vždy novou instanci.

Pro tabulku „tabulka“ uděláš továrničku třeba takto:

Tabulka.php (třída, kterou můžeš uložit třeba do /app/model nebo /app/factories či jinam do /app/ to je úplně jedno.

class Tabulka extends Nette\Database\Table\Selection {

    public function __construct(Nette\Database\Connection $connection) {
        parent::__construct('tabulka', $connection);
    }

    public function updateAdmin($data, $id) {
        ...
    }

    public function isExist($email, $id) {
        ...
    }

}

v config.neon

factories:
    tabulka: Tabulka

V presenteru vytvoříš novou instanci

$foo = $this->context->createTabulka();

Metody voláš

$this->context->createTabulka()->updateAdmin($data, $id);
// nebo v pripade instance tridy Tabulka:
$foo->updateAdmin($data, $id);

Editoval castamir (27. 8. 2012 12:36)

Maxell92
Člen | 38
+
0
-

Kód vychází z QuickStartu (Todo list), model dědí od Selection. Connection mám jako parametr v konstruktoru:

<?php
class Admins extends Selection {
    public function __construct(Connection $connection) {
        parent::__construct('admin', $connection);
    }
    ...
?>

Naopak se mi nezdá moc čisté řešení to popisované v odpovědi :) Spíš bych čekal, že frontu jednoduše vymažu. Myslím, že to bude občas potřeba i v jiných případech.

Maxell92
Člen | 38
+
0
-

castamir: V případě, že update volám jako $this->context->createTabulka()->updateAdmin($data, $id), tak to funguje. To mi osobně nepřijde jako nejlepší řešení, nebo je to takhle ok?

castamir
Člen | 629
+
0
-

quickstartu|model se přibližně v polovině stránky vysvětluje rozdíl mezi továrnami a službami:

„Základní rozdíl mezi službou a továrnou je ten, že továrna vytváří pokaždé, kdy si jí vyžádáte, novou instanci objektu. Oproti tomu služba vytvoří pouze jednu instanci při jejím prvním použití a pak tuto instanci používá. Službu si lze představit podobně jako singleton, akorát její jedinečnost je zajištěna pouze v rámci jednoho kontejneru.“

V případě databází můžeš s objekty továren pracovat v postatě úplně stejně jako s objekty ze služeb s tím rozdílem, že pokud máš instanci služby už použitou (např. nějakým selectem) a chceš provést nový select nad původní tabulkou, tak se ti to nepovede, neboť nový dotaz bude proveden nad tím posledním. V případě továren však můžeš vytvořit novou instanci (a nemusíš přitom ztratit tu minulou) a databázový dotaz provést.

Služby jsou definovány nejčastěji v configu (ale lze je definovat i dynamicky) a to následujícím způsobem popsaným právě v quickstartu:

services:
        authenticator: Authenticator
        tasks: TaskList\Tasks
        users: TaskList\Users
        tasklists: TaskList\Tasklists

Výše zmíněný kód deklaruje 4 služby:

  • authenticator
  • tasks
  • users
  • tasklists

Editoval castamir (27. 8. 2012 12:54)

castamir
Člen | 629
+
0
-

@Maxell92: Je to zcela korektní použití, obzvlášť, pokud tvá metoda updateAdmin() provádí další operace nad tabulkou, které mění její obsah např. selekcí…

Editoval castamir (27. 8. 2012 12:59)

VojtaSvoboda
Člen | 2
+
0
-

Přijde mi trošku zvláštní pro každý dotaz vytvářet novou instanci třídy modelu.

Editoval VojtaSvoboda (27. 8. 2012 13:00)

castamir
Člen | 629
+
0
-

Není potřeba vytvářet novou instanci pokaždé, ale je to vhodné tehdy, když potřebuješ přistoupit třeba k původní tabulce a nikoliv k selekci z té tabulky.

	$foo = $this->context->createTabulka();
	$foo->where(...)->order(...);
...
	$var = $this->context->createTabulka();
...
	$foo->fetch(); // prvni radek ze selekce z tabulky
	$var->fetch(); // prvni radek puvodni tabulky

Naproti tomu u služby (např. tasks) by to dopadlo následovně:

	$foo = $this->context->tasks;
	$foo->where(...)->order(...);
...
	$var = $this->context->tasks; // $var === $foo
...
	$foo->fetch(); // prvni radek ze selekce z tabulky
	$var->fetch(); // druhy radek ze selekce z tabulky

Editoval castamir (27. 8. 2012 13:07)

vvoody
Člen | 910
+
0
-

Možno by mal ten Selection hádzať nejakú exception keď sa pokúsi položiť druhý dotaz :D alebo snáď to má nejaký význam, že Selection umožňuje položiť viac krát dotaz nad tou istou tabuľkou?

VojtaSvoboda: To rozširovanie Selection bola hlúposť ktorá už bola opravená v quickstarte. Model tak ako je teraz v quickstarte už môže byt použitý viac krát, preto je zadefinovaný ako služba.

redhead
Člen | 1313
+
0
-

@vvoody: ano, má to svůj význam, např.:

$selection = ...
$totalCount = $selection->count();
$itemsOnPage = $selection->limit(10, 10);
castamir
Člen | 629
+
0
-

@vvoody Např. když potřebuješ upřesnit select nebo třeba jinak seřadit.

Edit: pozdě ;)

Editoval castamir (27. 8. 2012 13:23)

Elijen
Člen | 171
+
0
-

Maxell92 napsal(a):

Kód vychází z QuickStartu (Todo list), model dědí od Selection. Connection mám jako parametr v konstruktoru:

<?php
class Admins extends Selection {
    public function __construct(Connection $connection) {
        parent::__construct('admin', $connection);
    }
    ...
?>

Dědit service od Selection je naprostá blbost. Bohužel se to nějak dostalo do QuickStartu a tak jsem to i já na jednom projektu začal používat :-/

Editoval Elijen (27. 8. 2012 13:32)

vvoody
Člen | 910
+
0
-

redhead napsal(a):

@vvoody: ano, má to svůj význam, např.:

$selection = ...
$totalCount = $selection->count();
$itemsOnPage = $selection->limit(10, 10);

Ten count ma nenapadol. Tak potom inak, že by Selection vyhodil exception keď by bola zavolaná metóda where/order/limit po tom čo by bol tento Selection fetch/foreach-nutý?

Opýtam sa radšej priamo :D je vhodné foreach-nuť 2× ten istý selection? S tým že sa pred tým druhým foreachom zmení podmienka a budú sa napríklad aj ťahať iné stĺpce. Nebude to robiť problém napríklad v cache?

Tak som si to aj vyskúšal a celkom nepríjemné je že pre oba dotazy sa cachujú použité stĺpce spolu. Takže prvý foreach prejde napríklad všetky články s tým že v jeho tele vyberáme z ActiveRow len meno článku. Doplníme podmienku do Selection na zúženie výberu a teraz pre zmenu budeme vyberať aj celý content. Vo výsledku prvé query ťahá aj stĺpce použité v druhom. Pokúsil som sa to teda explicitne nastaviť cez select() čo pomohlo prvému dotazu, no pri druhom zase čo som nadefinoval pri prvom, už neodstránim pri druhom, takže som bol nútený doplniť tie stĺpce čo chýbali aby som neobdrzal PDOException a nič som nemohol robiť s tým že sa mi vyberú všetky stĺpce nadefinované pred prvým foreachom aj keď všetky nepotrebujem.

Editoval vvoody (27. 8. 2012 15:33)

David Matějka
Moderator | 6445
+
0
-

jen poznamka k te cache: to nesouvisi s tim, ze je to provadeno na stejne instanci, viz: https://forum.nette.org/…tabase-cache

vvoody
Člen | 910
+
0
-

Aha tak čo som napísal je hlúposť. Skúsil som to znovu a poriadne aj s priebežným mazaním cache a je to tak. Predsa sa to dobre zacachuje a vyberú len tie potrebné stĺpce. V query mi tie stĺpce zostali lebo pôvodne som použil všetky stĺpce v oboch foreach-och a až potom niektoré zakomentoval a nezmazal cache. Nemali by sa tie stĺpce po ich nepoužití znovu z cache odstrániť? Mohlo by sa to kontrolovať aspoň vo vývojárskom režime.

David Matějka
Moderator | 6445
+
0
-

bylo by to vhodny, ale bohuzel ne tak jednoduchy. muselo by to byt spojeno s rozlisenim, odkud je selection vytvareno.
protoze stejnej dotaz muze byt pouzit napriklad na dvou strankach – na jedne vypisu jen titulek a na druhe i telo clanku. kdyz bych se ale vratil na tu stranku, kde se vypsal jen titulek, z cache by se smazalo, ze ten stejnej dotaz volany z jineho mista chtel i telo clanku…