Ako správne získavať dáta z databáze a zobrazovať ich

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

Ahojte,

trochu som váhal a je mi jasné, že toto je otázka, ktorá Vás rozosmeje, no moje skúsenosti s Nette a MVC sú minimálne a ak sa neopýtam, nikdy nebudem vedieť, či je to správne:
Mám databázu firma, telefon, email.
Potrebujem výpis z tabuľky firma a zobraziť z tabuliek telefon a email relevantne hodnoty (telefónne číslo a e-mail pre danú firmu).
Podla ukážkovej aplikácie som spravil renderDefault() metódu v triede HomepagePresenter:

<?php
public function renderDefault() {
	$this->template->firmy = $this->database->table('firma')
		->order('nazov DESC');
    }
?>

V template default som teda vytvoril výpis nasledovne:

{foreach $firmy as $firma }
    <div class="firma">
	<h2 class="nazov">{$firma->nazov }</h2>
	<address>Adresa:<br />
	    {$firma->ulica } {$firma->cislo }<br />
	    {$firma->psc } {$firma->mesto }<br />
	    {$firma->stat }
	</address>
	{foreach $firma->related('telefon') as $telefon }
	    <span>Telefón: {$telefon->cislo }</span><br />
	{/foreach }
	{foreach $firma->related('email') as $email }
	    <span>E-mail: {$email->email }</span><br />
	{/foreach }
	<div class="popis">{$firma->popis }</div>
    </div>
{/foreach }

…je to správne? myslím to, že som získavanie telefónnych čísel a e-mailov riešil v template?

ďakujem Peter

David Matějka
Moderator | 6445
+
0
-

Ano, tento zapis je v souladu s filosofii NDBT, ale: https://forum.nette.org/…principy-mvc

Bambulko
Člen | 6
+
0
-

ja napriklad nerozumiem ani tomu, preco sa nacitanie tabulky ‚firmy‘ nenachadza v modeloch, ale v presentery

Azathoth
Člen | 495
+
+3
-

Myslím, že to je přesně to, na co narážel matej21. Ten výše uvedený kód funguje, ale správně by veškeré připojení k databázi měl obstarat model a presentery by měly komunikovat pouze s modelem a nikoli s databází.

japlavaren
Člen | 404
+
+1
-

ako napisal Azathoth, tento kod funguje. ale je neudrzatelny. osobne pouzivam vsetky vyhodu ndtb v modeloch a presenter je len most pre sablonu ktora to vypise

rozsirit takyto kod je totiz peklo…

Lkopo
Člen | 65
+
0
-

Tiez by ma zaujimalo, ako taky model v Nette vyzera, priklad na model som nenasiel, len ako pouzit model v presenteri.

Nejake zakladne pravidla a tak, napr. insertovanie, edits atd.

japlavaren
Člen | 404
+
0
-

no dobry model s ndtb je problem. asi kazdy ma vlastne riesenie (otazka je, co z toho je spravne…)

Lkopo
Člen | 65
+
0
-

Takže mi zrejme nezostáva nič iné len experimentovať a študovať kódy iných :D

PeterB
Člen | 12
+
0
-

…ak som správne pochopil, je to zlé, funkčné, ale zlé z pohľadu MCV.
No tak som zmetený, v podstate som nespravil nič extra, iba zobral základ zo vzorového príkladu a dorobil volanie z druhej tabuľky…

Viete mi prosím vysvetliť, ako to má byť správne?

ďakujem Peter

Azathoth
Člen | 495
+
+1
-

Pokud je nějaký příklad na správný model, tak se také hlásím.
@Lkopo Ale mohu lehce popsat architekturu svého modelu, který používám.
Je to vlastně spousta tříd, které injektuji do presenteru jako závislosti.
A ty třídy dělají aplikační logiku, mají napojení na databázi a tak podobně.
Mám například v modelu třídu UserHandler, která má veškeré metody potřebné pro práci s uživateli a každý presenter, který ji chce, ji dostane přes DI.
malá ukázka:

<?php

    private $customOtherDependecyFromModel;

    function __construct(dependencies) {
		//injecting all dependencies
    }

 	public function authenticate(array $credentials) {
        //some magic
        return $user;
    }

    public function getUserFromLoginName($loginName) {
	    //some magic
        return $user;
    }

    public function registerNewClient($firstName, $lastName, $email, $linkToAccountActivation) {
        //some magic, registering to database, sending registration e-mail
	}
    public function registerNewAdmin($firstName, $lastName, $email, $link) {
        //some magic, registering to database, sending registration e-mail
    }

	public function promoteToLector(Client $client, $commission) {
		//some magic, promoting and sending e-mail
    }

    public function activateAccount($activationParameter) {
        //some magic
    }

    public function setNewPassword($password, $activationParameter) {
        //some magic
    }
?>

a potom mám podobné třídy na další věci, mám messageHandler, který obstarává samotné odesílání zpráv, několik database handlerů, které mají samotné připojení k databázi (tato třída má pouze database handler jako závislost a sama přímo napojení na databázi nemá), notificationHandler, který generuje novinky o tom, co se děje, fileHandler, který obsluhuje úložiště uživatelů a tak podobně…

Editoval Azathoth (18. 9. 2014 20:35)

japlavaren
Člen | 404
+
+1
-

ja ndtb chapem asi takto – rozsireny nastroj ktory ma nenuti rozmyslat a skladat joiny…

cize ak vezmem klsicky model spranevy v dibi, toto iste sa da spravit s ndtb a je to jednoduchsie na napisanie (jednoducho napisem co chcem a nestaram sa o joiny a pod)…

cize model by mal nacitat data z tabuliek, zoskupit ich a predat do resenteru. ci pouzije ciste sql, dibi, ndtb, doctrine, nosql db… → odovzda data

Azathoth
Člen | 495
+
+1
-

@PeterB doporučuji toto: http://www.zdrojak.cz/…neho-navrhu/ dalo mi to hodně.
(pokud melu blbosti, opravujte mne)
U malých projektů je to jedno, ale u velkých už na tom záleží.
Snaž se, aby každá třída měla zodpovědnost pouze za jednu věc. Tedy presenter má mít na starosti pouze zobrazování a nějaké úkolování modelu, takže v presenteru by mělo být akorát
$this->template->firmy = $this->mujDatabazovyModel->dejMiVsechnyFirmy

A třída MujDatabazovyModel by měla poskytnou všechny firmy.
Pokud jsem správně pochopil mvc a single responsibility principle, tak presenter, tedy to, co poskytuje data uživateli, by vůbec neměl vědět o tom, co se používá pro připojení k databázi.

PeterB
Člen | 12
+
+2
-

…hmmm
Je mi úplne jasné, čo znamená MVC, alebo čo by mal znamenať, nikdy som ho nepoužíval, čiže mám znalosti iba teoretické, myslel som si, že mi vzorový príklad pomôže pochopiť, ako sa to robí správne v Nette, ale správne to tam nie-je a teraz nejde o to, či je to malý projekt, alebo mikro, ide o to, že vzorový príklad by mohol naviesť človeka, ako správne používať Nette (teda mohlo by tam byť, ako vytvoriť model na vytiahnutie dát z db, predanie dát do prezentera, ktorý ich spracuje a potom do template, aby ich vykreslil)

Oli
Člen | 1215
+
+2
-

@PeterB je a není to špatně. To co jsi udělal ty je VC (template + presenter). Ono to takhle je popsáno i v dokumentaci. Byly tu i nějaké diskuse jestli to je správně nebo špatně. Jde o to aby se začátečník „nezahltil“. Já jsem třeba začínal stejně, po napsání 2 presenterů jsem hodil práci s databází do modelu, ale komponenty vytvářel v presenteru. Pak jsem experimentoval, že jsem třeba formuláře vytvářel jako potomky třídy \Nette\Application\UI\Form, pak jako \Nette\Application\UI\Control až nakonec teď téměř všechny služby generuju pomocí továrničky a pokud je to trochu komplexní tak z toho dělám extension.

Tím chci říct, že to co jsi udělal ty je první krok k tomu ten kód napsat ve správném MVC. K tomu ale musíš dospět postupně. Něco uděláš a řekneš si, sakra tohle bych mohl udělat obecněji. Pokud si to neřekneš, tak to asi nepotřebuješ. Například firemní presentace o 10 stránkách a blogu by klidně šla napsat způsobem jak jsi naznačil. ;-)

Bambulko
Člen | 6
+
0
-

fascinuju ma tie vyrazy ;)
co je komponenta? co je tovarnicka? (sorry za off, ale nejak sa to vsade spomina ale rozumne vysvetlenie som zatial neobjavil :( )

PeterB
Člen | 12
+
0
-

Azathoth napsal(a):

$this->template->firmy = $this->mujDatabazovyModel->dejMiVsechnyFirmy

…tomuto chápem, mám teda iba jedinú otázku, ako by sa mal volať model ktorý vytvorím (myslím, podľa obecných zásad Nette, alebo modely sú načítané všetky – usudzujem podľa use Nette, App\Model; ), kde potom definujem triedu a tieto metódy?

Lkopo
Člen | 65
+
0
-

To tam v dokumentacii je, ze to mas dat do zlozky model a nazov moze byt Article.php.

Ja sa zajtra s tym chystam pohrat.

Editoval Lkopo (18. 9. 2014 21:58)

Azathoth
Člen | 495
+
+2
-

@PeterB
Ve složce, tedy i v namespace App\Model si vytvoříš PHP Class, nějak si ji pojmenuješ a začneš tam psát ty metody, dáš tam jako property $database a tak dále. A potom ji v neon.config přidáš mezi služby (jak je services: tak tam přidáš nový řádek, kde bude pomlčka a jméno té php class spolus s celým namespace, takže tam bude:
services:

  • App\Model\MyDatabaseHandler (nebo jiný název, který zvolíš pro tu třídu)

) a injectuješ do presenteru, asi takhle:

/**
* @var \App\Model\MyDatabaseHandler
* @inject
*/
public $myDatabaseHandler;

Podívej se na nette sandbox, jakým způsobem je dělaný UserManager. Tak takhle bys to měl mít, obdobně.
Ale název té třídy je čistě na tobě, prostě to pojmenuj tak, abys tomu rozuměl a aby název přibližně odpovídal tomu, co to bude dělat.

Azathoth
Člen | 495
+
0
-

@Bambulko komponentu ti asi přesně nevysvětlím, na to si netroufnu, ale továrnička (ta zdrobnělina mne velmi irituje, ale celou komunitu na továrnu nepřeučím) je metoda, kterou zavoláš a ona ti vrátí nějaký objekt.
Takže místo $newObject = new Object(); zavoláš $newObject = $objectFactory->create(); nebo něco podobného.

Oli
Člen | 1215
+
+1
-

@PeterB Na foru bude urcite nekolik ukazek kodu jak se to ma delat. Myslenka je ale zhruba takovahle:
Napises nejakou tridu, ktera se stara o praci s databazi, rekneme treba clanky:

namespace App\Model;

class ArticleRepository extends \Nette\Object
{

	private $context;

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

	public function findArticleById($id)
	{
		return $this->context->table('articles')->get($id);
	}

Potom tu třídu zaregistruješ jako službu do config.neon (musí být někde, kam „vidí“ Nette Autoloader, typicky řekněme někde v App + podsložky). Protože jsi to zaregistroval jako službu, tak ti tam ten Context Nette injectne automaticky.:

services:
	- App\Model\ArticleRepository
	# můžeš si tu službu i pojmenovat - mujClanek: App\Model\ArticleRepository
	# a potom k tomu přistoupis jako k @mujClanek

To že jsi to zaregistroval jako službu v configu ti dovoluje to načíst pomocí DI v presenteru.

class ArticlePresenter extends BasePresenter
{

	public function __construct(\App\Model\ArticleRepository $articleRepository)
	{
		$this->articleRepository = $articleRepository;
	}


	public function renderDetail($id)
	{
		$this->template->article = $this->articleRepository->findArticleById($id);
	}
}

@Bambulko komponenty jsou jednoduše řečeno znovupoužitelné kusy kodu. Například paginator (stránkovač) chceš vždycky, když vypisuješ nějakej přehled např. článků. Je blbost to psát pořád dokola, tak si uděláš komponentu, které předáš celkový počet článků a kolik jich chceš zobrazit a ona ti to „rozřeže“. Potom když chceš takhle vypsat novinky, tak už si zavoláš jen tu komponentu a nastavíš ji.

Továrnička není nic jiného než návrhový vzor factory. Prostě metoda, která vytvoří jinou třídu. Její výhoda je, že pokaždé vytvoří novou instanci.

Editoval Oli (18. 9. 2014 22:11)

PeterB
Člen | 12
+
+1
-

…no, ako tak pozerám, tak s Vašou pomocou to nebolo až také tápanie…
Nespravil som nič, iba prerobil horný príklad nasledovne:
Model:
DatabaseHandler s rovnakou triedou

<?php
namespace App\Model;

use Nette;

class DatabaseHandler extends Nette\Object {

    /** @var \Nette\Database\Context */
    private $database;

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

    public function vsetkyFirmy() {
	return $this->database->table('firma')->order('nazov DESC');
    }
}
?>
  • pridané do services v config.neon

V HompagePresenter:

<?php
/** @var \App\Model\DatabaseHandler @inject */

    public $firmy;

    public function renderDefault() {
	$this->template->firmy = $this->firmy->vsetkyFirmy();
    }
?>

…a default.latte ostal rovnaký

…je to takto správne? Robí to presne to isté

Peter

Lkopo
Člen | 65
+
0
-

Mám takú otázku ohľadom toho, či záznamy vkládať cez presenter alebo model, keďže takisto musím v presenteri povedať, že tieto $values chcem vložiť/upraviť.

Čiže v modeli mám to asi takto

public function findOneById($id)
{
    $post = $this->database->table(self::TABLE_NAME)->get($id);

    return $post;
}

A v presenteri mám:

$post = $this->postRepository->findOneById($postId);
$post->update($values);

Je to správne riešenie? Keďže na insert sa v modeli robí metóda (napr. add), ale na update alebo delete si nie som istý.

Editoval Lkopo (18. 9. 2014 23:27)

Azathoth
Člen | 495
+
+1
-

@PeterB Je to mnohem lepší. Sice to dělá to samé, ale ve chvíli, kdy bys chtěl kód předělávat, tak tohle bude mnohem lepší.

  1. pokud budeš chtít přejít na jinou knihovnu/framework pro práci s databází nebo budeš přejmenovávat tabulky v sql databázi, tak nebudeš muset měnit presenter.
  2. pokud budeš chtít použít výpis všech firem ještě v jiném presenteru, tak nebudeš kopírovat table(‚firma‘)->order(‚nazov DESC‘);, ale zavoláš si metodu vsetkyFirmy(); což je praktické, když budeš chtít upravit získání všech firem (například z DESC budeš chtít přejít na ASC), takhle to změníš jenom na 1 místě a ne všude, kde seznam všech firem používáš.

A možná se najdou ještě nějaké výhody.

Editoval Azathoth (18. 9. 2014 23:29)

Azathoth
Člen | 495
+
0
-

@Lkopo nevím, jak ostatní, ale já mám na všechno model, v presenterech se snažím nic neupravovat, ale vždy volat model. I když tohle má asi každý programátor jinak.

Editoval Azathoth (18. 9. 2014 23:31)

Lkopo
Člen | 65
+
0
-

No lenže v tomto prípade sa mi to zdá menej efektívne robiť cez model, keďže by som musel vkládať aj objekt daného postu a potom $values, čiže o 1 arg zbytočne navyše.

Azathoth
Člen | 495
+
+1
-

Ano, je to víc psaní než kdybys to dělal v presenteru. Ale já se snažím držet single responsibility principle a to toho mi úprava dat presenterem nějak nesedí.

japlavaren
Člen | 404
+
+2
-

insert/update/delete, related/ref vsetko v modeli. model by mal predavat polozky (polozka = array/object) a presenter by nemal vediet o strukture db a pod…

Lkopo
Člen | 65
+
0
-

No a ako poviem modelu len po odoslaní formulára updatne tabuľky? V návode bolo v presenteri:

$post = $this->database->table('posts')->get($postId);
$post->update($values);

No a ja mám

$post = $this->postRepository->findOneById($postId);
$post->update($values);

Naozaj je lepšie to spraviť týmto štýlom?

$post = $this->postRepository->findOneById($postId);
$this->postRepository->updateData($post, $values);

Editoval Lkopo (19. 9. 2014 0:20)

Azathoth
Člen | 495
+
0
-

Já si stojím za tím, že je ten 3. způsob je nejlepší, ale řekl bych, že to je opravdu podle chuti. Já se snažím držet single responsibility principle a 3. řešení je více single responsibility než to nad ním.

Bambulko
Člen | 6
+
0
-

Naozaj je lepšie to spraviť týmto štýlom?

$post = $this->postRepository->findOneById($postId);
$this->postRepository->updateData($post, $values);

Ano ;)

japlavaren
Člen | 404
+
+2
-

Lkopo napsal(a):

No a ako poviem modelu len po odoslaní formulára updatne tabuľky? V návode bolo v presenteri:

$post = $this->database->table('posts')->get($postId);
$post->update($values);

No a ja mám

$post = $this->postRepository->findOneById($postId);
$post->update($values);

Naozaj je lepšie to spraviť týmto štýlom?

$post = $this->postRepository->findOneById($postId);
$this->postRepository->updateData($post, $values);

problem tvojho riesenia vznikne, ked potrebujes updatovat viac tabuliek, alebo budes mat vo formulari inputy, ktore nemas v db ako stlpce

ja to robim tak, ze values z formularu predam modelu, ten si vydzobe co potrebuje a spravi update