Funkce pro odčítání proměnných

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

Zdravím,

mám takový menší problém. Snažím se vytvořit funkci, který by uživateli odečítala kredit na základě toho, kolik kreditů je nastaveno u jednotlivých článků. Vytvořil jsem proto tuto funkci.

public function countclick(){
        $first = $this->getUser()->get('credit');
        $second = $this->getTutorial()->get('price');
        $count = $first-$second;
        $this->getUser()->update(array('credit' => $count));
    }

Bohužel se nestane nic jiného, než že se zapíše nula. Jinak četl jsem již několik příspěvku na podobné téma, kde se řešilo přičítání a odčítání čísel, bohužel nikde jsem nenašel jak odčítat proměnné.

Díky za radu.

h4kuna
Backer | 740
+
0
-

Já bych to řešil procedurou v databázi. Pokud to chceš v php tak na první pohled to máš ok.

  1. co je v proměnné $count?
  2. jaký datový typ máš v databázi?
quiced
Člen | 85
+
0
-

Tou procedurou v databázi myslíš zapsat to rovnou do toho update? Do proměnné $count by se měl načítat ten rozdíl, tedy alespoň tak je to myšleno. Datový typ v db je u obou int.

Glottis
Člen | 129
+
0
-

myslel spis toto

v update bys to delal spis takhle
update tabulka set sloupec = sloupec + hodnota
coz je lepsi nez to prohanet pres php. rozhodne ti to nezaruci ze ti to mezitim nekdo neprepise

Glo

quiced
Člen | 85
+
0
-

Mohl by jsi mi to co jsi napsal tady: update tabulka set sloupec atd.. trochu převést do nějakého kódu? Jinak ty mysql procedury používat nebudu už jenom proto, že to vidím poprvé v životě :D.

llsm
Člen | 121
+
0
-

Ocividne jsi nejak mimo misu. h4ktuna se te spravne ptal, co mas v jednotlivych promenych, protoze pokud v nich mas cisla (tj. integer), tak $count musi byt take cislo. A pak uz jen musi byt sloupec v databazi taky spravny typ.

Uplne základy:

<?php
 $first = 15;
 $second = 5;
 $count = $first - $second;

 echo $count; //vypise 10
?>
Glottis
Člen | 129
+
0
-

quiced napsal(a):

Mohl by jsi mi to co jsi napsal tady: update tabulka set sloupec atd.. trochu převést do nějakého kódu? Jinak ty mysql procedury používat nebudu už jenom proto, že to vidím poprvé v životě :D.

:) netusim co tam mas za db vrstvu tak ti vic nenapisu. musi se vykonat tohle :)

<?php
$query = "UPDATE `tabulka` SET `credit` = `credit` - {$count}";
?>

s nette database by to bylo tohle?

<?php
$db->exec("UPDATE `tabulka` SET `credit` = `credit` - ?", $count);
?>

to bys mel v metode toho tvojeho modelu? a volal bys treba

<?php
$this->getUser()->odectiKredit($count);
?>

a db se ti postara o to ze se ti krediti nebudou vyparovat :)

quiced
Člen | 85
+
0
-

Asi jsem trochu mimo mísu:D, protože nechápu co jsi napsal. Už v předešlém příspěvku jsem psal, že v databázi mám používané sloupce nastavené na int, což je podlě mě číslo, pokud ne tak mě prosím oprav :D.

Edit: Jinak používám nette database.

Editoval quiced (13. 3. 2013 23:48)

llsm
Člen | 121
+
0
-

Nette database nepouzivam, tak neznam presnou syntaxi, ale co ti brani ziskat aktualni stav kreditu samostatnym dotazem, odecist si to v aplikaci a pak uz jen ulozit do databaze vysledne cislo? Tj presne to, co mas v uvodnim prispevku.

<?php
 $first = $this->getUser()->get('credit'); //pokud je dotaz tak, jak potrebujes, musi ti vratit cislo, zkontroluj si to Debuggerem

 $second = $this->getTutorial()->get('price'); //pokud je dotaz tak, jak potrebujes, musi ti vratit cislo, zkontroluj si to Debuggerem

 $count = $count = $first - $second; //Odecitas 2 cisla, vysledek musi byt cislo
 $this->getUser()->update(array('credit' => $count));

 //Volany update musi nad databazi udelat
 $database->exec('UPDATE users SET ? WHERE id = ?', $credit, $asiIDUzivatele);
 //promenna $credit obsahuje array('credit' => $count)
?>

Opravdu v tom nikde nevidim zadny problem. Jen si musis zkontrolovat, ze kazdy krok (tj kazda promena) ma co ocekavas a pripadne resit, proc to nema…

petr.pavel
Člen | 535
+
0
-

@quiced: Nejlepší bude, když sem zkopíruješ z debug baru, jaké dotazy se ti pouští do databáze, ať se vyhneme nepodloženým dohadům.

quiced
Člen | 85
+
0
-

Už jsem přišel na to, kde je hlavní problém. Ten je v tom, že jsem vůbec neřešil výběr podle aktuálního uživatele a aktuálně otevřené stránky. Jinými slovy jsem načítal dva celé sloupce hodnot.

Teď by mě spíše zajímalo, zda je správně to, že pokud chci vybrat jeden sloupec tabulky, tak to dělám přes ->get(‚sloupec‘) ?

Glottis
Člen | 129
+
0
-

kdyz to budes resit (to odcitani) aplikacne, zadelavas si na problem. to sem se snazil naznacit.

budou dva pozadavky na odecteni kreditu. prijdou prakticky stejne. jeste se nekde neco zakucka a nastane tohle

select1
select2
spocitas1
spocitas2
update1
update2

a update1 jako by nebyl

jost125
Člen | 7
+
0
-

Glottis napsal(a):

kdyz to budes resit (to odcitani) aplikacne, zadelavas si na problem. to sem se snazil naznacit.

budou dva pozadavky na odecteni kreditu. prijdou prakticky stejne. jeste se nekde neco zakucka a nastane tohle

select1
select2
spocitas1
spocitas2
update1
update2

a update1 jako by nebyl

Protože tam chybí transakce. Tj. před selectem začni transakci a po updatu commitni a tenhle problém se ti nestane.

Glottis
Člen | 129
+
0
-

no nevim, jeden update co jsem psal mi prijde jednodusi nez zacit transakci, select, update, commit :) ale je mi to vlastne jedno :)

Majkl578
Moderator | 1364
+
0
-

K předcházení problémů souvisejícím s manipulací s daty paralelním procesem slouží zámky.

jost125
Člen | 7
+
0
-

Píšu to spíš pro princip, aby se na takový věci nezapomínalo i jinde, kde to jedním dotazem vyřešit nejde.

jost125
Člen | 7
+
0
-

Majkl578 napsal(a):

K předcházení problémů souvisejícím s manipulací s daty paralelním procesem slouží zámky.

Transakce si zamykají tabuky samy. Manuální lockování je samozřejmě alternativa.

quiced
Člen | 85
+
0
-

Díka za rady, bohužel tomu co tady píšete vůbec nerozumím. Je mi někdo schopen pomoct navrhnout nějaké funkční řešení?

jost125
Člen | 7
+
0
-

Pošli výstup tohoto:

...
$first = $this->getUser()->get('credit');
var_dump($first);
$second = $this->getTutorial()->get('price');
var_dump($second);
$count = $first-$second;
var_dump($count);
exit();
...
quiced
Člen | 85
+
0
-

To co jsi mi poslal jsem trochu upravil, tak aby to vybíralo aktuálního uživatele a aktuální článek.

public function countclick($user, $id){
        $first = $this->getUser()->where(array('id' => $user))->get('credit');
        var_dump($first);
        $second = $this->getTutorial()->where(array('id' => $id))->get('price');
        var_dump($second);
        $count = $first-$second;
        var_dump($count);
        exit();
    }

tohle vypíše toto:

bool(false) bool(false) int(0)

pro jistotu posílám i funkci z presenteru pro zobrazení

public function renderTutorial($id, $user){
       $this->template->tutorials = $this->categoryRepository->findTutorial($this->getParam('id'));
       $user = $this->getUser()->getIdentity()->id;
       $this->template->countclick = $this->categoryRepository->countclick($user, $this->getParam('id'));
   }
llsm
Člen | 121
+
0
-

Pokud ti databaze vrací místo výsledku false, znamena to, ze dotazu neodpovídají žádné řádky. To může být tím, že sestavený dotaz je špatně nebo ty záznamy v databázi opravdu nejsou… pak je jasne, ze kdyz odecitas od sebe 2 booleany, tak ti z toho nikdy cislo nevypadne…

Posli jeste toto, aby se zkontrolovalo, jestli v tech promenych mas neco:

<?php
public function countclick($user, $id){
	var_dump($user);
	var_dump($id);
        $first = $this->getUser()->where(array('id' => $user))->get('credit');
        var_dump($first);
        $second = $this->getTutorial()->where(array('id' => $id))->get('price');
        var_dump($second);
        $count = $first-$second;
        var_dump($count);
        exit();
    }
?>

Editoval llsm (14. 3. 2013 12:25)

Majkl578
Moderator | 1364
+
0
-

jost125 napsal(a):

Transakce si zamykají tabuky samy. Manuální lockování je samozřejmě alternativa.

To je mystifikace.
Transakce a zamykání nejsou zaměnitelné, obojí slouží k něčemu jinému.
Použiji příklad od Glottise výše a trochu jej rozvedu. Mějme následující situaci s dvěmi paralelně běžícími procesy nad stejným záznamem (pro jednoduchost např. vklad/převod na bankovní účet s počátečním zůstatkem 100,–) a aplikační logikou upravující zůstatek; 1 a 2 rozlišují proces:

  1. select1 – Proces 1 pracuje s vkladem 50,–. Zjistí si tedy aktuální zůstatek, který je 100,–.
  2. select2 – Proces 2 pracuje s vkladem 20,–, udělá totéž a taktéž zjistí 100,–.
  3. spocitas1 – Proces 1 nyní provede příslušnou početní operaci a dojde k tomu, že nový zůstatek je 150,–.
  4. spocitas2 – Proces 2 udělá totéž a dojde k novému zůstatku 120,–.
  5. update1 – Proces 1 nyní uloží vypočtenou hodnotu, tedy nastaví zůstatek na 150,–.
  6. update2 – Proces 2 nyní chce udělat totéž, ale musí počkat na dokončení Procesu 1, jež přišel dřív. Když na něj dojde, nastaví zůstatek na 120,–, který vypočetl.
  7. Aktuální zůstatek je 120,–. To je ale špatně.

Co se stalo?
Jelikož oba procesy běžely paralelně, oba chybně pracovaly s původní hodnotou. Důsledkem bylo špatné vypočtení zůstatku. Kdyby paralelně neběžely, nestalo by se to.
Jak tomu předejít?
Vynutit procesy běžet synchronně, tedy použít zámky. Proces 1 by v prvním kroku, select1, zjistil hodnotu a zamkl řádek pro čtení, čímž nedovolí procesu 2 dělat totéž. Proces 2 nyní musí počkat na dokončení procesu 1 (respektive transakce operující se zůstatkem) než bude moci pokračovat.

Takže jaký je rozdíl mezi transakcemi a zámky?
Transakce zajišťují bezpečné provedení více operací a poskytují možnost změny odvolat.
Zámky nabízejí možnost uzamčení záznamu před ostatními procesy/transakcemi do doby, než bude opět bezpečné s těmito záznamy pracovat.


(Edit: Doporučuji ještě přečíst na StackOverflow tento komentář včetně reakcí na něj.)

quiced
Člen | 85
+
0
-

llsm napsal(a):

Pokud ti databaze vrací místo výsledku false, znamena to, ze dotazu neodpovídají žádné řádky. To může být tím, že sestavený dotaz je špatně nebo ty záznamy v databázi opravdu nejsou… pak je jasne, ze kdyz odecitas od sebe 2 booleany, tak ti z toho nikdy cislo nevypadne…

Posli jeste toto, aby se zkontrolovalo, jestli v tech promenych mas neco:

<?php
public function countclick($user, $id){
	var_dump($user);
	var_dump($id);
        $first = $this->getUser()->where(array('id' => $user))->get('credit');
        var_dump($first);
        $second = $this->getTutorial()->where(array('id' => $id))->get('price');
        var_dump($second);
        $count = $first-$second;
        var_dump($count);
        exit();
    }
?>

Ještě jsem to trochu upravil, aby se tam náhodou nějak nepletli ty id i když jsou stejný.

public function countclick($user, $clanek){
    var_dump($user);
    var_dump($clanek);
        $first = $this->getUser()->where(array('id' => $user))->get('credit');
        var_dump($first);
        $second = $this->getTutorial()->where(array('id' => $clanek))->get('price');
        var_dump($second);
        $count = $first-$second;
        var_dump($count);
        exit();
    }

a

public function renderTutorial($id, $user, $clanek){
    $this->template->categories = $this->categoryRepository->findCategory();
    $this->template->tutorials = $this->categoryRepository->findTutorial($this->getParam('id'));
    $user = $this->getUser()->getIdentity()->id;
    $clanek = $this->getParam('id');
    $this->template->countclick = $this->categoryRepository->countclick($user, $clanek);
}

potom z toho vypadne toto:

int(31) string(2) "30" bool(false) bool(false) int(0)

Pokud jsem to dobře pochopil, tak z toho plyne chyba v id článku, protože to nebere jako číslo.

h4kuna
Backer | 740
+
0
-

Uvědom si že databáze je základ pro každou aplikaci a čím kvalitnější bude databáze tím kvalitnější bude aplikace, teda mělo by to tak být :D. Shnilou aplikaci nevylepší super cool Nette pokud je už shnilá databáze. Takže by nebylo od věci pročíst si k čemu jsou procedury, transakce, triggery, zámky a cizí klíče. Není tak těžké to používat a nastavovat chce to jen cvik. A pokud pro návrh databáze použiješ Adminera tak ti pomáhá nastavovat cizí klíče, pokud dodržíš konvenci pro tabulku a cizí klíče.

Časem se pak budeš divit proč se to chová tak jak se to chová, jak je popsáno výše ohledně paralelních procesů.

llsm
Člen | 121
+
0
-

Ne, chápeš to špatně. Číslo, které je předáno jako string problém nebývá. Vstupní parametry jsou takto téměř v pořádku, pokud to chceš mít pinktlich, tak můžeš ten string jednoduše přetypovat, pokud to máš nějak ochráněné, aby tam nic jiného než číslo nevlezlo:

<?php
 $clanek = (int) $this->getParam('id');
?>

Z toho co vidím, tak ti ty dotazy do databáze nevrací žádné výsledky, jak jsem psal v minulém postu. Mám na mysli tyto konkrétní dva, na ty se soustřeď a snaž se opravit ty:

<?php
$first = $this->getUser()->where(array('id' => $user))->get('credit');
$second = $this->getTutorial()->where(array('id' => $id))->get('price');
?>
hAssassin
Člen | 293
+
0
-

ano, tam bude zakopany pes, ale zeptam se jinak: co delaji metody getUser() a getTutorial()? A pokud me pamet neklame, tak get() nad Selection vraci neco jinyho nez hodnotu sloupecku, ne (viz zde)? Takze ten dotaz by mel byt spis neco jako:

$userEntity = $this->getUser()->where(array('id' => $user));
$tutorialEntity = $this->getTutorial()->where(array('id' => $id));

if ($userEntity && $tutorialEntity) {
	$first = $userEntity->credit;
	$second = $tutorialEntity->price;
	$count = $first - $second;
} else {
	$this->flashMessage("Uzivatel nebo tutorial neexistuje!", "error");
}

Mozna to nebude uplne ok, ale uz si NDB moc nepamatuju a starsi zdrojaky po ruce nemam, ale.

quiced
Člen | 85
+
0
-

Metoda getUser() ta vybírá tabulku s uživatelama a getTutorial() s článkama. Jinak to co jsi napsal jsem zkoušel a háže to chybu:

Cannot read an undeclared property Nette\Database\Table\Selection::$credit
hAssassin
Člen | 293
+
0
-

jo, takze to neco ala repozitar, to jsem predpokladal. No asi tam mam nekde chybu a asi uz vim kde :-) Zkus upravit prvni dva radky na:

$userEntity = $this->getUser()->where(array('id' => $user))->fetch();
$tutorialEntity = $this->getTutorial()->where(array('id' => $id))->fetch();

A pokud pak das dump($userEntity); tak by ti to melo vypsat entitu aktualniho uzivatele z DB. Pak vis ze jsi na spravny ceste a mas spravny data, s nimiz muzes delat neco dal ;-)

EDIT: jinak ten get() se pouziva tak, ze mu tam vlozis primo hodnotu primarniho klice (tedy v obou pripadech IDcka) a uz to nemusis ani fetchovat a ono ti to vrati primo radek podle primarniho klice, takze to muzes zkusit upravit na:

$userEntity = $this->getUser()->get($user);
$tutorialEntity = $this->getTutorial()->get($id);

Editoval hAssassin (14. 3. 2013 16:12)

quiced
Člen | 85
+
0
-

hAssassin musím ti poděkovat :D už to vypisuje přesně ten výsledek co má.

EDIT: Zkoušel jsem i ten druhý způsob a je také funkční. Ještě jednou díky za ochotu.

Editoval quiced (14. 3. 2013 16:18)

llsm
Člen | 121
+
0
-

To je rozhodne dobre. Nezapomen se ale venovat taky tomu, co mezitim psali ostatni, tj osetreni, aby ti tam nemohla vzniknout chyba asynchronim vkladanim, nejlepsi k tomu byl prispevek Majkla578

hAssassin
Člen | 293
+
0
-

nz, jsem rad ze jsem pomoh. kazdopadne doporuju se podivat i na ty zamky a transakce o kterych je zminka vyse. Na localhostu ti to muze jet (kor pokud jsi jediny kdo si s tim hraje) ale na produkci to muze byt o necem jinym, nahoda je blbec a ani nemusis mit na webu 1000 navstev denne ;-)

quiced
Člen | 85
+
0
-

Ještě jednou díky a určitě se na zámky a transakce o kterých byla zmínka podívám.

jost125
Člen | 7
+
0
-

Majkl578 napsal(a):

jost125 napsal(a):

Transakce si zamykají tabuky samy. Manuální lockování je samozřejmě alternativa.

To je mystifikace.
Transakce a zamykání nejsou zaměnitelné, obojí slouží k něčemu jinému.
Použiji příklad od Glottise výše a trochu jej rozvedu. Mějme následující situaci s dvěmi paralelně běžícími procesy nad stejným záznamem (pro jednoduchost např. vklad/převod na bankovní účet s počátečním zůstatkem 100,–) a aplikační logikou upravující zůstatek; 1 a 2 rozlišují proces:

  1. select1 – Proces 1 pracuje s vkladem 50,–. Zjistí si tedy aktuální zůstatek, který je 100,–.
  2. select2 – Proces 2 pracuje s vkladem 20,–, udělá totéž a taktéž zjistí 100,–.
  3. spocitas1 – Proces 1 nyní provede příslušnou početní operaci a dojde k tomu, že nový zůstatek je 150,–.
  4. spocitas2 – Proces 2 udělá totéž a dojde k novému zůstatku 120,–.
  5. update1 – Proces 1 nyní uloží vypočtenou hodnotu, tedy nastaví zůstatek na 150,–.
  6. update2 – Proces 2 nyní chce udělat totéž, ale musí počkat na dokončení Procesu 1, jež přišel dřív. Když na něj dojde, nastaví zůstatek na 120,–, který vypočetl.
  7. Aktuální zůstatek je 120,–. To je ale špatně.

Co se stalo?
Jelikož oba procesy běžely paralelně, oba chybně pracovaly s původní hodnotou. Důsledkem bylo špatné vypočtení zůstatku. Kdyby paralelně neběžely, nestalo by se to.
Jak tomu předejít?
Vynutit procesy běžet synchronně, tedy použít zámky. Proces 1 by v prvním kroku, select1, zjistil hodnotu a zamkl řádek pro čtení, čímž nedovolí procesu 2 dělat totéž. Proces 2 nyní musí počkat na dokončení procesu 1 (respektive transakce operující se zůstatkem) než bude moci pokračovat.

Takže jaký je rozdíl mezi transakcemi a zámky?
Transakce zajišťují bezpečné provedení více operací a poskytují možnost změny odvolat.
Zámky nabízejí možnost uzamčení záznamu před ostatními procesy/transakcemi do doby, než bude opět bezpečné s těmito záznamy pracovat.


(Edit: Doporučuji ještě přečíst na StackOverflow tento komentář včetně reakcí na něj.)

Souhlasím a samozřejmě vím, že transakce a zamykání není totéž, ale nesouhlasím s tím, že by transakce nevyřešili onen problém. Osobně si nemyslím že to patří do tohohle threadu, ale když už jsme to načali …

Transakce mohou běžet v různých úrovních izolace. Ta „nejstriktnější“ a zároveň použitá jako výchozí v mysql je SERIALIZABLE. Tedy serializace transakcí. V případě serializace se pak uvnitř transakce zamykají pro read and write tabulky v tom pořadí, v jakém se k nim přistupuje. Jakákoliv další paralelní transakce, která chce opět přistupovat k již zamčenému zdroji musí počkat, než ho první transakce pustí. Což dělá teprve v momentě, kdy dojde ke commitu, nebo rollbacku. To dělá z dvou paralelních transakcí serializované oprace.

Příklad:

proces A započne transakci,

proces B započne transakci,

proces A udělá select do tabulky foo a zamkne tabulku foo pro read a write

proces B chce udělat select do tabulky foo, ale protože proces A zamkl foo, musí počkat

proces A udělá update do tabulky foo, což může protože vlastní zámek

proces A udělá commit a tím odemkne zámek na foo

proces B pokračuje kde přestal (pokud nedošel timeout) tedy udělá select nad foo a zamkne ji pro sebe, pak update do foo a nakonec commit a tím ji opět odemkne.

Zámkama se dá docílit naprosto samého jen je to ukecané. Zámky samkozřejmě nemají zbylé vlastnosti z ACID (resp. zámky zajišťují pouze I, independency). Transakce ještě zajišťuje atomicity, consisitency a duralbility.

Zámky mají ale jinou výhodou vlastnost oproti transakcím. Transakce zamykají v tom pořadí, jak přistupují ke zdrojům. Při manuálním zamykání mohu mít „uzamykací protokol“. Zatímco „chaotické“ zamykání transakcí může způsobovat (a u velkých projektů s velkou návštěvností způsobuje) deadlocky, při manuálním zamykání a uzamykacím protokolu (například zamykám zdroje v abecedním pořadí) k deadlocku nedojde. A proto se často používají zámky uvnitř transakcí, abych měl dodržená ACID a současně nedošlo k deadlocku.

Pokud nesouhlasíš, doporučuju nastudovat ACID, úrovně izolací transakcí (READ UNCOMMITED, READ COMMITTED, REPEATABLE READ , SERIALIZABLE) a anomálie s nima spojený.

můžeš mrknout třeba sem http://www.root.cz/…-databazich/

h4kuna
Backer | 740
+
0
-

Není nic jednoduššího než si to vyzkoušet.

Problém který tu je popsán tak se řeší takto, viz odkaz PHP. Pokud to není v transakci tak to nefunguje a transakce nezamyká řádky.