Košík eshopu – zoufalý neprogramátor

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

Ahoj, rád bych požádal o radu. Ve škole(peďák) máme naprogramovat funkční eshop s MVC architekturou. Za pomoci quickstart návodu jsem zprovoznil přidávání produktů, editaci produktů, příhlašování admina, ale nevím, jak zprovoznit košík a přidávání do něho. Teoreticky chápu, že se do session bude přidavat id produktů a pak se nějak bude vypisovat. Ale prakticky už to nezvládám. Našel jsem tuto diskuzi, která bude mému dotazu nejblíže ale chybí mi tam ale ještě pár informací kam co přesně napsat, aby mi to fungovalo. Ještě jednou upozorňuji, že jsem neprogramátor, takže to co pro ostatní zde bude samozřejmost, pro mě ne. Díky za každou pomoc.

Pavel Kravčík
Člen | 1196
+
0
-

Pokud si rozchodil přidávání a editaci, tak to zase tak zoufalé nebude.

Pomůže tohle?

//Přidání do košíku
public function actionPridat($id)
{
	$session = $this->getSession('kosik');

	if(is_array($session['zbozi'])
	{
		$session['zbozi'] = $session['zbozi'] + [$id];
	}
	else
	{
		$session['zbozi'] = [$id];
	}
}

// Zjištění, co je v košíku
public function zboziVKosiku()
{
	$session = $this->getSession('kosik');

	if(is_array($session['zbozi'])
	{
		return $this->productRepository->where('id', $session['zbozi']);
	}
	else
	{
		return NULL;
	}
}
Luděk Veselý
Člen | 29
+
-9
-

Nejtrivialnejsi reseni co me napada by mohlo vypadat takhle:

BasePresenteru (od ktereho ti dedi ostatni presentery) si vytvoris persistentni parametr s obsahem kosiku – pole s idcky produktu.

class BasePresenter extends Presenter
{
    /** @persistent */
    public $basket = [];

Dale si vytvoris handler, ktery prijme id pridavaneho produktu a prida do pole

public function handleAddToBasket($productId)
{
    $this->basket[] = $productId;
}

Odkaz pro pridani do kosiku pak v latte vygenerujes takto:

{link addToBasket! productId => 123}

Je to jen nastrel, v praxi by bylo lepsi to resit jinak, ale treba ti to pro ten projekt bude stacit :)

Editoval Luděk Veselý (19. 8. 2015 15:18)

bigfoot
Člen | 9
+
0
-

Pomůže tohle?

K čemu je prosím to productRepository ve volání košíku?
A mám pro tohle vytvářet nový presenter? A jak to pak budu volat šablonou? To jsou právě ty základní věci kterým nerozumím. Jak a kde se ty funkce volají.

Luďěk Veselý

A ten handler mám napsat kam? Jinak to řešení ale chápu. Tvoje řešení zapisuje to pole jako proměnou běžícího programu, ale Pavlovo řešení to zapisuje do session takže může přetrvat i po vypnutí prohlížeče, je to tak?

Oběma díky.

one-two
Člen | 80
+
+8
-

jsem jedinej kdo si myslí, že je lepší mít košík v DB místo v session (ikdyž je to trochu víc práce)? :)

  • uživatel se může přihlásit odkudkoliv a bude mít v košíku pořád svoje věci (např. na Alze to tak je taky)
  • pokud se rozhodnu měnit strukturu tak to neni problém, s datama v session se z toho zbláznim…
Pavel Kravčík
Člen | 1196
+
0
-

bigfoot napsal(a):

Pomůže tohle?

K čemu je prosím to productRepository ve volání košíku?
A mám pro tohle vytvářet nový presenter? A jak to pak budu volat šablonou? To jsou právě ty základní věci kterým nerozumím. Jak a kde se ty funkce volají.

ProductRepository by mělo být nějaký repositář, kterým pracuješ s těmi produkty (zbožím) v databázi. Tj. to co používáš na editaci z DB. A where je připravený dotaz, jak to vytáhneš. Pak si na to zavoláš fetchAll() a máš kompletní seznam všech věcí z košíku.

Zkusím to více rozepsat. Máš nějaké tlačítko v latte „koupit“. To tlačítko bude směřovat na akci pridat, musíš jí dát parametr id, abys věděl co přidáváš. Pokud Ti dává smysl handle, upravíme to podle něj. Je to lepší řešení.

Do BasePresenter (ten od koho dědí všechny presentery) vložíš tuhle funkci:

public function handleKosikPridat($id)
{
	$session = $this->getSession('kosik');

    if(is_array($session['zbozi'])
    {
        $session['zbozi'] = $session['zbozi'] + [$id];
    }
    else
    {
        $session['zbozi'] = [$id];
    }
}

Pak kdekoliv v latte můžeš volat tlačítko koupit, které bude směřovat na tuhle akci:

	// ID je konkrétní id zboží z databáze
	<a n:href="kosikPridat! id => $id">No, nekup to!</a>

Tohle by Ti mělo uložit id produktu do session. Na jiném místě si jí můžeš načítat (třeba v latte kde zobrazuješ košík).

Pro prvotní kontrolu tam doporučuji vložit:

	$session = $this->getSession('kosik');
	dump($session['zbozi']); die; //Mělo by vrátit něco jako array(0){[0] => 1}
Pavel Kravčík
Člen | 1196
+
+1
-

one-two napsal(a):

jsem jedinej kdo si myslí, že je lepší mít košík v DB místo v session

Ne – mám stejný názor, ale pokud Tě programování nezajímá a chceš jen kredity… :)

duke
Člen | 650
+
0
-

K čemu je prosím to productRepository ve volání košíku?

To je služba, která umí načíst produkty z nějakého úložiště (např. z databáze).

A mám pro tohle vytvářet nový presenter? A jak to pak budu volat šablonou? To jsou právě ty základní věci kterým nerozumím. Jak a kde se ty funkce volají.

Pročti si dokumentaci (zejména tuto část) a pak se sám rozhodni, jaké presentery a jaké akce presenterů budeš v aplikaci mít (některé akce budou vytvářet výstup přes šablonu, což je výchozí chování Nette presenterů, jiné to budou dělat přímo vrácením Response objektu (tj. objektu implementujícího rozhraní Nette\Application\IResponse), jiné mohou přesměrovávat na jiné akce/presentery, atp.). Pokud např. budeš mít akce, které spolu úzce souvisí, může být výhodné mít je v tomtéž presenteru.

A ten handler mám napsat kam? Jinak to řešení ale chápu. Tvoje řešení zapisuje to pole jako proměnou běžícího programu, ale Pavlovo řešení to zapisuje do session takže může přetrvat i po vypnutí prohlížeče, je to tak?

Šlo o handler presenteru, takže do presenteru. Jen upozorňuju, že při použití řešení přes persistentní proměnnou presenteru (tj. proměnnou přenášenou přes url) se ti obsah té proměnné ztratí, pokud přejdeš na jiný presenter, který tuto persistentní proměnnou nesdílí (sdílet ji může prostřednictvím dědičnosti). Proto je vhodnější mít košík v session (viz dokumentace Sessions). O vypínání prohlížeče zas až tak moc nejde. I v nově spuštěném prohlížeči si můžeš nechat obnovit url obsahující produkty košíku. Prostě když to bude v session, bude to nezávislé na url (tedy s výjimkou případu, kdy si přes url posíláš sessionId, což ale, pokud je mi známo, není v Nette přípustné – řeší se přes cookies).

Šaman
Člen | 2666
+
-1
-

one-two napsal(a):

jsem jedinej kdo si myslí, že je lepší mít košík v DB místo v session (ikdyž je to trochu víc práce)? :)

  • uživatel se může přihlásit odkudkoliv a bude mít v košíku pořád svoje věci (např. na Alze to tak je taky)
  • pokud se rozhodnu měnit strukturu tak to neni problém, s datama v session se z toho zbláznim…

Pro přihlášeného rozhodně databáze. Session je fajn, pokud uživatel přihlášený není.

Luděk Veselý
Člen | 29
+
0
-

@bigfoot mel to byt BasePresenter. Slo mi o nastineni principu jak vlozit do kosiku a automaticky prenaset na dalsi stranky. Zminene reseni pres sessions je rozhodne lepsi, u me se id produktu v kosiku predavaji jako parametry GET.

@one-two u nas ukladame kosiky do db, pouzivame to pro to aby se k nemu prihlaseny uzivatel mohl vratit a my mohli analyzovat nedokoncene objednavky, zkouset pak treba zakaznika reaktivovat..

bigfoot
Člen | 9
+
0
-

Děkuju, párkrát ještě projedu dokumentaci a zkusím to nějak dát dohromady. Je řešení přes databaze o hodně složitější?
Pavle co má být potom při tvém řešení v té šabloně .latte pro košík? Nějaký foreach řetězec?
Jinak mi nejde o kredity, rád bych tomu rozuměl, ale bez základů se tím prokousávám dost těžko. Ve škole nám k tomu předmětu ani nedali cvika.

Pavel Kravčík
Člen | 1196
+
0
-

Ano asi nějaký foreach pro nejjednodušší řešení. Ten foreach Ti vrátí activeRowa to je vlastně řádek z databáze, takže si z něj můžeš vytáhnout jméno nebo cenu. Vysledek budou data z „productRepostiry“.

$cena = 0;
foreach($vysledek as $row)
{
	$cena = $cena + $row->cena;
}

echo $cena;

Být Tebou zkusím to teď postupně po krocích (chci naprogramovat tlačítkou koupit, kliknutí mi uloží zboží, session fungují, načtení zboží, součet ceny) a klidně na papír si rozepiš, co chceš konkrétně dosáhnout. A pak pokud by ses někde zasekl, můžeš se tu zeptat na konkrétní věc a určitě Ti tu někdo poradí.

bigfoot
Člen | 9
+
0
-

Když si dám vývojářskou konzoli v chromu (F12), tak by se mi tam předpokládám ta session měla zobrazit, je to tak?

Pavel Kravčík
Člen | 1196
+
0
-

Asi ano. Teoreticky bys jí měl vidět i v Nette složce (/temp/sessions), ale nechci plácat nesmysly. Nejjistější způsob bude si to dupnout na místě, kde to chceš volat. Viz:

$session = $this->getSession('kosik');
dump($session['zbozi']); die; //Mělo by vrátit něco jako array(0){[0] => 1}

Dump umí vypsat hodnotu proměnné. Ten die zatím ukončí PHP a výsledkem by měla být prázdná stránka s nějakým výpisem té proměnné.

oldrich.valek
Člen | 21
+
0
-

Session se ukládá na serveru. V konzoli najdeš nejspíš jen nějaký identifikátor v cookies. Tu session by si si asi mohl vypsat třeba do debug baru.

<?php
	$session = $this->getSession('kosik');
	\Tracy\Debugger::barDump($session,'session');
?>
David Matějka
Moderator | 6445
+
+5
-

Nette ma panel pro tracy na zobrazeni session, ale musis ho zapnout v configu:

session:
	debugger: true
bigfoot
Člen | 9
+
0
-

Tento kód mi nefungoval.Dokázal vytvořit jen jeden záznam ale nepřidal k němu už další položku, stále přepisoval jednu a tu samou:

public function handleKosikPridat($id)
{
    $session = $this->getSession('kosik');

    if(is_array($session['zbozi'])
    {
        $session['zbozi'] = $session['zbozi'] + [$id];
    }
    else
    {
        $session['zbozi'] = [$id];
    }
}

Kamarád mi poslal kód který funguje:

public function handleKosikPridat($id) {
       $session = $this->getSession('kosik');
       if (is_array($session->zbozi)) {
           $session->zbozi[] = $id;
       } else {
           $session->zbozi = [$id];
       }
       $this->redirect('this');
   }

Byla chyba na mé straně nebo v původním kódu? A co prosím znamená tento zápis? Díky.

$session->zbozi
duke
Člen | 650
+
+1
-

Chyba byla v původním kódu. Fungovalo by to, kdyby místo:

$session['zbozi'] = $session['zbozi'] + [$id];

… bylo použito:

$session['zbozi'] = array_merge($session['zbozi'], [$id]);

pozn.: Navenek by to fungovalo úplně stejně, i kdyby se všude nahradil zápis $session['zbozi'] zápisem $session->zbozi.

Kód $session->zbozi[] = $id vede na volání $session->__get('zbozi'); tato metoda vrací referenci na prvek ‚zbozi‘, tj. referenci na pole s položkami košíku; následně se operátorem [] = přidá na konec tohoto pole prvek $id.

Kód $session->zbozi = [$id] pak vede na volání $session->__set('zbozi', [$id]); tato metoda přiřadí prvku ‚zbozi‘ hodnotu [$id], tj. jednoprvkové pole s prvkem $id.

Editoval duke (21. 8. 2015 18:45)

bigfoot
Člen | 9
+
0
-

Díky, takovéhle vysvětlení mi hodně pomáhá. Je to těžké se tím prokousávat od začátku.
Teď řeším smazání zaznamu z databáze, ale nedaří se mi. Řeším to handlerem v BasePresenteru ale hazí mi to chybu* Call to a member function table() on null*. Předpokládám, že to bude opět nějaká začátečnická chyba.

public function handleDelete($id) {
$this->database->table('sklad')->where('id', $id)->delete();
}
Šaman
Člen | 2666
+
0
-

Tvůj BasePresenter nejspíš nezná databázi. Neinjectuješ ji až v nějakém konkrétním presenteru? Resp. proč tohle děláš v BasePresenter? Tohle patří do nějakého konrétního. Do BasePresenteru patří max nějaká kontrola oprávnění, nebo hromadné nastavení pro všechny presentery, které z něho dědí.

bigfoot
Člen | 9
+
0
-

Spíše se snažím ty věci zatím pochopit jak fungují a pracují a pár věci jsem hodil do BasePresenteru abych je mohl volat odkudkoliv. Pokud je iniciace databaze toto, tak jí mám inicializovanou i v BasePresenteru.

private $database;

  public function __construct(Nette\Database\Context $database) {
      $this->database = $database;
  }

EDIT:
Z HomepagePresenteru to funguje, nevím kde byla chyba. Ale už mi to funguje normálně. Díky

Editoval bigfoot (21. 8. 2015 22:17)

CZechBoY
Člen | 3608
+
0
-

Databázi nech v modelu.
Navíc přepisovat konstruktor presenteru (navíc bez volání předka!) je hodně o hubu…

Udělej si místo toho BaseModel:

abstract class BaseModel extends \Nette\Object
{
  /** @var \Nette\Database\Context */
  protected $database;

  public function __construct(\Nette\Database\Context $database)
  {
    $this->database = $database;
  }
}

Tenhle BaseModel potom poděď (CartModel extends BaseModel) a tam používej databázi. V presenteru si potom injectni ten daný model a používej ho.
Třeba nějak takhle:
Model

class CartModel extends BaseModel
{
  public function getProducts ($user_id)
  {
    return $this->database->table('cart')
                          ->where('user_id', $user_id);
  }
}

Presenter

/** @var CartModel */
protected $cartModel;

public function injectCartModel (CartModel $cartModel)
{
  $this->cartModel = $cartModel;
}

public function actionCartDetail ()
{
  $this->template->cartProducts = $this->cartModel->getProducts($this->getUser()->getId());
}

Dál je potřeba model přidat jako službu do config.neon.

Tohle je cca základní použití modelu (s Nette Database Table) a připojení k presenteru.

Editoval CZechBoY (21. 8. 2015 23:50)

duke
Člen | 650
+
0
-

Jen pro upřesnění, název BaseModel zde není úplně nejšťastnější, neboť model typicky není jen nějaká služba, ale celá vrstva aplikace řešící její vnitřní logiku, tj. mnohost služeb, z nichž ne všechny musí využívat databázi. Vhodnější název mateřské třídy pro služby řešící načítání záznamů z databáze je např. BaseRepository (byť i zde by šlo namítnout, že ne všechny repozitáře musí načítat z databáze – v takovém případě pak spíše DatabaseRepository).

bigfoot
Člen | 9
+
0
-

Tak celou aplikaci píšu znovu a zlepšuju jí díky radám zde z fora, aby měla nějakou logiku a nebylo to odfláklé. Narazil jsem na následující problém: Už jsem si zavedl modely, ale asi uplně nejsem znalý jejich volání. Mám jednoduchý formulář na zápis do databáze a snažím se do databaze přistupovat přes model ale asi mi tam chybí nějaký krok. Formulář v pořádku zobrazím ale už nezapíšu do databaze kvůli chybě
`Nette\MemberAccessException
Cannot read an undeclared property App\Presenters\AdminPresenter::$model.
Je takhle aplikace logicky správně vystavěná? Podle toho erroru je neiniciovaná ta položka model že ano? Jak se tedy k funkci toho modelu dostat? Díky za rady.

Presenter s injectovaným modelem

use Nette,
    Nette\Application\UI\Form;
use App\Model;

class AdminPresenter extends BasePresenter {

 public $adminRepository;

    public function injectAdminRepository(Model\AdminRepository $service) {
        $this->adminRepository = $service;
    }
protected function createComponentPridatknihuForm()
{
    $form = new Form;

    $form->addText('nazev', 'Název:')
        ->setRequired();
   .
   .
   .
   $form->addSubmit('send', 'Publikovat komentář');
    $form->onSuccess[] = array($this,'zapisKnihu');

    return $form;
}
public function zapisKnihu(Form $form)
{
   $values = $form->getValues();
        $this->model->adminRepository->zapis($values);

        $this->flashMessage('Record updated!', 'success');
        $this->redirect('edit');
        }

A model:

class AdminRepository extends BaseRepository
{
 public function zapis($values)
{
                $knihaId = $this->getParameter('knihaId');

    if ($knihaId) {
        $kniha = $this->database->table('sklad')->get($knihaId);
        $kniha->update($values);
    } else {
        $kniha = $this->database->table('sklad')->insert($values);
    }

    $this->flashMessage('Příspěvek byl úspěšně publikován.', 'success');
    $this->redirect('Homepage);
        }
}

`

duke
Člen | 650
+
0
-

Místo:

$this->model->adminRepository->zapis($values);

… piš rovnou:

$this->adminRepository->zapis($values);

Modej je pomyslná vrstva, kterou však tvůj presenter pod názvem model nezná. Nemůže tedy pracovat s $this->model.

Pak je problém v modelové třídě AdminRepository. Zobrazování flash zpráviček a přesměrovávání není zodpovědností modelové vrstvy. Tj. volání metod flashMessage a redirect patří do presenteru, nikoli do modelu.

A konečně je dobrým zvykem pojmenovávat metody a proměnné anglicky.

EDIT: Ještě jsem přehlédl jeden problém a to je tvé volání:

$knihaId = $this->getParameter('knihaId');

To je něco, co by mohlo fungovat v presenteru, pokud knihaId je parametr akce presenteru, ale model o akcích presenteru (a jejich parametrech) nic neví. To id je třeba předat nějak jinak. Nejlépe asi jako součást $values. Buď to do $values přidej ručně, nebo použij hidden input ve formuláři. V modelu pak můžeš mít např.:

if (empty($values['id'])) {
	// insert
} else {
	// update
}

Editoval duke (24. 8. 2015 16:00)

bigfoot
Člen | 9
+
+9
-

Všem v místním fóru děkuji, obhájil jsem svoji práci za jedna a pochopil základy práce s Nette a MVC obecně a to mimo jiné díky vám.

Tomáš Votruba
Moderator | 1114
+
0
-

@bigfoot To rádi slyšíme, skvělá práce!