Nákupní košík

Školení, která pořádám

Pokud chceme uživateli webového obchodu umožnit házet zboží do košíku, ideálně se k tomu hodí session proměnné. Obsah košíku můžeme uchovávat v poli, kde klíčem bude ID položky a hodnotou počet kusů v košíku. Pokud by pole bylo navrženo tak, že by pouze v hodnotách bylo uloženo ID položek, znesnadnily by se některé operace (zjištění počtu druhů zboží, vyjmutí jednoho druhu zboží z košíku, …).

<?php
// přidání zboží do košíku
$_SESSION["kosik"][intval($_GET["id"])] = intval($_GET["pocet"]);

// vyjmutí zboží z košíku
unset($_SESSION["kosik"][$_GET["id"]]);

// vypsání obsahu košíku
if ($_SESSION["kosik"]) {
    $result = mysql_query("SELECT id, nazev FROM zbozi WHERE id IN ('" . implode("', '", array_keys($_SESSION["kosik"])) . "')");
    while ($row = mysql_fetch_assoc($result)) {
        echo htmlspecialchars($row["nazev"]) . " (" . $_SESSION["kosik"][$row["id"]] . ")<br />\n";
    }
    mysql_free_result($result);
}
?>

Všechny operace jsou velice jednoduché, např. počet kusů v košíku zjistí array_sum($_SESSION["kosik"]), počet druhů zboží v košíku jednoduché count($_SESSION["kosik"]). Pokud bychom obsah košíku nechtěli ukládat do session proměnné, ale do cookie, je potřeba dát pozor na limit počtu ukládaných cookie, o kterém už jsem psal, protože každý prvek pole se ukládá do samostatné cookie. Pro uložení do cookie by tedy bylo potřeba pole nějak zřetězit, např. funkcí implode. Určitě v tomto kontextu nedoporučuji funkci serialize, protože útočník nám může podstrčit úplně jiná data, než čekáme. Použití session proměnné je zkrátka po všech stránkách vhodnější.

Viz také Nákupní košík v cookie.

Jakub Vrána, Řešení problému, 12.12.2005, diskuse: 43 (nové: 0)

Diskuse

RATMex B:

Iba drobnosť. Kód
<?php
$_SESSION
["kosik"][$_GET["id"]] = intval($_GET["pocet"]);
?>
by som nenazýval "pridanie tovaru do košíku", ale skôr "nastavenie počtu tovaru v košíku".

Michal Hantl:

Dělám to stejně, ale v cookies a používám (vlastní) funkci pro  konverzi pole do stringu. (a při čtení zase zpět)

Martin Straka:

a pokud neni zapnute magic_quotes_gpc (jako ze se na to neda spolehat), tak je tohle pekny priklad SQL injection:)

http://php.vrana.cz/obrana-proti-sql-injection.php

ikona Jakub Vrána OpenID:

V patičce stránky je uvedeno, s jakým nastavením ukázky kódu počítají. V článku http://php.vrana.cz/konfigurace-php.php je uvedeno, proč jsem zastánce centrální konfigurace a ne ošetřování v každém skriptu zvlášť.

Martin Straka:

Mas pravdu, ten predpoklad tam opravdu je.

ikona Jakub Vrána OpenID:

Kód jsem předělal na magic_quotes_gpc = Off a zmínil jsem to v patičce.

lukáš:

Nemelo by na zacatku byt jeste zavolano session_start()? Kazdopadne by me zajimal vas nazor na nasledujici potiz se sessions: rozume se daji pouzivat maximalne pokud se predavaji pres cookies (pri predavani identifikatoru pres PHPSESSID do URI se URI prodluzuje o "nahodny" retezec a to dela problemy hlavne robotum). Tim padem takovy kosik nebude fungovat lidem bez cookies... Je to snesitelna nevyhoda? Yuhuu rikal, ze podle jeho mereni ma 99.7% lidi povoleny javascript, podporu cookies bych cekal jeste vyssi. Mimoto treba krasa.cz bez cookies taky nedrandí.

ikona Jakub Vrána OpenID:

session_start() by na začátku samozřejmě být mohlo (pokud není zapnuté session.auto_start), ale vzhledem k tomu, že to jsou jenom útržky kódu, tak jsem to nepovažoval za nutné.

Co se cookies týče - webové prezentace by určitě měly fungovat i bez nich, u aplikací je možné jejich zapnutí vyžadovat (pak je slušnost o tom uživatele informovat). Dá se spolehnout na to, že cookies bude mít velká většina uživatelů skutečně zapnuté, byť často s různými omezeními - např. z trvalých cookies vytvoří session cookies nebo nedovololí cookies třetích stran (vložených z objektů na stránce pocházejících z jiné domény).

Naith:

Tak to já měl celkem smůlu. Můj první zákazník, pro kterého jsem dělal obchod, má cookies vypnuté. Chvilku mi trvalo, proč mě to jede a jemu ne. Musel jsem dat session id do uri. Nepovedlo se mi jej přesvědčit, aby to zapnul... Jediné co mě napadlo je udělat veškeré odkazy přes formuláře, nasadit na ně css a modlit se, aby se na to nedival někdo např. v Safari, které stylování formulářů nepodporuje.

Tomáš Havel:

Nevím nevím ale co jsem začal programovat v objektovém php tak jsem košík uložil do session jako objekt, rzošířil jsem si ho tam že lze jednoduše vracet změny. Další výhoda je že se nemusí znovu chodit do db pro jména či co a vypisovat.

Honza:

Ahoj, mohl bych se zeptat, jak by se třeba v tomhle případě řešila celková cena? pokaždý se připojit k DB a spočítat podle id a počtu kusů celkovou cenu je hloupí, ale používat promenou pro celkovou cenu zas znemožnuje jednoduše odebrat zboží z košíku protože už bychom nevěděli kolik kč zase máme odečíst, takže jedine se znova připojit k DB zjistit kolik stojí odebraný zboží a tuhle cenu pak odečíst od ceny celkové? nebo by snad, což mi příjde jako nejlepší, asi bylo přídat do toho session pole ještě místo pro ukládání zboží... Jak by jste to řešili vy?

ikona Jakub Vrána OpenID:

Já bych celkovou cenu načítal z databáze.

Honza:

hmm, mně příjde praktičtějši, a tak sem to teda nakonec taky udelal, cenu prostě pričíst do session pole úplně stejně jako ukládame počet kusů, nic méně, mohu mít ještě dotaz? :) kolik místa v pamětu bude takový pole zabírat jestliže bude obsahovat například pouze jednu hodnotu ale třeba na 1800tý pozici, takhle: $_SESSION["kosik"][1800] = 7; ? Předem děkuji za odpověd...

ikona Jakub Vrána OpenID:

Bude zabírat přesně stejně místa jako když by bylo na první pozici. Všechna pole v PHP jsou asociativní a tedy řídká.

davEsim:

Sessions jsou bezva, Porad jsem ale nikde nenasel řešení toho, že sesssion proste sama za nejakou dobu (treba 20 minut spadne). Treba na mall.cz idkyz pockám treba dve hodiny a vymazu cookies košík pořád "drží". Neví někdo?

ikona Jakub Vrána OpenID:

Vymazání cookies košík neudrží ani na mall.cz. To by se jeho obsah musel přenášet v URL, což má také svoje nevýhody.

Řešit se to dá nastavením dostatečně dlouhé doby platnosti session proměnných na straně serveru – u klienta to je obvykle do zavření prohlížeče, což je většinou přesně to pravé.

Druhá možnost je ukládat obsah košíku do cookie s delší dobou platnosti.

dugi:

ahojte, náhodou som natrafil na vašu diskusiu a veľmi ma to zaujalo.Nie som v tom "doma" a veľmi by som potreboval na moju webstránku zaradiť ku každému produktu možnosť hodenia do košíka a udať počet kusov. pomože mi s tým niekto prosím? Budem vám veľmi vďačný. Možte mi prosím odpovedať na dusan.o@drnona.eu.sk      dik

vojta:

Sessions mají jednu nevýhodu - když máte více fyzických serverů, může docházet k jejich ztrátě (když uživatele obslouží jiný server než předtím). Proto je podle mě lepší zapomenout na sessions a udělat si vlastní systém využívající staré dobré koláčky a případně databázi :). Můžete i lépe kontrolovat čas vypršení.

ikona Jakub Vrána OpenID:

V první řadě mícháš dvě věci dohromady - koncept sessions a způsob jejich ukládání. Koncept sessions (session identifikátor u klienta, data uložená na serveru) je u webových aplikací nepostradatelný.

Co se jejich ukládání týče, lze použít funkci session_set_save_handler(), pomocí které můžeš způsob ukládání změnit. Zadarmo získáš vytvoření a přenos session identifikátoru.

Ale ani ukládání do souboru není nutné zatracovat, stačí všechny servery připojit ke stejnému úložišti. Používá to třeba sf.net.

Phantom:

Při přidávání do koše bych změnil znaménko "=" na "+=" protože při klepnutí 2x na jeden stejný kus mám v koši pořád jeden kus :-)

Karlissimo:

Mohu se zeptat co se bude dít pokud se bude přidávát do košíku ze dvou počítačů současně? Jestli každý bude mít vlastní $_SESSION['kosik'], nebo musím to nějak ošetřit aby se jejich košík nesdílel (např. $_SESSION[<vygenerovane_id>]['kosik'], a pak to <vygenerovane_id> předávát po stránkach u každého z uživatelů)
Děkuji za odpověď

ikona Jakub Vrána OpenID:

Session proměnné jsou specifické pro prohlížeč. ID tedy není potřeba.

trancemaniac:

Dobrý dem, právě programuji nakupní košík a tohle řešení se mi celkem líbí, jen by mně zajímalo jak by jste řešil to, že mimo počtu kusů ukládám např. barvu zboží nebo velikost?
Děkuji za odpověď

ikona Jakub Vrána OpenID:

Místo počtu kusů bych do session proměnné ukládal pole se všemi informacemi.

Jakub:

Dobrý den. Chtěl jsem se zeptat, jestli se ještě používá $PHPSESSID. Dávám ho jako název tabulky košík (platnost tabulky 1 den), ale funguje, jen když mám register globals on.

Celý obchod mám rozdělený do 8 stránek. Další řešení, co mě napadlo byla globální dočasná tabulka. Tu, ale mysql asi zatím ještě nepodporuje?

Miloslav Bělský:

Dobrý den,
je možné řešit košík jako tabulku v DB? Čtu zde pouze o použití session. Proč to není vhodné použít tabulku v DB?

ikona Jakub Vrána OpenID:

Klidně košík do databáze ukládejte. U registrovaných uživatelů to je dokonce vhodné. U neregistrovaných je potřeba řešit mazání starých košíků.

juraj:

Dobry den
robim  pridavanie tovaru do kosika .Ak uzivatel klikne na tlacidlo do kosika tak sa premenne nacitaju

echo $_SESSION["kosik"][intval($_GET["idtovar"])] = intval($_GET["idtovar"]);

echo $_SESSION["kosik"][($_GET["velkosti"])] = $velkosti;
echo $_SESSION["kosik"][($_GET["farba"])] = $farba;
echo $_SESSION["kosik"][($_get["pocet"])] = $pocet;
ak kliknem na tlacidlo zobraz kosik nie je prenasana ziadna hodnota, ako sa riesi zobrazenie kosika cez session,neviem si s tym rady dakujem za pomoc
alebo ako sa riesi cez prikaz foreach s viacerymi hdodnotami este raz diky za akukolvek pomoc

Holmistr:

Zdravím Jakube,

zajímal by mě tvůj názor na mé řešení :-)

Košík máš realizovaný přes třídu, dejme tomu Kosik, ten obsahuje členskou proměnou typu pole, nazvěme ji $content. Do tohoto pole ukládám jednotlivé nakoupené položky, opět objekty třídy Product.

Třída Kosik má pak samozřejmě další metody, jak získat produkt, jak spočítat ceny...

Session mám potom tedy pouze jednu a sice $_SESSION['kosik'], do které při každé úpravě serializuju onen jedinný objekt třídy Kosik.

Dle mého je to elegatní řešení a poměrně pěkné, čisté a jednoduché. Jako klady vidím, že nemusím pokaždé přistupovat do databáze.

Zajímalo by mě, jak by jsi tento způsob zhodnotil ty, popř. i jiní kolegové :-)

Díky

ikona Jakub Vrána OpenID:

Já si raději ukládám jen samotná ID. V případě, že se detaily produktu změní, tak je lepší je mít uložené centrálně.

Holmistr:

To by znamenalo problém pouze v případě, že by se detaily produktu změnily v okamžiku mezi vložením do košíku a zaplacením, mýlím se?

Na toto jsem nemyslel. Vyřeším to pravděpodobně tak, že při finálním placení se to pro jistotu aktualizuje.

Díky za postřeh

ikona Tomac1:

Řešení se SESSION je podle mě nejjednoduší. Osobně ukládám do sessions celé pole (id,nazev,cenu,ks,kod) se kterým pak pracuji bez připojení do DB. Vypisuji tak například nákupní košík ve sloupci na straně webu. Pokud ale zákazník dojde k objdednávce, tak sessions a ceny aktualizuji připojením do db (pro jistotu), protože je velice pravděpodobné, že zrovna nakoupí produkt, u kterého někdo v administraci změní cenu ;-)

Holmistr:

Uvažoval jsem o několika způsobech, jak košík vyřešit (viz. příspěvek o něco výš) a nakonec jsem to vyřešil úplně stejně :-)

Až na to, že já přístuji do databáze při každém vložení do košíku. :-)

Citous:

Zdravím,

chtěl bych se zeptat, kdyby k session chtěl přidat i další vlastnost, třeba velikost, jak by to vypadalo ? díky

ikona Jakub Vrána OpenID:

Jsou dvě možnosti:

<?php
$_SESSION
["kosik"][$id] = array("pocet" => 1, "velikost" => "XL");

$_SESSION["kosik"][$id] = 1;
$_SESSION["kosik_velikost"][$id] = "XL";
?>

Jirka:

Zřejmě Vám to bude připadat jako stupidní dotaz, ale potřeboval bych si to srovnat v hlavě. Takže, vytvoří se SESSION s názvem "kosik", id=id_produktu_z_db a pocet=POST z formuláře. A tato session je pro každou PC stanici unikátní i když mají stejný název (zřejmě důsledkem toho že si ukládá ještě nějaký svůj speciální identifikátor do cookies na straně uživatele). Pochopil jsem to správně? Děkuji

Holmistr:

Naprosto :-)

Richard:

Myslím si že by bylo rozhodně vhodné požít nějakou nástavbu jako třeba mysqli a PS tím jse vyhneme SQL injekci, omlouvám jse jestli jste tohle už řešili.

Tomáš:

dobrý den, vytvořil jsem košík podle vašeho návodu a funguje, bohužel když vkládám do něj vkládám zboží tak se někdy rozdělí a část nákupu se uloží do "druhého" košíku, jako kdyby první košík nenaběhl, když kliknu na odkaz ke košíku vypíše se mi obsah jednoho z obou košíků ve většině případů je to košík první může být problém v hostingu?

Kalashniirek:

Můžete mi , prosím, poradit jak to udělat, když chci po dokončení objednávky odeslat vypsaný obsah košíku emailem ?

Forest:

Dobrý den,

pokud mám například indexy 0 - 5

a pak použiju unset na 3, tak mi to vyhodí:

Notice: Undefined offset: 3

a index 4 a 5 už to neukáže .. kde je problém? díky

ikona Jakub Vrána OpenID:

Nezdá se mi, že by dotaz nějak souvisel s článkem. Funkce unset() notice nevyhazuje. Pro lepší odpověď by to chtělo uvést kód, kterému nerozumíš.

Forest:

Jak nesouvisí s článkem? Pokud vím, tak unset je na smazání položky. No nic, díky za ochotu ...

Vložit příspěvek

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2016 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.