Doctrine, nebo Nette Database? Problém času a počtu dotazů

Polki
Člen | 553
+
0
-

Zdarec.
Doctrine už od prvního setkání, stejně jako ostatní ORM věci nemám v lásce.

Utvrzuje mě v tom i následující věc:

Dostal jsem projekt po profíkovi na doctrine, kde byl výpis produktů z tabulky produkty, u každého bylo jméno prodejce a další detaily, včetně toho, jestli jsem si produkt koupil.

No a teď jsem zůstal hledět jak zabitý. Na stránce s produkty je stránkování. Stránkování je na 10 produktů na stránce.
Když jsem koukl na tracy na to, kolik je zde dotazů apod, jelikož mi to přišlo nějak dlouho…

Když jsem to viděl, tak jsem málem omdlel. Udělal jsem tu samou věc ale pomocí Nette Database.

Srovnání:
Celkový počet produktů v Databázi (testovací data na produkci je jich 25000): 370
Celkový počet produktů na stránce: 10

Výsledky od tracy:

Doctrine | Nette Database
Čas: 280ms | 6ms
Queries: 68 | 8

Otázka pro vás: V čem a proč je lepší používat doctrine? Proč o ní všichni básní?

PS: Uvádím zde testové informace, jelikož díky doctrine na ostrém serveru vyprší při zapnuté TRACY pro mou IP timeout :D

MajklNajt
Člen | 470
+
+5
-

Ten profík nebol asi až taký profík… Jedna vec je naprototypovať entity, druhá je potom optimálne dáta získavať – pomocou DQL, nie len findBy nad repozitarmi.

Hlavným prínosom ORM je odtienenie od DB, pracuješ s entitami, ktoré majú jasne API, nie len s nejakými riadkami a stĺpcami…

David Klouček
Člen | 57
+
0
-

Problém neni v Doctrine, ale ve špatně udělanym výpisu. Naopak bych tipnul, že u verze s Doctrine bylo míň dotazů než u NDB, protože v Doctrine se většinou dělaj joiny (asi ti tam chybí), ale v NDB hromada dotazů SELECT .. WHERE id IN (..).

Výhoda ORM je, že pracuješ s ucelenejma objektama a víš co v nich je, můžeš si postavit v entitách logiku. Samozřejmě to má i svoje nevýhody jako nižší výkon – tahaj se i nepotřebný sloupce, mapování na objekty něco stojí..

Mysteria
Člen | 797
+
+3
-

Osobně proto, že mám data pěkně v objektech, kde vidím hnedka vazby, funguje napovídání a podobné věci. Z neORM věcí mi většinou vypadne pole a tím to končí, blbě se s tím pracuje. Ale zase to vyžaduje umět daný nástroj používat, protože to tvoje vypadá jako klasický problém někoho kdo neví, jak Doctrine funguje.

Pravděpodobně je to napsané tak, že jedním dotazem si vytáhne těch 10 produktů a pak to prochází foreachem a dotahuje další data, což ve výchozím nastavení udělá pěkně pro každý záznam další dotaz (ano, v tomhle je Nette DB lepší, protože to sama dělá za uživatele out of box). Nicméně pokud se správně udělá první dotaz tak, aby se vytáhly i další potřeba data (= přidají se JOINy), tak pak bude výsledek velmi podobný Nette DB.

Pokud se do toho chceš více zavrtat, tak doporučuji tenhle článek, kde se probírají nejčastějí chyby při použití Doctrine. Pokud to teda samozřejmě nehodláš celé přepsat do Nette DB. :)

Polki
Člen | 553
+
-4
-

David Klouček napsal(a):

Problém neni v Doctrine, ale ve špatně udělanym výpisu. Naopak bych tipnul, že u verze s Doctrine bylo míň dotazů než u NDB, protože v Doctrine se většinou dělaj joiny (asi ti tam chybí), ale v NDB hromada dotazů SELECT .. WHERE id IN (..).

Jistě. Dokonce co jsem koukal, do tracy na dotazy, tak dělala spoustu JOINů, které vůbec nebyly potřeba. Prostě si vytáhla data jen tak protože se jí chtělo. Dál v aplikaci se s nimi nepracovalo.

Výhoda ORM je, že pracuješ s ucelenejma objektama a víš co v nich je, můžeš si postavit v entitách logiku. Samozřejmě to má i svoje nevýhody jako nižší výkon – tahaj se i nepotřebný sloupce, mapování na objekty něco stojí..

To moc jako výhodu nevidím. Krom toho, že se musí vytvářet nová instance třídy, která musí mít v sobě nějakým způsobem nadefinovány všechny sloupce a ty také tahá z db to je velice obtěžující skutečnost. Ne jen, že to že bude programátor psát více rutinního kódu a zabere mu to čas, který může věnovat vývoji a řešení opravdových věcí, tak se štve s tím, aby doctrina mohla tahat to co on chce a taky ji musí říct přesně co je třeba jak a kdy? takže se ještě o ni starat jak o dítě? Další ztracený čas a ještě navíc vyšší HW nároky? Nevím no.

Pokud jde o logiku, tak v případě NotORM se mi o to postará model. Zavolám jeho metodu a on mi vrátí výstup. A to, že mi NotORM vrátí pole místo objektu je mi jedno. Když vím, jaké mám sloupce v DB, tak k nim z toho pole přistupuji úplně stejně jako v případě objektu a dokonce dostanu i stejné hodnoty.

Nevýhoda samozřejmě je ta, že si nemůžu vynutit obsah jestli je správný. O to by se ale mělo podle mě starat už samotné připojení k DB aby bylo dost robustní a nemohlo se stát podvržení ne?

Mysteria napsal(a):
Osobně proto, že mám data pěkně v objektech, kde vidím hnedka vazby, funguje napovídání a podobné věci. Z neORM věcí mi většinou vypadne pole a tím to končí, blbě se s tím pracuje. Ale zase to vyžaduje umět daný nástroj používat, protože to tvoje vypadá jako klasický problém někoho kdo neví, jak Doctrine funguje.

Osobně jsem napovídání ještě nevyužil ani v DOCTRINE ani v NDB. Prostě v 90% případů jen natáhneš data z DB, vychrlíš je do výpisu, nebo do formuláře a tím končíš.
Ba naopak jsem si všiml, že DOCTRINE nějak špatně pracuje s datem a pár dalších věcí, což má za následek místo:
$form->setDefaults(); a $db->table(‚nazev‘)->insert($form->values);

napsání pro každou hodnotu, která se může do formuláře propisovat zvlášť řádek, kde se tyto hodnoty upravují a u formuláře se 100 poli je pak továrna na formulář velká, že se stydíš za to to vůbec používat.
Dále jsem třeba při výpisech v šablonách, nebo modelech už taky narazil na to, že předaná entita nenapovídá ani své property, natož tak nějaké další vazby.

V rámci NDB je vrácený výsledek sice ActiveRow, který nemá našeptávání, ale vzhledem k tomu, že v Doctrině to našeptávání funguje jen hned potom, co si nechám entitu vrátit…

Pravděpodobně je to napsané tak, že jedním dotazem si vytáhne těch 10 produktů a pak to prochází foreachem a dotahuje další data, což ve výchozím nastavení udělá pěkně pro každý záznam další dotaz (ano, v tomhle je Nette DB lepší, protože to sama dělá za uživatele out of box). Nicméně pokud se správně udělá první dotaz tak, aby se vytáhly i další potřeba data (= přidají se JOINy), tak pak bude výsledek velmi podobný Nette DB.

No zrovna tady je dělané stránkování a nestačil jsem se divit. Nejdřív kvůli počtu stránek načte počet záznamů. Dosud ok.
Pak ale zavolá toto:

Model:

$results = $this->em->createQueryBuilder();
               	    ->select('p')															// Select product
               	    ->from(Product::class, 'p')												// from product
               	    ->join('p.client', 'c')													// Client which bought product
			   	    ->addWhere('c.user = :user')->setParameter(':user', $this->user->id)	// Merchant of client
               	    ->setMaxResults($paginator->itemsPerPage) 								// 10 items
               	    ->setFirstResult($paginator->offset)		 							// page
			   	    ->getQuery()->getResult();

Šablona:

<tbody>
	<tr n:foreach="$results as $result">
    	<td><b>{$result->name}</b></td>
		<td>
           	{$result->client->name} {$result->client->surname}
        </td>
        <td>{$result->created->format('d. m. Y')}</td>
        <td>{$result->client->user->getFullName()}</td>
        <td class="table-options">
        	<a onclick="return confirm('Opravdu chcete smazat tento obchod?')" n:href="remove! $result->id" class="btn-icon"><i class="material-icons">delete</i></a>
        </td>
	</tr>
</tbody>

Toto co jsem napsal udělá (bez počítání počtů stránek, tahání aktuálního uživatele počítám čistě tento kousek kódu) 51 dotazů. A každý natáhne všechna data místo pouze jména a příjmení se natáhnou všechny údaje o klientovi a ne jen údaje z jeho tabulky, ale ještě navíc údaje z tabulek mu přidružených, jako například faktury apod… Prostě si doctrina udělala JOIN navíc, i když tam nemá co dělat.

V NetteDB:
Model:

$result = $this->database->table('product')
						 ->where('client.user_id', $this->user->id)
						 ->page($paginator->offset, $paginator->itemsPerPage, $pagesCount);

Šablona:

<tbody>
	<tr n:foreach="$results as $result">
    	<td><b>{$result->name}</b></td>
		<td>
           	{$result->client->name} {$result->client->surname}
        </td>
        <td>{$result->created->format('d. m. Y')}</td>
        <td>{$result->client->user->name} {$result->client->user->surname}</td>
        <td class="table-options">
        	<a onclick="return confirm('Opravdu chcete smazat tento obchod?')" n:href="remove! $result->id" class="btn-icon"><i class="material-icons">delete</i></a>
        </td>
	</tr>
</tbody>

Jak je vidno, šablona se téměř nijak nezměnila. Každopádně NetteDB udělalo (bez paginatoru stejně jako nahoře) pouze 3 dotazy a to jeden na tabulku ‚product‘ (s joinem na filtrovací tabulku), odkud vytáhlo jen jméno produktu a datum vytvoření, druhý na tabulku client, odkud to vytáhlo jen jméno a příjmení klienta a třetí na tabulku user, odkud to vytáhlo zase jen jméno a příjemní.

Co je tedy v doctrine dotazu špatně?

Pokud se do toho chceš více zavrtat, tak doporučuji tenhle článek, kde se probírají nejčastějí chyby při použití Doctrine. Pokud to teda samozřejmě nehodláš celé přepsat do Nette DB. :)

Určitě pročtu díky.

PS: nejvíc mě zarazil dotaz na vyhledání pouze oněch 10 výpisů na stránku u DOCTRINE (to za select jsem nepsal, jelikož tam bylo asi 100 sloupců vyjmenovaných):

FROM product p0_ 											// vyber produkty to je ok
INNER JOIN client c1_ ON p0_.client_id = c1_.id 			// Vyber k nim klienty taky ok
LEFT JOIN company c2_ ON c1_.id = c2_.id 					// Proč ale musíme dělat join na tabulku, abychom věděli, kde klient pracuje?
LEFT JOIN view_to_client v3_ ON c1_.id = v3_.client_id 		// Proč se dělá join, kteří obchodníci na klienta vidí navíc kromě přímého obchodníka, když víme, že bereme jen klienty, které má přímo pod sebou pod sebou daný obchodník? (bez náhledu)
INNER JOIN user u4_ ON c1_.user_id = u4_.id 				// Výběr z tabulky obchodníků to je ok
WHERE u4_.id = 3 											// Vyselektování jen toho jednoho aktuálního to je ok
LIMIT 1000 													// Limit 1000? WTF? však jsem zadal jako počet prvků na stránce velikost 10... Proč to tahá 1000 prvků, když jich to má natáhnout 10?!?!?!?!
OFFSET 0

Moje zloba na ORM Doctrine tímto stoupla :D

Editoval Polki (30. 7. 2019 10:37)

Kamil Valenta
Člen | 752
+
+2
-

Jo, je to důvod, proč Doctrine také nepoužívám, ta režie navíc tam prostě je, ať už se argumentuje optimalizací jak chce. Sama Doctrine to svého času (nevím jak teď) měla přímo v dokumentaci, že prostě člověk musí s nějakou režií navíc počítat. Také mi vadí to tahání dat, která nakonec nejsou vůbec potřeba. Ona to Doctrine „řeší“ parciálními entitami, čímž ale trochu sama sebe popírá.

Je to přístup, který upřednostňuje pohodlí programátora. Ještě mě nic nepřesvědčilo, že se to hodí na velké projekty (čím složitější DB, tím se mi zdá Doctrine pomalejší). Na běžné weby je to asi celkem jedno…

EDIT: to, co jsi ale připsal do P.S. je divoký, to už bych úplně neházel na Doctrine. I NDBS může být hloupé, pokud se špatně použije.

Editoval kamil_v (30. 7. 2019 10:51)

MajklNajt
Člen | 470
+
0
-

@Polki objekty aj celé OOP bude mať vždy svoju réžiu, je to daň za prehľadný a udržateľný kód – kľudne môžeš písať špagety a ručné dotazy do mysql, nemusíš používať ORM, ani žiadny framework, ani dodržiavať princípy MVC :)

ali
Člen | 342
+
+3
-

@MajklNajt Opravdoví programátoři nepoužívají frameworky :D

kocourPB
Člen | 47
+
0
-

Dokonce co jsem koukal, do tracy na dotazy, tak dělala spoustu JOINů, které vůbec nebyly potřeba. Prostě si vytáhla data jen tak protože se jí chtělo. Dál v aplikaci se s nimi nepracovalo.

Hod sem prosim tu query, aby sme boli v obraze.

Krom toho, že se musí vytvářet nová instance třídy, která musí mít v sobě nějakým způsobem nadefinovány všechny sloupce a ty také tahá z db to je velice obtěžující skutečnost. Ne jen, že to že bude programátor psát více rutinního kódu a zabere mu to čas, který může věnovat vývoji a řešení opravdových věcí, tak se štve s tím, aby doctrina mohla tahat to co on chce a taky ji musí říct přesně co je třeba jak a kdy? takže se ještě o ni starat jak o dítě? Další ztracený čas a ještě navíc vyšší HW nároky?

O tom prave ORM je, ze mas databazu namodelovanu v PHP classach …

Toto co jsem napsal udělá (bez počítání počtů stránek, tahání aktuálního uživatele počítám čistě tento kousek kódu) 51 dotazů. A každý natáhne všechna data místo pouze jména a příjmení se natáhnou všechny údaje o klientovi a ne jen údaje z jeho tabulky, ale ještě navíc údaje z tabulek mu přidružených, jako například faktury apod… Prostě si doctrina udělala JOIN navíc, i když tam nemá co dělat.

V sablone mas:

<td>{$result->client->user->getFullName()}</td>

Takze mas tam vazbu z produkt ⇒ client ⇒ user. Takze asi kvoli tomu ten join naviac.

Limit 1000? WTF? však jsem zadal jako počet prvků na stránce velikost 10

No vydumpuj si jestli si tam opravdu zadal 10 do toho ->setMaxResults().

Editoval kocourPB (30. 7. 2019 11:00)

MajklNajt
Člen | 470
+
+2
-

@ali skutoční programátori nerobia v PHP :D

kocourPB
Člen | 47
+
+2
-

@Polki

Inak opravdu ta nikto nenuti pouzivat ORM. Je to kazdeho volba. Na niektore projekty sa hodi viac a na niektore menej … ma svoje vyhody/nevyhody.

Ale nerozvijal by som tu flame o tom aka je Doctrine blba a robi si co chce. To nie je pravda. Mate ten kod neoptimalizovany. Takze asi tolko k tvojmu prispevku.

Polki
Člen | 553
+
+1
-

@kocourPB

Ale nerozvijal by som tu flame o tom aka je Doctrine blba a robi si co chce. To nie je pravda. Mate ten kod neoptimalizovany. Takze asi tolko k tvojmu prispevku.

Nejde o fámu. Jde o reálná data, která reálně tahá (podle záznamu z TRACY) prosil jsem, aby mi někdo napsal ten samý kód optimalizovaný, abych viděl, kde je chyba. To se nestalo.

Hod sem prosim tu query, aby sme boli v obraze.

v tom příspěvku na který reaguješ je, nebo ne? Jasně jsem tam popsal, že v selectu, kde jsem tahal 10 produktů na stránku, se tahá kromě klienta a usera i další tabulky. Dostaz si můžeš projít.

No vydumpuj si jestli si tam opravdu zadal 10 do toho ->setMaxResults().

Ano opravdu jsem to tam zadal. Všimni si, že v NetteDB zadávám stejnou proměnnou a tahá jich to opravdu jen 10… Stejný problém to dělá, když tam napíšu čislo 10 natvrdo.

@MajklNajt

@Polki objekty aj celé OOP bude mať vždy svoju réžiu, je to daň za prehľadný a udržateľný kód – kľudne môžeš písať špagety a ručné dotazy do mysql, nemusíš používať ORM, ani žiadny framework, ani dodržiavať princípy MVC :)

Jistě. Když napíši aplikaci v čistém PHP, bude rychlejší než v Nette, když ji napíšu bez tříd, tak možná bude rychlejší, než když použiju objektový model.

O tom ale otázka nebyla. Jinými slovy: Framework použiji, jelikož za mě řeší spousty věcí, například bezpečnost, logické oddělení kódu atd.. Takže ve výsledku bez frameworku bude kód rychleji běžet, ale s ním bude rychleji napsaný a nebude muset programátor vše ošetřovat sám.

Úplně stejně to funguje i mezi použitím ORM a NotORM. NotORM rychlejší, než ORM, ale problém je, že tady již neplatí jednoduchost zápisu. Když použiji ORM, tak místo toho, aby to můj zápis zjednodušilo a udělalo to věci za mě, tak mě to nutí psát dotazy a to tak, že místo 5 příkazů se musím naučit 50, místo 4 řádků na select musím napsat 12, a ještě navíc musím přidat třídu, která v případě rozsáhlejší db může mít třeba i 1000 řádků, což je 1000 řádků kódu, které bych mohl využít jinde.

Osobně v tom nevidím žádný přínos ba naopak.

Moje otázka zněla tedy, jaký přínos v tom vidí ostatní, protože se to hojně používá a tak tedy nějakou výhodu, která převáží misku vah ze zdlouhavého kódu, starání se o ni a práce navíc, převáží misku vah na druhou stranu abych řekl jo to je dobré v tomto případě začít používat.

Rozhodně od toho nechci nikoho odrazovat. Jen bych chtěl taky uvidět to, co vidí všichni, kteří to používají.

Polki
Člen | 553
+
0
-

@kamil_v

EDIT: to, co jsi ale připsal do P.S. je divoký, to už bych úplně neházel na Doctrine. I NDBS může být hloupé, pokud se špatně použije.

Jasně to je pravda. Jenže já tam žádný špatný použítí nevidím (doctrine) Pokud ho tam vidíš, prosím oprav mi kód :) Budu rád za každou připomínku

@kocourPB

Takze mas tam vazbu z produkt ⇒ client ⇒ user. Takze asi kvoli tomu ten join naviac

Nemyslím, jelikož client má vždy cizí klíč na svého usera. Nevidím důvod, proč dělat 2 zbytečné joiny a to jeden na firmu, ve které client pracuje a druhý na náhledy jiných userů na tohoto clienta, když se vypisuje jen jeho user.

A to vůbec nepíšu o tom, že tento dotaz se dělal v momentě, kdy se zavolal model.
V tom cyklu například u {$result->client->name} se pro každý $result v $results zavolal dotaz do db na entitu client a to samé u{$result->client->user->name}

MajklNajt
Člen | 470
+
+10
-

@Polki

dávaš sem útržky kódov, z ktorých vôbec nie je vidno, ako vyzerá entita Product, Client či User, takže analyzovať konkrétne použitie je bezpredmetné…

čo sa týka výhod a prínosov doctriny, padlo ich tu niekoľko a od niekoľkých ľudí, myslím, že sumár si vieš dať dokopy aj sám, ak tie prínosy pre teba nemajú význam, používaj nette db

každopádne, ak chceš doctrinu hodnotiť, najprv si preštuduj dokumentáciu, nauč sa ju používať, napíš si v nej vlastnú aplikáciu (nie hodnotiť to, čo vyrobil „profík“), potom otestuj, až potom sa podeľ o svoje poznaky a verejne vyhlás rozsudok :)

howgh

Polki
Člen | 553
+
-1
-

@MajklNajt

dávaš sem útržky kódov, z ktorých vôbec nie je vidno, ako vyzerá entita Product, Client či User,

Product:

<?php

namespace App\Model;

use Doctrine\ORM\Mapping as ORM;
use Kdyby;

/**
 * @ORM\Entity
 */
class Product extends BaseEntity {

    use Kdyby\Doctrine\Entities\Attributes\Identifier;
    use Kdyby\Doctrine\Entities\MagicAccessors;

    /**
     * @ORM\Column(type="datetime")
     */
    protected $created;

    /**
     * @ORM\Column(type="string", nullable=true)
     */
    protected $name;

    /**
     * @ORM\Column(type="integer", nullable=true)
     */
    protected $pocet = 1;

    /**
     * @ORM\ManyToOne(targetEntity="\App\Model\Client\Client", inversedBy="products")
     */
    protected $client;

    /**
     * @ORM\Column(type="string", nullable=true)
     */
    protected $status;


}

Client:

<?php
namespace App\Model\Client;

use Doctrine\ORM\Mapping as ORM;
use Kdyby;
use App\Model\BaseEntity;

/**
 * @ORM\Entity
 */
abstract class Client extends BaseEntity
{

    use Kdyby\Doctrine\Entities\Attributes\Identifier;
    use Kdyby\Doctrine\Entities\MagicAccessors;

    /**
     * @ORM\ManyToOne(targetEntity="App\Model\User", inversedBy="clients")
     */
    protected $user;

    /**
     * @ORM\ManyToOne(targetEntity="App\Model\Company", inversedBy="clients")
     */
    protected $company;

    /**
     * @ORM\Column(type="string")
     */
    protected $rc;

    /**
     * @ORM\Column(type="string")
     */
    protected $note;

    /**
     * @ORM\OneToMany(targetEntity="App\Model\Product", mappedBy="client")
     */
    protected $products;

    /**
     * @ORM\OneToMany(targetEntity="App\Model\Client\ViewToClient", mappedBy="client", cascade={"persist", "remove"})
     */
    protected $visibleFor;

    /**
     * @ORM\ManyToOne(targetEntity="App\Model\Profession", inversedBy="clients")
     */
    protected $profession;

    public function getFullName()
    {
        return $this->name . ' ' . $this->surname;
    }

User:

<?php
namespace App\Model;

use Doctrine\ORM\Mapping as ORM;
use Kdyby;
use Nette\Utils\Arrays;

/**
 * @ORM\Entity @ORM\HasLifecycleCallbacks
 */
class User extends BaseEntity
{

    use Kdyby\Doctrine\Entities\Attributes\Identifier;
    use Kdyby\Doctrine\Entities\MagicAccessors;

    /**
     * @ORM\Column(type="string", nullable=true)
     */
    protected $title;

    /**
     * @ORM\Column(type="string")
     */
    protected $name;

    /**
     * @ORM\Column(type="string")
     */
    protected $surname;

    /**
     * @ORM\Column(type="string", nullable=true)
     */
    protected $password;

    /**
     * @ORM\Column(type="string")
     */
    protected $phone;

    /**
     * @ORM\Column(type="string", unique=true)
     */
    protected $email;

    /**
     * @ORM\Column(type="string", nullable=true)
     */
    protected $ic;

    /**
     * @ORM\OneToMany(targetEntity="\App\Model\Client\Client", mappedBy="user")
     */
    protected $clients;

    /**
     * @ORM\OneToMany(targetEntity="App\Model\Client\ViewToClient", mappedBy="user", cascade={"persist", "remove"})
     */
    protected $visibleClients;

    public function __construct()
    {
        $this->created = new \DateTime();
        $this->visibleClients = new \Doctrine\Common\Collections\ArrayCollection();
    }

    public function getFullName()
    {
        return $this->name . ' ' . $this->surname;
    }
}

Tady máš entity co jsi chtěl z mé demo aplikace vytažené z ostré na odzkoušení. Zbytek kódu je jen model, který vrací $results z příspěvku výše a předává jej rovnou do šablony viz ten samý příspěvek

>takže analyzovať konkrétne použitie je bezpredmetné…

Teď už to zvládneš? nebo ti chybí na analýzu, proč se v tomto případě chová doctrine takto ještě neco?

čo sa týka výhod a prínosov doctriny, padlo ich tu niekoľko a od niekoľkých ľudí, myslím, že sumár si vieš dať dokopy aj sám, ak tie prínosy pre teba nemajú význam, používaj nette db

Nepadla žádná pokud vím.

Psalo se tu: | Realita je:
Díky entitám funguje napovídání | metoda find() vrací EntityManager a metoda ->getResult() vrací AbstractQuery. Takže ani jedna nenapovídá property dané entity. Je nejprve třeba si explicitně toto na entitu převést. O výhodu se tedy rozhodně nejedná zvlášť, když i při explicitním převodu stačí poslat instanci jinam a napovídání je totam, pokud si nevynutíš datový typ. Navíc, pokud si u NDB uděláš v modelu, který ti vrací activeRow uděláš konstanty s názvy sloupců v db, tak přistupuješ pak k oněm hodnotám v ActiveRow skoro stejně jako k property v entitě, není tu rozdíl jen s tím, že ty konstanty ti IDE napovídá vždy i bez vynucování datového typu
kód je přehlednější | Obecně si myslím, že mít jednu třídu jako model pro práci s databází, která dělá nad ActiveRow nějaké operace a získání dat je na 3 řádky, je daleko přehlednější, než mít tuto třídu a k ní ještě jednu jako entitu, ve které je spousta balastu, který není vůbec třeba při použití NotORM. 2× více tříd a minimálně 2× více kódu(a to jsem mírný) není podle mě znak přehlednosti. Ba naopak.
Pracuješ s entitami, které mají jasné API, ne jen s řádky a sloupečky. | I pole, které mi vrací NotORM má jasné API. Nemá sice vlastnosti, ale pořád je tam způsob zápisu, který se musí dodržovat. Jinak jestli voláš $user->name nad entitou nebo voláš $user->name nad ActiveRow, tak nevím jak ty, ale já tam nevidím rozdíl. A pokud jde o chování entity. Modelová vrstva je přesně pro tohle dělaná jestli se nepletu.
Výhoda ORM je, že pracuješ s ucelenejma objektama a víš co v nich je, můžeš si postavit v entitách logiku | Já osobně vím, co je i v tom ActiveRow. Pokud to nekdo nevidí, je tu DB, která ti vyplivne i krásný obrázek ER diagramu (který se stejně u každého většího projektu tvoří a tiskne, aby byl přehled, nehledě na to, jestli se používá doctrine, nebo ne), který si můžeš vytisknout a použít jako podložku pod myš. Tak budeš vždy vědět, co v daném ActiveRow je.

každopádne, ak chceš doctrinu hodnotiť, najprv si preštuduj dokumentáciu, nauč sa ju používať, napíš si v nej vlastnú aplikáciu (nie hodnotiť to, čo vyrobil „profík“), potom otestuj, až potom sa podeľ o svoje poznaky a verejne vyhlás rozsudok :)

Nemám za cíl nic hodnotit. Napsal jsem si demo, který je přesně detailně postnutý tady. pokud něčemu nerozumíš, omlouvám se, ale víc dopodrobna to snad už ani nejde.

howgh

Toto používali indiáni. Taky si mysleli že jsou nejlepší. No nakonec nedopadli nejlíp.

EDIT 1: Pokud jsem na nějaký přínos zapoměl, tak se omlouvám :)

Editoval Polki (30. 7. 2019 13:46)

kocourPB
Člen | 47
+
+5
-

@Polki

podla mna ten tvoj problem popisuje presne ten 5.bod z clanku, ktory postoval @Mysteria alebo sa mylim? https://tideways.com/…should-avoid

O vyhodach/nevyhodach ORM je na internetu popisaneho podla mna dost clankov. Jednu z najvacsich vyhod, ktore ORM ponuka sme ti tu uz spomenuli a to tu, ze pracujes s objektami. Ak pre teba ako PHP programatora nie je toto dostacne plus, tak asi tazko mozeme viest dalsiu zmysluplnu diskusiu.

PS: na to napovedanie nad entitami ti staci, ked si tu premennu pred foreachom zaanotujes napr. takto:

/** Product $product */
foreach ($result as $product) {
// ... PHPStorm ti ponukne napovedu pri pisani $product
}

PS2: Znova pripominam, ze Doctrine sa nemusi hodit na vsetky projekty. Kludne pouzivaj Dibi, NetteDatabase … je to volba teba resp. vasho programatorskeho teamu aku vrstvu si nasadite.

Editoval kocourPB (30. 7. 2019 14:19)

Polki
Člen | 553
+
0
-

@kocourPB

podla mna ten tvoj problem popisuje presne ten 5.bod z clanku, ktory postoval @Mysteria alebo sa mylim? https://tideways.com/…should-avoid

Pročítal jsem to a ano máš pravdu. Problém je, že ta metoda na získání oněch producktů se napříč celou aplikací používá na vícero místech. No a psaní zvláštního select pro každý případ, kde to chci použít (Dejme tomu, že vypisuju tyto produkty na seznamu produktů zákazníka, na seznamu produktů prodejce, na seznamu produktů bla bla.) A pokaždé vypisuju jiné věci, ale základní dotaz je stejný. Prostě potřebuju vytáhnout všechny produkty od určitého obchodníka. Jen jednou vypisuju objednávku na to, jednou klienty, kteří si objednali atd. A pro každý takto vytvořený výpis musím dělat zvlášť dotaz aby to bylo efektivní? Já myslel, že od toho jsou ty vazby. Aby tak jak to dělá NetteDB si to zjistilo co vše se má tahat a pak si to natáhlo naráz.

Jinak konkrétně tam vidím napříč celou aplikací problémy 2, 3 a 5

O vyhodach/nevyhodach ORM je na internetu popisaneho podla mna dost clankov. Jednu z najvacsich vyhod, ktore ORM ponuka sme ti tu uz spomenuli a to tu, ze pracujes s objektami. Ak pre teba ako PHP programatora nie je toto dostacne plus, tak asi tazko mozeme viest dalsiu zmysluplnu diskusiu.

No vzhledem k tomu, že PHP se objektovým stalo až nedávno, tak si PHP programátora spojovat s někým, kdo lpí na objektech není úplně fajn. Neřekl bych u Javy, nebo RUBY, kde je všechno objekt tam je to jasná volba. Ale u PHP, které umí s objekty pracovat stejně efektivně jako se základními datovými typy tak tam nevidím přínos. Naopak v případě, kde bude aplikace rozsáhlá například velké serverové app jako jsou některé E-Shopy, tak vývoj při ceně 600/hod na člověka a tím, jestli bez ORM trvá napsání komponenty pro získávání produktů a práci s nimi hodinu, tak při použití ORM to trvá hodinu a půl, tak ta půl hodina je poznat. Není ro problém při jedné věci. Ale když se takových věcí dělá 1000, tak 500 hodin ± je už značný rozdíl.

PS: na to napovedanie nad entitami ti staci, ked si tu premennu pred foreachom zaanotujes napr. takto:

/** Product $product */
foreach ($result as $product) {
// ... PHPStorm ti ponukne napovedu pri pisani $product
}

Nepoužívám PHPStorm :( Nicméně se na něj chystám a tak se naskýtá další otázka:

Jak mám vynutit napovídání v tomto případě?

<h2>Detail produktu</h2>
Název: {$mainProduct->name} <br>
Cena: {$mainProduct->price} <br>
Datum přidání: {$mainProduct->date} <br>
<h2>Další stejné produkty u jiných dodavatelů</h2>
<div class="products" n:foreach="$products as $product">
	Název: {$product->name} <br>
	Cena: {$product->price} <br>
	Ušetříte/přeplatíte: {$mainProduct->price - $product->price} <br>
	Datum přidání: {$product->date} <br>
</div>
David Klouček
Člen | 57
+
+1
-

Polki napsal(a):

Pročítal jsem to a ano máš pravdu. Problém je, že ta metoda na získání oněch producktů se napříč celou aplikací používá na vícero místech.

Nečetl jsem to podrobně, ale nestačí ti vracet si z metody repozitáře ten základní dotaz s joinama a na individuálních místech si ho doplnit, nebo použít https://github.com/…resultset.md?

Jinak místo MagicAccessors bys měl vygenerovat settery a gettery, nevím jestli ti teď tvoje IDE dokáže napovídat properties

Mysteria
Člen | 797
+
+1
-

Polki napsal(a):

Pročítal jsem to a ano máš pravdu. Problém je, že ta metoda na získání oněch producktů se napříč celou aplikací používá na vícero místech. No a psaní zvláštního select pro každý případ, kde to chci použít (Dejme tomu, že vypisuju tyto produkty na seznamu produktů zákazníka, na seznamu produktů prodejce, na seznamu produktů bla bla.) A pokaždé vypisuju jiné věci, ale základní dotaz je stejný. Prostě potřebuju vytáhnout všechny produkty od určitého obchodníka. Jen jednou vypisuju objednávku na to, jednou klienty, kteří si objednali atd. A pro každý takto vytvořený výpis musím dělat zvlášť dotaz aby to bylo efektivní? Já myslel, že od toho jsou ty vazby. Aby tak jak to dělá NetteDB si to zjistilo co vše se má tahat a pak si to natáhlo naráz.

Základní poučka je ta, že si musíš pro každý výpis udělat dotaz tak, aby už obsahoval všechny potřebná data, tzn. dát tam všechny JOINy tak, aby se nemusel dělat žádný další dotaz. Pak budeš mít vždy jenom jeden dotaz a můžeš si tahat co chceš.
Ano, neumí to automaticky jako Nette DB, ale to už se musíš zeptat autorů Doctrine proč nechtějí používat takovouhle magii, která za tím stojí.

Polki napsal(a):

Jak mám vynutit napovídání v tomto případě?

<h2>Detail produktu</h2>
Název: {$mainProduct->name} <br>
Cena: {$mainProduct->price} <br>
Datum přidání: {$mainProduct->date} <br>
<h2>Další stejné produkty u jiných dodavatelů</h2>
<div class="products" n:foreach="$products as $product">
	Název: {$product->name} <br>
	Cena: {$product->price} <br>
	Ušetříte/přeplatíte: {$mainProduct->price - $product->price} <br>
	Datum přidání: {$product->date} <br>
</div>

Obávám se, že pokud je to Latte šablona tak asi nijak, ale to není problém Doctrine, to je problém toho, že ten plugin na Latte nemá jak poznat co je která proměnná zač, protože se dostávají do šablony přes $this->template. Já v 99% případů stavím pouze API, tak mi tahle nedokonalost nevadí. Nicméně je jasné, že pokud děláš jednodušší weby typu vytáhnu data z databáze, dám je rovnou do šablony, kde je vypíšu, tak pak je přínos Doctrine minimálně z pohledu nápovědy metod roven nule.

Editoval Mysteria (30. 7. 2019 17:15)

Polki
Člen | 553
+
0
-

@DavidKlouček
V tomto projektu to tak reálně dělají. Udělají základní dotaz a potom podle potřeby jej doplňují další joiny apod. Ale pak to generuje ještě další balasty. Já tomu tak mic nerozumím s doctrine dělám jen půl roku a zatím jen problémy… :(

Používám NetBeans zatím a ten to zvládá, ale jen dokud jde o přímou vazbu a ví, jaký objekt používá.

Ještě dotaz. Když někde používám jen data z produktu, někde i klienty, někde i usery, jak uděláš 3 metody, které budou získávat progukty tak, že v každé další metodě přidáš join aby to vyselektovalo hodnotu?

takto?

public function getProducts() {
   return $this->em->createQueryBuilder()
				->select('p')
				->from(Product::class, 'p');
}

public function getProductsWithClients() {
	return $this->getProducts()
				->addSelect('c')
				->leftJoin(Client:class, 'c');
}

??

@Mysteria
je to celkem škoda. Kdyby to tam bylo, tak by to byla radost používat. No a když musím udělat všechny joiny a hlídat to. Nemůže se stát, že nakonec stejně budu tahat věci, co nechci? protože tu samou funkci použiju jinde a ejhle problém, jelikož byla dělaná na získání více dat jinam. Tím mi vzroste buď režie, nebo bude duplicitní určitá část kódu ne?

Každopádně díky vám oběma hoši. Konečně první dva, kteří vedou smyslplnou debatu a zvládnou odpovědět věcně na otázky co jsem položil.

Mysteria
Člen | 797
+
0
-

Ano, pokud nechceš dělat zbytečné JOINy, tak je ten kód, který jsi poslal, podle mě nejlepší řešení. Sám to používám podobným způsobem.

Takže ano, pokud chceš mít co nejefektivnější kód, tak musíš mít sadu X funkcí po jednotlivých JOINech podle toho, kde co potřebuješ vypisovat, aby to přesně sedělo (tzn. aby jsi tam jednak měl všechny JOINy, aby se udělal jenom jeden dotaz a zároveň aby jich tam nebylo ani víc, protože by zbytečně dotaz trval o malinko déle).

Kamil Valenta
Člen | 752
+
+1
-

Ta poslední ukázka je ale více podobná Nette/Database než Doctrine, nemyslíš? :)

Třeba u desktopových app je fajn mít po ruce stále naplněné objekty. Pořád mám ale pocit, že u webových app by měl člověk zohledňovat, že s každým requestem vše vzniká a vše také zaniká. A naplňovat něco, na co třeba ani nesáhnu, je zbytečná režie.

A to jsme se nezačali bavit o složitějších systémech, kde se selectuje ze selectu, pracuje s proměnnými, s procedurami, s proměnlivými sloupci…

Editoval kamil_v (30. 7. 2019 19:32)

Polki
Člen | 553
+
0
-

@Mysteria
Super díky vyzkouším. Jsi borec

@kamil_v
Absolutní souhlas. Na vysoké nám přednášel Javu týpek, který je vedoucí developerů v nějaké bance v Praze.
A ten nám ukazoval ORM a říkal, že jsou buď předpřipravená univerzální řešení a nebo, že si můžeme napsat ORM vlastní.
No a prej pokud jde o malý projekt a bylo by zbytečné vyvíjet to na míru, tak je nejlepší sáhnout po hotovém, pokud ale je v řešení něco většího, kde je každá režie důležitá, je lepší si napsat vlastní, co sedí přímo na aplikaci, jelikož je mnohonásobně rychlejší, případně se využití ORM vyhnout.

To nás učil v Javě a pro desktopové aplikace.

A jak říkáš, v rámci web aplikací, které při každém requestu (ať už jde o ajax, nebo o reload apod.) se vše tvoří znovu a znovu a data se znovu a znovu tahají a přitom se nevyužijí, tak zde je lazy dost na místě. Taktéž jako tahání všech, nebo jen některých sloupečků.

Jasný souhlas, že proto, že je něco trend by se to nemělo nasazovat. Nevím ale, jestli je to tento případ.
Prostě jsem si řekl, každý mluví o ORM a o Doctrine převážně, tak jsem chtěl vědět, co je vede k tomu to užívat, abych v tom taky viděl smysl a ne jen otravu psát kód navíc, dopředu zjišťovat, kde se co bude tahat a ještě pak poslouchat, že aplikace je pomalá. :)

EDIT 1:
Jo a jistě, že zápis je trochu podobný, ale v rámci Nette Database bych použil DatabaseExplorer, takže by zápis vypadal takto:

public function getProducts() {
	return $this->db->table('product')->fetchAll();
}

Hotovo. V případě, že bych chtěl pak procházet klienty, tak bych nad každým řádkem zavolal jen $row->client a tím dostanu klienta a nepíšu další metodu na vytvoření optimálního dotazu :)

Editoval Polki (30. 7. 2019 22:59)

fizzy
Backer | 49
+
+3
-

Bohuzial vacsina ludi, ktori pouzivaju Doctrine, ju v skutocnosti nevedia pouzivat a stale si model predstavuju ako nejake hlupe tabulky v databaze

Odporucam precitat https://ocramius.github.io/…t-practices/#/

A ked az tak velmi potrebujes riesit performance, mozes si vytvorit vlastny view model a nasypat do neho data z cisteho sql.

Polki
Člen | 553
+
0
-

@fizzy Je to celkem na místě. V aplikaci se tahá velké množství dat.

Tady je obrázek, jak dlouho vypadají dotazy nad daty. Nemohu uvádět sloupce a názvy tabulky, jelikož firemní politika.
Každopádně jde o tabulku, kde je 35 sloupečků a 25 z nich je tinyint, 2 jsou int(11) a zbytek je varchar(255)

Kódy pro jednotlivé selecty jsou tu:
Doctrine:

$productsDoctrine = $this->em->createQueryBuilder()
                                ->select('p')
                                ->from(Product::class, 'p')
                                ->setMaxResults(5000)
                                ->setFirstResult(1)
                                ->getQuery()->getResult();

NetteDB:

$productsNDB = $this->db->table('product')->fetchAll();

U doctrine je omezení na prvních 5000 řádků, jelikož při žádném omezení mi nejdřív aplikace spadla na maximální alokované paměti a při navýšení paměti na timeout. :)

Otázka je, jak je možné optimalizovat doctrine, aby měla stejný výsledek dotazu jako NetteDB, nepadalo to na paměti, ani na timeoutu? Protože načítání 25 tisíc položek a nebo 5000 s tím, že 5000 se načítá v průměru o 1/4 déle, než 25000 je celkem problém už.

Podotýkám, že schválně projde pouze tento select. NIJAK se dál data nezpracovávají. Jde mi jen čistě o čas natáhnutí z DB a velikost paměti, co musím mít, abych dostal záznamy.

Martk
Člen | 651
+
+1
-

Ano, doctrine není totiž stavěná na takové množství entit. Doctrine tě nenutí u takových dotazů, kde se to nevyplatí, vytvářet entity, proto lze udělat toto:

$this->em->createQueryBuilder()
                                ->select('p.id, p.client')
                                ->from(Product::class, 'p')
                                ->setMaxResults(5000)
                                ->getQuery()->getScalarResult();

Editoval Martk (31. 7. 2019 20:51)

Polki
Člen | 553
+
0
-

@Martk
Jasně, vyzkoušel jsem a funguje. Teď už zvládne vytáhnout všechna data a jen sloupce, které definuji.

Všiml jsem si ale, že toto řešení vrací array, což vlastně je, jako bych ji vůbec nepoužil.

Jinými slovy doctrine je v tuto chvíli stejně účinná jako NDB, ale NDB mi vrátí ActiveRow, místo array, takže nad daty z NDB jednoduše zavolám update, což na výsledku z doctrine nejde. Nějaký způsob, jak to obejít?

Je také nějaké řešení abych v tomto případě nemusel explicitně vyjmenovávat, jaké sloupečky chci natáhnout?

fizzy
Backer | 49
+
0
-

Porovnavas hrusky s jablkami.. NDB pracuje s ActiveRow co je wrapnuty array a naopak Doctrine mapuje tieto data a relacie na objekty, vytvara proxy triedy, v unit of work uchovava pociatocny stav vsetkych objektov atd. Doctrine v pamati musi drzat daleko vacsie mnozstvo dat, preto nikdy pri loadovani entit nedosiahne rovnaky performance ako NDB.

Na vypis tisiciek produktov nepotrebujes business logiku z entit, staci ti vytiahnut array, pripadne si ich vies automaticky loadnut do nejakeho DTO https://www.doctrine-project.org/…anguage.html#…

Polki
Člen | 553
+
0
-

@fizzy
Jak můžeš vědět, že nepotřebuju? :D Co když provádím nad těmi daty nějaké výpočty, potřebuju nějak nastavovat hodnoty apod.

Prostě pokud to nejde hold nejde. Stejnou práci mi obstará i model není co řešit.

fizzy
Backer | 49
+
0
-

Aj batch spracovanie sa da vyriesiet https://www.doctrine-project.org/…cessing.html

Polki napsal(a):

@fizzy
Stejnou práci mi obstará i model není co řešit.

aky model ked ziadny model nemas :D NDB je len abstrakcia nad databazou, nie model.. NDB je fajn na jednoduche projekty..

napriklad taky eshop, potrebujes pracovat s peniazmi a pouzivat datovy typ money napr https://github.com/brick/money – ako spravis s NDB „model“ ktory ti vrati objednavku a hodnota objednavky a jej poloziek bude reprezentovana typom money? Ako pridas do objednavky dalsi produkt, prepocitas celkovu cenu objednavky (do ktorej vstupuje doprava, zlavy, prirazky.. s Doctrine si na to napises triedu, ktora funguje bez databazy, mozes ju rychlo otestovat unit testami, s NDB si mozes tak maximalne vytvorit servisu, do ktorej nasypes celu logiku a operacie nad databazou, v jednej triede riesis business logiku a persistenciu zaroven.. je to bordel a z dlhodobeho hladiska neudrzatelne.

Martk
Člen | 651
+
0
-

Doctrine nejsou jen entity a nic jiného a přes to nejede vlak. Když potřebuješ optimalizovat nebo provádět operace nad tímto extrémním množstvím dat, tak k tomu slouží právě pole. Hydratace na entity je náročná na čas a paměť, když budeme porovnávat s jednoduchým polem.

Kdybys opravdu potřeboval provádět operaci nad tímto množstvím dat, tak si to zapouzdříš do funkce, která vrací asi jen stav true/false a nikoho nebude zajímat, že se právě v té funkci pracuje s poli, protože nic nevracíš a je to v rámci optimalizace. A kdybys náhodou chtěl entity (to by mě opravdu zajímalo na co takové množství), tak se to dá paměťově optimalizovat přes yield.

Potřebuješ někdy ActiveRow jako výsledek? Není problém udělej vlastní hydrataci a volej getResult('activeRow'), i když se mi to nezdá jako nejlepší nápad.

Polki napsal(a):

Jak můžeš vědět, že nepotřebuju?

To bude tím, že vytahuješ jen 2 idečka. Nejlepší by bylo dát kus kódu, který ti nefunguje nebo který je napsaný v NetteDB

IMHO tato diskuze je spíše „ORM není ActiveRow, jak to?“, přitom se jedná o různé techniky

Ale chápu tě, taky jsem jí ve svých začátcích nesnášel, ale čím víc o ní vím, tím se mi více líbí.

Polki
Člen | 553
+
-1
-

@fizzy

Aj batch spracovanie sa da vyriesiet https://www.doctrine-project.org/…cessing.html

Dobrý toto je užitečné. Víc už to snad ani nejde zlepšit. Je mi jasné, že toto za mě automaticky naudělá nikdo. Fofo byla fakt dobrá připomínka. Jen mi vadí používání new, ale to už se dá upravit.

aky model ked ziadny model nemas :D

Jak nemám? :D Při Doctrine mám jaký model? :D Úplně ten stejný, jako v případě NTB, jediný rozdíl je v reprezentaci dat nad databází. Pokud manipulaci píšu přímo do entity, tedy dělám z entity modelovou vrstvu a s každou entitou vytvořím v paměti balast a duplicitu jako blázen, jen proto, že jsem si řekl jo dám to tam, nebo si vytvořím jak ty píšeš servisu, která je SINGLETON, který v rámci celé aplikace běží jeden, jak už název napovídá a ten se o to stará tadá ušetřil jsem tuny paměti. A jestli tento SINGLETON service pracuje s Entitami reprezentovanými jako třída s daným názvem, nebo jako ActiveRow, tak to už nikoho nezajímá.

ako spravis s NDB „model“ ktory ti vrati objednavku a hodnota objednavky a jej poloziek bude reprezentovana typom money?

Naco? K čemu mi je mít typ Money, když stejně s ním pracuju podle výše popsaného v service, nad kterou volám jednoduchou API a ta se o to stará interně? Výhodou vytvoření servicy je to, že jí předáš jakýkoli objekt a víš, že nad ním můžeš volat stejné věci a je všem jedno, jeslti je to instance ActiveRow, nebo objekt typu Money a zároveň je v rámci aplikace jeden jediný, takže šetří paměť a čas.

Ako pridas do objednavky dalsi produkt,

Jednoduše. Zavolám nad $this->service->add($tableName, $data), přičemž v $tableName je název tabulky kam ukládám a v $data je array hodnot. Něco těžkého?

prepocitas celkovu cenu objednavky (do ktorej vstupuje doprava, zlavy, prirazky..

Triggery v databazi? Například? Nebo databázevé metody? V případě, že bych potřeboval tuto hodnotu vracet, není nic jednodužšího, než si na to v service udělat metodu. :)

s Doctrine si na to napises triedu, ktora funguje bez databazy, mozes ju rychlo otestovat unit testami

s NDB to nejde? Nemůžu, když do servicy injectuju připojení k databázi si injectnout místo připojení k DB virtuální databázi, která funguje odevšad, takže databáze reálně neexistuje a místo toho se to jen tváří, jako že existuje, ale přitom data ukládá dočasně do paměti? To nejde? Já myslím, že jo a tím pádem nad tím bez problému spustíš i unit testy. (Jen ti nebudou fungovat triggery apod. A mimoto unit testy se dají spustit i nad kódem, který komunikuje s databází ;) To na unit testy nemá vliv.)

funguje bez databazy

V případě, že má zákazník vytvořenou databázi pro svůj systém a chce udělat aplikaci, která je rozšíření pro jeho systém a on v databázi využívá triggery, pohledy, funkce… Jak otestuješ v doctrině bez této databáze, když teda doctrine bez databáze může běžet jak tvrdíš, co ti tyto funkce vrací a jak odsimuluješ chování triggerů?

v jednej triede riesis business logiku a persistenciu zaroven.

Toto se v doctrině neděje? Já myslím, že v doctrině můžeš vytvořit přece v classe money udělat toto:

class Money {
	// entity logic...

	public function persist() {
		$this->em->persist();
	}
}

$money->perist();

Nehledě na to, že persistování a ->flush() metody mít rozházené různě po kódě je peklo, protože pak nevíš, kde co máš a při úpravách nevíš, jestli si nepersistnul náhodou 2× třeba. Takže řešení napsat si service, která pesistování řeší za tebe a najednou máme zase service.

NDB je fajn na jednoduche projekty..

Můj názor je že to je naopak.
S NDB je kód 4× kratší, přehlednější a lépe se v tom člověk vyzná. V případě, že dělám na projektu v doctrine, kde lineárně s počtem databázových tabulek roste počet tříd a lineárně s počtem výpisů dat roste počet různých dotazů nad databází, protože pro každý výpis musíš napsat jiný select, tak mi přijde pro větší projekty neudržitelný právě Doctrine.

Reálně se tato data v jedné firmě, se kterou spolupracuji měřila a kód, který psal člověk v Doctrine tak, aby to bylo optimální a nepadalo to na paměti a taky aby se nedělal zbytečný počet dotazů a například při výpise v selectu aby se zbytečně nenačítaly entity, tak taková úprava jedné stránky (úpravy jedné entity, aby bylo možno ji vypsat na stránce v seznamu, poté ve filtru a poté v řazení) trvalo řešení 2 hodiny a 10 minut.

V NDB měl ten samý kód, jen 2/3 kratší a dělalo to to samé, napsaný za 40 minut.

Nutno podotknout, že jde o projekt, který je velkého rozsahu. A velkého myslím fakt velkého. Jde o projekt, kde se řeší provázání 7 různých aplikací a každá má 15 různých funkcionalit a navzájem se doplňují, přičemž každá funkcionalita se skládá ze spousty formulářů a výpočtů.

@Martk
>Doctrine nejsou jen entity a nic jiného a přes to nejede vlak. Když potřebuješ optimalizovat nebo provádět operace nad tímto extrémním množstvím dat, tak k tomu slouží právě pole. Hydratace na entity je náročná na čas a paměť, když budeme porovnávat s jednoduchým polem.

Rozumím. To je věc, kterou neobechčiješ.

>Kdybys opravdu potřeboval provádět operaci nad tímto množstvím dat, tak si to zapouzdříš do funkce, která vrací asi jen stav true/false a nikoho nebude zajímat, že se právě v té funkci pracuje s poli, protože nic nevracíš a je to v rámci optimalizace.

No ono to reálně funguje tak, že při získání prvků z db se nad získanými daty provede výpočet a výsledky výpočtu se zibrazí v grafech. Těch grafů je 6 a další 2 seznamy tam jsou, kde se vypočítává skóre. Nejjednodužší je napsat funkci v DB, která propočty udělá (nelze tyto propočty dělat už při ukládání prvku, jelikož propočty závisí na počtu prvků, takže by se s vložením musel každý prvek přepočítat a zároveň je to ovlivněno vnějším vlivem, tedy něco jako bych využíval aktuální kurz měny), jenže týpek předemnou, to počítá až v aplikaci. No a já nejsem v Doctrině natolik zběhlý, abych věděl, jak volat nad db funkci.

A kdybys náhodou chtěl entity (to by mě opravdu zajímalo na co takové množství), tak se to dá paměťově optimalizovat přes yield.

Jistě, to je možnost. Ale stejně nepřístupná, jelikož stejně tato data vyrenderuju naráz. Ano, ušetřím tím nároky na paměť, jelikož budu načítat vždy vše jen po částech, ne naráz, ale onen čas to prodlouží. Mimoto nebude lazy pomocí yield dělat víco dotazů do db než 1? Nebo se dá v doctrine nějak nastavit, že si stáhne data z db jedním dotazem a až v případě, že je chci, tak vygeneruje entitu? (nechci dělat generování entit sám nad daty z pole, co vrátí metoda ->GetScalarResult(), to můžu rovnou vzít NDB a tu samou metodu pro generování entity zavolat nad ActiveRow)
>Potřebuješ někdy ActiveRow jako výsledek?
Nepotřebuju. Jen se mi líbí, že můžeš k jednotlivým sloupcům přistupovat jako k property třídy, takže se s tím pracuje stejně a že ActiveRow má nad sebou rovnou metody ->update() a ->delete()
No a kouzlo je, že metoda ->update() bere jak array, tak ArrayHash, takže tam můžeš rovnou nacpat values z formuláře a nemusíš cpát do každé property zvlášť hodnotu a nebo si tvořit nějakou metodu populate apod. prostě jeden řádek a hotovo. Je to takové přívětivé.

To bude tím, že vytahuješ jen 2 idečka. Nejlepší by bylo dát kus kódu, který ti nefunguje nebo který je napsaný v NetteDB

Chápu, ale nás už na základce učili, že máme být univerzální a brát v potaz každou možnost. A to, že to třeba teď nepotřebuju neznamená, že to do budoucna potřeba nebude. A taky to, že tu je demo, kde se to nevyužívá neznamená, že v ostré aplikaci to není. Kdyby to tam totiž nebylo, tak ten problém vůbec neřeším. :)

IMHO tato diskuze je spíše „ORM není ActiveRow, jak to?“

Rozumím rozdíl těchto dvou návrhových vzorů. Jen jsem potřeboval vysvětlit rozdíl mezi návrhovým vzorem ORM a ActiveRow, abych pochopil přidanou hodnotu ORM oproti ActiveRow. Ale máš pravdu, že se konverzace ubírá spíš tímto směrem, než jaký byl původně zamýšlen.

Martk
Člen | 651
+
0
-

@Polki

Těch grafů je 6 a další 2 seznamy tam jsou, kde se vypočítává skóre.

V aplikaci mám taky grafy a u toho si beru data v poli. Vlastní funkce si můžeš vytvořit (možná najdeš i kódy na netu). Příklady

https://blog.4xxi.com/…516496001945
https://www.andreafiori.net/…tom-function
https://github.com/…neExtensions

Jelikož budu načítat vždy vše jen po částech, ne naráz, ale onen čas to prodlouží

Myslím, že jsem sledoval test s yieldem a ten čas to při větších datech sníží, pokud nepotřebuješ znovu použití.

Jen se mi líbí, že můžeš k jednotlivým sloupcům přistupovat jako k property třídy

Stačí, když si vytvoříš ArrayHashHydrator, který bude dědit od ArrayHydrator a výsledek místo array bude vracet ArrayHash

Chápu, ale nás už na základce učili, že máme být univerzální a brát v potaz každou možnost

Takhle jsem to taky kdysi dělal a to extrémně, programoval jsem funkce/kódy navíc, které bych mohl v budoucnu použít. Samozřejmě jsem je nikdy nepoužil :) Prostě jsem chtěl říct, že se to třeba stane za projekt 0–3× a když tak použiješ pole nebo najdeš lepší řešení.

Polki
Člen | 553
+
-1
-

Martk napsal(a):
Takhle jsem to taky kdysi dělal a to extrémně, programoval jsem funkce/kódy navíc, které bych mohl v budoucnu použít. Samozřejmě jsem je nikdy nepoužil :) Prostě jsem chtěl říct, že se to třeba stane za projekt 0–3× a když tak použiješ pole nebo najdeš lepší řešení.

Takhle jsem to nemyslel, že si připravuju kódy, které bych v budoucnu mohl použít. Myslel jsem to jako reakci na vysvětlování. Jakože když se mě někdo zeptá, jak otevřít dveře, tak že nevím, jak vypadá, co může nebo nemůže dělat, tak mu popíšu i jak otevřít dveře, pokud nemá ruce. Protože nevím, jestli ruce má. :D

Pokud jde ale o psaní metod, tak píšu jen ty, které potřebuju, ale ty zase píšu univerzálně natolik, aby se daly využít na vše, na co být využity mohou a zároveň, když je nevyužiju v projektu, na kterém dělám, tak aby šly přesunout do dalšího projektu a mohl jsem je využít tam.

Jinak se vším naprostý souhlas a díky za tipy.
Jo a k tomuto:

Myslím, že jsem sledoval test s yieldem a ten čas to při větších datech sníží, pokud nepotřebuješ znovu použití.

Ne nepotřebuji. Jasné, že načítat znovu pro použití někde jinde by zdvojnásobilo čas. Každopádně jsi udělal přesně to, co jsem vysvětloval výše. odpověděl jsi univerzálně. To je to, co jsem tím chtěl napsat.

A nevíš, jestli ten yield vytvoří ten dostaz jeden, nebo nad každým prvkem dál volá získání z db?

Felix
Nette Core | 1183
+
+1
-

V ActiveRecord jsem celkem narazel, ze kdyz jsem refactoroval sloupecky nebo menil nazvy, tak jsem casto zapomnel na nejakej zapomenutej kod a pak to tam spadlo. S entitam je to o dost lehci, kdyztak i phpstan pohlida.

Za me vsechny DB knihovny a vrstvy jsou fajn, je potreba je pouzivat spravne a na spravne use-cases.

Pokud bude tym 20 lidi pouzivat ciste PDO a projekt mu bude slapat, tak proc ne. Obcas ale uz clovek nechce byt tak blizko DB a spis resit domenove veci, ruzne udalosti pred/po ulozeni, cely lifecycle dat, psat si vlastni funkce pro mapovani z/do DB, vlastni funkce pro SQL, migrace, Tracy panel, validaci, kopirovani daaaat…

<Agonie>
Uaaaa, vlastne si rikam, ze byste meli vsichni dat sanci Nettrine. Tam to vlastne vsechno mame. 🤣
</Agonie>

Polki
Člen | 553
+
0
-

@Felix
jasan rozumím. Refactoring není těžké řešit. :) Jen už nemám moc motivaci pro to něco nějak řešit. Na Nettrine jsem se už díval. Ne dopodrobna, ale určitě brzo kouknu.

Martk
Člen | 651
+
0
-

Odpovím takhle, když ten dotaz bez yieldu udělá jen jeden dotaz, tak i s yieldem udělá jen jeden dotaz :D

Yield jen značí v překladu, nevolej funkci, až se použije tato funkce jako iterátor, dojdi k nejbližšímu yield, pozastav zpracování funkce, vrať hodnotu. Při další průchodu iteraci pokračuj ve zpracování funkce a jdi k nejbližšímu yieldu.

Polki
Člen | 553
+
0
-

@Martk
jo to znám :D Ale teď už nechápu, jak se tedy dá udělat načítání entit až je třeba.

To by ale bylo na dlouhé probírání.

Mysteria
Člen | 797
+
0
-

Felix napsal(a):
<Agonie>
Uaaaa, vlastne si rikam, ze byste meli vsichni dat sanci Nettrine. Tam to vlastne vsechno mame. 🤣
</Agonie>

K dokonalosti chybí poslední věc a tou je podpora více spojení do databáze (a s tím i souvisejících EntityManagerů, Migrations, Fixtures,…).

Polki
Člen | 553
+
0
-

Mysteria napsal(a):

Felix napsal(a):
<Agonie>
Uaaaa, vlastne si rikam, ze byste meli vsichni dat sanci Nettrine. Tam to vlastne vsechno mame. 🤣
</Agonie>

K dokonalosti chybí poslední věc a tou je podpora více spojení do databáze (a s tím i souvisejících EntityManagerů, Migrations, Fixtures,…).

To zrovna potřebuju :D Mám 3 různé DB

F.Vesely
Člen | 368
+
0
-

Podle me @Polki uplne nerozumi tomu, jak Doctrine funguje. V Doctrine mas data (Entity, Value objekty) oddelene od uloziste (Databaze), takze Entity nevedi nic o tom, jak jsou ukladane a ani svoje ukladani a nacitani nijak neresi.

Coz je podle me ta nejvetsi vyhoda oproti Nette Database. Takove Entity nebo VO jsou lehce testovatelne bez databaze, muzu zarucit jejich validitu, lehce se refaktoruji, jsou znovu pouzitelne, IDE mi napovida jejich metody, atd.

Pro jednoduchost si vem vyse zmineny VO Money. Napriklad cena v eshopu, vyskytuje se tam skoro vsude u produktu, v kosiku, slevy, objednavky, emaily, atd. Vsude, kde ji s ni pracujes (vypis, pocitani, atd.) potrebujes znat jeji hodnotu a menu.
Pokud jsi nekdy pracoval s penezma, tak vis, ze nejlepsi je ukladat je do DB jako int nebo string (kvuli zaokrouhlovani).
Jelikoz i pocitani (scitani, odecitani, rozdelovani podle procent, atd.) je u penez slozite, tak je nejlepsi na to pouzit nejakou knihovnu, osobne pouzivam https://github.com/brick/money.
No a v celem systemu pak pouzivam VO Money, nemusim se uz nikde starat jestli nescitam spatne meny, jestli spravne zaokrouhluji halere, v sablone si udelam vlastni filtr, ktery mi vypisuje podle lokalizace spravny format, atd.
Ano, muzes Money pouzivat i s Nette Database, ale musis se o mapovani starat sam, coz je dost nepohodlne a snadno na to zapomenes.

Stejne pak pracuji s emailem, kdy mam jednoducho tridu Email, se kterou pak vsude pracuji a Doctrina se mi stara o to, aby se ulozila a nacetla. Na tom samem principu pak funguji cele Entity, pekne otestovane a validni v celem systemu.

Polki
Člen | 553
+
-2
-

@F.Vesely
Prvně děkuji za skvělý komentář. Má vysokou výpovědní hodnotu. Podle mě jeden z nejpovedenějších.

Jen pár poznámek:

uplne nerozumi tomu, jak Doctrine funguje

Rozumím. Sám jsem dělal obdobnou vrstvu, když mi bylo 11, která se starala o „save“ desktopové aplikace. Při zmáčknutí křížku, nebo zavření pomocí Alt+F4 se vyvolala událost, která na disk uložila soubor s aktuálním stavem obsahu všech objektů, které aktuálně byly vytvořeny v rámci aplikace.

Tedy v základu to fungovalo jen jednoduše. Žádná věda. V události se zjistilo, jaké všechny objekty v aplikaci existují a jeden po druhém se serializovaly do řetězce, který se pak uložil na disk. V momentě znovuspuštění programu se pak program nejdřív podíval na disk, jestli existuje tento soubor a pokud ano a byl obsah validní, tak se obsah deserializoval a opět se zpětně vytvořily objekty, takže pro uživatele to vypadalo, jako by aplikaci vůbec nezavřel.

Takže rozumím, že Doctrine je podobná vrstva, jako ta moje. A rozumím taky, že objekty vůbec nevědí o tom, že se jejich obsah promítne do nějakého úložiště. Dokonce jak jsem psal, tak jsme museli dělat ORM pro Javu jako projekt v rámci školy.

Jediné, co mi uniká je pointa efektivnosti jakéhokoliv ORM. Jak jsem psal výše, těch dat bývají dost často neuvěřitelné objemy. Viz například jedna nejmenovaná firma v Ostravě, která se zabývá vývojem desktopové aplikace pro právníky a tato aplikace je takový hybrid mezi wordem, excelem a gitem. Nahrávají se zde zákoníky a jednotlivé části se dají měnit a dají se zobrazovat různé změny v čase. Reálně je to rozděleno do bloků, podle roku, paragrafů a odstavců a toto je ještě reprezentováno pomocí customizable tabulky. Ten, kdo ten systém navrhoval jej navrhl tak, že pro jeden paragraf zákona, který se dá vypsat se reálně z db tahá něco kolem 600 entit (pro každou buňku obsah buňky, 4 rámečky, 4 odsazení atd. Proč to neudělali jednodušeji se mě neptejte) takže když si vytáhne právník ne jeden paragraf, ale třeba celou vyhlášku o 10 paragrafech, tak může počet entit narůst klidně až na 6000a to v jednom zobrazení. Nepíši teď o tom, když si ještě vyfiltruje rozdíly v posledních 2 letech, nebo vyhláška je větší.

Reálně to řešili tak, že data reprezentovaly pomocí XML a posílali plain text a dekódování aby se vše zobrazilo jak má probíhalo až v aplikaci. Viděl jsem reálně případ, kdy si právník vytáhl data a jen onen plain text z DB měl 50MB. A to jsou velké množství dat. Už jen při načítání si mohl odskočit na kafe. Ta aplikace běžela tak, že člověk zadal požadavek, 5 minut čekal, než se to načte, pak když scrolloval, tak se to sekalo jako blázen (A to měli implementovaný LazyLoading tak, že daná data, co přišla z DB se mapovala na entity a zobrazovala až v momentě, kdy se měly zobrazit na monitoru. Do té doby zůstávala pořád jako plain text.) A nedej bože, když uživatel označil jedno políčko a změnil barvu. V ten moment mohl zajít na oběd. :)

A pak takového právníka plať :D Když čeká na odezvu systému. :D

osobne pouzivam https://github.com/brick/money

Knihovna pro práci s měnou šikovná.

Ač na práci s měnou nic složitého, kromě neustále se měnícího kurzu, nevidím, tak souhlasím, že pro počítání s tímto, je potřeba třída, která se mi o to postará.
Jen pořád nevidím důvod, proč by ta logika měla být obsažená v třídě Money, a ne v třídě MoneyManipulator?
V tomto pojetí by objekt Money uchovával pouze informace o tom, kolik je zrovna hodnota a jaká je měna. No a samotná logika práce s tímto objektem by byla obsažena v objektu třídy MoneyManipulator.

Takže by interface třídy MoneyManipulator vypadal například následovně:

/**
* @param Money $money1
* @param Money $money2
*
* @return Money Sum of $money1 and $money2
*/
public function add(Money $money1, Money $money2) : Money;

a pak voláš jen $moneyResult = $manipulator->add($money1, $money2);
Samozřejmě je možné udělat api jakékoli. Třeba, že návratová hodnota bude NULL a v proměnné $money1 bude výsledek sumy, abychom nemuseli dělat přiřazení.

Nevýhoda tohoto řešení je, že nemůžu volat ->add rovnou nad instancí Money.
Výhoda, (N-1) násobně převyšující nevýhodu, přičemž N je počet instancí třídy Money je, že mám logiku v jednom modelu/Singletonu a ne v N entitách, čímž neuvěřitelně šetřím prostředky.

No a když je tedy třída Money pouze ‚skladníkem‘ dat, tak ji můžu s klidným srdcem nahradit za pole. (Mimoto stejně jako třída se v případě doctriny/ORM všeobecně nestará o ukládání sama sebe do DB, ale dělá to za ni někdo jiný, aniž by to věděla, tak troufnu si se 100% jistotou říct, že ani to pole nemá páru, jestli ho někdo někam uloží.)
No a místo $em->persist($money) a $em->flush($money) Zavolám jen $manipulator->save($money); Jestli je obsah $money objekt nějaké sofistikované třídy, pole, nebo například medvídek PŮ, tak to je úplně jedno, metoda save si s tím poradí.

Jistě, toto řešení s ‚polem‘ má jednu nevýhodu a to tu, že v rámci obejktu (a případně nějaké vrstvy, která objekt naplní daty) probíhá validace již na úrovni jazyka, takže každý atribut může mít vynucený datový typ a tím pádem už při získání dat z DB a následném vkládání do Objektu pomocí Getterů a Setterů validuji, jestli jsou data v objektu ‚správná‘. U pole tuto validaci na úrovni jazyka nelze provést.

To je ale taky vcelku jedno, protože jestli mi getter porovná, že je to, co jsem dostal z DB řetězec, nebo double a podobně, tak to, že je email řetězec nemá samo o sobě žádnou výpovědní hodnotu. Jelikož v řetězci může být cokoliv. Takže nějaká základní validace na typ dat je vlastně zbytečná. Důležitá je velidace, jestli mi z DB přišel opravdu mail, a ne jen nějaký podvržený sled znaků. No jo, tak v případě objektu dám tuto logiku do setterů. Pecka mám vyřešeno.
Jenže, tím nastává ten samý (N-1) nevýhoda problém. Co s tím. Tak toto ověření přesunu do manipulatoru. No a ejhle, zase třída funguje jako obyč. pole.

Nevím už co víc k tomu napsat. Takže tímto nejspíš končím toto celé vlákno :)

F.Vesely
Člen | 368
+
+4
-

@Polki jen v rychlosti
U toho, ze uplne nerozumis Doctrine jsem vychazel z tveho prispevku vyse, kde jsi ve tride Money pouzival flush.

Kde jsi prosimte cetl/vyzkoumal, ze metody tridy se kopiruji v pameti do kazde jeji instance? Vzdyt to by bylo naprosto zbytecne, kdyz se v prubehu behu pozadavku nijak nemeni. IMHO se v pameti drzi jen jeji properties a nejakej ukazatel na predpis metod. Nebo o cem to mluvis jako o neuveritelnem setreni prostredku?

Proc nemit MoneyManipulator? Protoze je to (podle me) lepe citelne, nemusis mit na to zbytecne dalsi tridu a vsude mit o 1 use vice.

Taky pises, ze das validaci emailu do setteru. Coz je super, akorat ja mam validaci na 1 miste a nezapomenu ji pouzit. Obecne jsem se naucil, ze nejvetsim zaskodnikem v mem kodu jsem ja sam, takze si davam co nejmensi moznou sanci, abych nekde na neco zapomnel a podelal to. Rika se tomu defensive programing.

Posledni bod je, ze bys vsude pouzival pole. Vis, ze pole v PHP neni nijak pametove mene narocne nez trida? Protoze PHP nevi pri inicializaci, jak bude velke, jestli klice budou jen int nebo i string, musi si ukladat jeho velikost, poiter na jakem prvku aktualne jsi, pak ho ruzne zvetsovat a zmensovat jak s nim pracujes, atd. Dalsi veci je, ze ti u pole IDE nenapovida, vratis ho z metody a furt aby sis pamatoval a nebo dohledaval, co tam vlastne mas. Jasne, da se to pamatovat ten den, nebo po dobu, co s tim pracujes, ale za mesic? Za mesic jsem rad, ze ± vim, kde mam nejakej Presenter. :D No a nejvetsi zlo je, ze si v tom poli muzes nechtene neco prepsat a takovym WTF momentum se ve svem zivote radsi vyhybam. Jsem akorat nervni pri debugovani, kdyz to nemuzu najit a pak nas*anej sam na sebe. :D

jiri.pudil
Nette Blogger | 1028
+
+3
-

@Polki dovolím si s tebou souhlasit v tom, že pokud máš anemický model (což je imo to, co tu celou dobu popisuješ), Doctrine ti nedokáže přinést takovou hodnotu, často ani ne tak velkou, aby sis její použití dokázal obhájit, třeba zrovna z hlediska výkonu. Cílem ORM totiž není být efektivní, stejně tak není cílem ORM úplně vývojáře oprostit od použitého databázového enginu a jeho výhod a optimalizací; ORM prostě a jednoduše mapuje relační databázi na objekty a naopak. A podstatou objektově orientovaného programování je zapouzdření dat a logiky pohromadě v objektech – což je důvod, proč spousta lidí považuje anemický model za anti-pattern. Více k tomu má co říct Martin Fowler

Editoval jiri.pudil (2. 8. 2019 11:31)

Polki
Člen | 553
+
-1
-

@jiripudil
Díky za skvělou odpověď

@FVesely
Ve zkratce píšu o tomto . Můžeš si to vyzkoušet, jaké hodnoty ti to napíše.
Jasně, že pole, pokud ho nevytvoříš statické, tak má větší režii, jelikož si k tomu ještě na pozadí PHP vytvoří HashTable a bla bla. Jenže Nette Database Explorer, nevrací klasické pole, ale optimalizovanější ActiveRow.

Vyzkoušej si oboje.

Všiml jsem si, že s tím duplikováním metod máš pravdu. Buď to vylepšili, nebo jsem to viděl u jiného interpretovaného jazyka. Tam kde jsem to viděl to totiž tak bylo proto, že interpretovaný jazyk se může měnit za běhu a tím pádem mohu za běhu změnit chování celé jedné instance, přičemž ostatní zůstanou stejné a proto se to kopírovalo do paměti. Aby kód třídy pro danou instanci mohl být jednoduše rychle měněn.

Jak se chová PHP, když u třídy dynamicky vytvářím a mažu Properties? Máš nějaké info?

Další problém je, když mám třeba tu validaci mailu, tak k tomu je potřeba nějaký pattern. A ke každé validaci jsou potřeba nějaké hodnoty. Pro validaci, jetli je řetězec dlouhý alespoň N znaků potřebuješ integer min atd. Pokud máš třídu Money, která tuto validaci dělá automaticky v sobě, tak s každou instancí této třídy ti narůstá počet zduplikovaných konstant. (odzkoušeno).

Jasně, můžeš dát onu konstantu opravdu jako konstantu, nebo jako statickou(ale ještě jsem neviděl nikoho, kdo to dělá). Ale v tomto případě zase na druhou stranu se toto alokuje při startu aplikace i když to nevyužiješ (pokud se nepletu)

zatímco když budeš mít třídu MoneyManipulator, která je SINGLETON, tak instance bude jen 1, takže žádná duplicita dat a jako bonus budeš mít validační konstanty načtené jen v momentě, kdy je opravdu potřebuješ a ne pořád.

Jako člověk si řekne: 'však co, je to pár proměnných a ty, když budou konstanty, tak mě troška paměti navíc nezabije, jde v rámci celé aplikace o jednoty či desítky kilobajtů."
No to sice jo. :D Ale při připojení 1000 uživatelů v jednom čase, což se může stát to již nejsou jednotky, či desítky KB, ale MB.

A to už samo o sobě je někdy problém.

EDIT

Proc nemit MoneyManipulator? Protoze je to (podle me) lepe citelne, nemusis mit na to zbytecne dalsi tridu a vsude mit o 1 use vice

Když místo N tříd budu mít jednu manipulator, která nad několika různými daty(user, merchant, quest) bude provádět ty samé operace, tak nemám jednu třídu navíc, ale o (N – 1) méně.

Taky pises, ze das validaci emailu do setteru. Coz je super, akorat ja mam validaci na 1 miste a nezapomenu ji pouzit.

Pokud se nepletu, tak u mého řešení je validace taky na jednom místě a nezapomeneš ji použít, jelikož se o ni automaticky postará onen manipulator. :)

Editoval Polki (2. 8. 2019 13:24)

Martk
Člen | 651
+
0
-

@Polki V tom testu máš chybu, máš tam emial namísto email, jakmile to změníš, tak to bude u třídy Man o dost méně. A zároveň zabráníš takovým chybám, pokud použiješ SmartObject

F.Vesely
Člen | 368
+
+1
-

@Polki

  1. Vyzkousel jsem, akorat jsem to hodil kazde do vlastniho souboru a testoval pres CLI. Vysledek je trida – 133kB, pole – 413kB, ArrayHash (obalka nad stdClass) – 461kB. Coz ti odpovi i na tvou otazku dynamickeho pridavani a mazani properties, coz je stdClass. Nevim o jakem optimalizovanejsim ActiveRow mluvis, do ActiveRow se v kontruktoru predavaji data v poli viz https://api.nette.org/…Row.php.html#….
  2. Ano, konstanty davam opravdu jako konstanty a pouzivam i static.
  3. Pletes si tridy a instance trid. Ja mluvim o 1 tride navic (Money + MoneyManipulator), ty o N – 1 instancich. Ano budes mit N – 1 instanci mene, ale o N poli vice, coz je viz bod 1 o cca 3× vice pameti.
  4. Pochopil jsem jinak, ano, pokud nezapomenes pouzit nekde v kodu manipulator. Ja zapomenout pouzit Email nemuzu, protoze si ho uz vsude predavam. :)
Polki
Člen | 553
+
-1
-

F.Vesely napsal(a):

@Polki

  1. Vyzkousel jsem, akorat jsem to hodil kazde do vlastniho souboru a testoval pres CLI. Vysledek je trida – 133kB, pole – 413kB, ArrayHash (obalka nad stdClass) – 461kB. Coz ti odpovi i na tvou otazku dynamickeho pridavani a mazani properties, coz je stdClass. Nevim o jakem optimalizovanejsim ActiveRow mluvis, do ActiveRow se v kontruktoru predavaji data v poli viz https://api.nette.org/…Row.php.html#….

Rozumím mám tam chybu.

Každopádně, když pole definuješ takto:

for($i = 0; $i < $count; $i++) {
    $resultArray[] = [
        'name' => $name,
        'surname' => $surname,
        'email' => $email
    ];
}

Tak je několikanásobně menší na paměť, než třída. A vzhledem k tomu, že ActiveRow má v sobě přesně takto vytvořená pole (A nejde do nich zapisovat, musí se použít metoda update), tak ve výsledku, viz příklad 2 v mém posledním příspěvku, má výsledek z Databáze, který mi vrátí pole plné ActiveRow, daleko menší velikost, než pole naplněno instancemi tříd.

  1. Ano, konstanty davam opravdu jako konstanty a pouzivam i static.

Ok. Pořád je zde problém toho, že všechno STATIC je v paměti po celou dobu běhu programu. Ne jen když to potřebuješ jako zmíněný manipulator, Jelikož Mail validator ne všude a vždy použiju, ale v paměti bude smrdět pořád, zatímco když bude mail validator ne statický, ale uvnitř manipulatoru, tak v případech, kde manipulator nebudu potřebovat se instance vůbec nevytvoří a tím pádem to paměť nezabírá. Navíc používáním static proměnných a tedy i konstant v PHP, což jsou statické proměnné (pokud se nepletu) porušuješ princip zapouzdření a OOP celkově . V PHP to tolik nebolí, jelikož se každý request zpracovává samostatně a pokaždé to zase umře. Pokud se ale něco takového jako používat statické fičury naučíš a pak přejdeš třeba na C#, kde běží na serveru celá aplikace pořád a statické proměnné tam hnijí po celou dobu běhu aplikace, tak navíc každý request je obhospodařen zvlášť vláknem nad neustále běžící aplikací a když se stane a nějakou statickou proměnnou náhodou změníš, tak už musíš implementovat i nějaké základní semafory apod. Čímž roste práce.

  1. Pletes si tridy a instance trid. Ja mluvim o 1 tride navic (Money + MoneyManipulator), ty o N – 1 instancich. Ano budes mit N – 1 instanci mene, ale o N poli vice, coz je viz bod 1 o cca 3× vice pameti.

Ne nepletu. Když mám pole/ActiveRow/ArrayHash, tak mám jen třídu MoneyManipulator. A třída Money neexistuje. Pokud pak třída Money má stejný interface jako například třída Weight, nebo Třída Distance, která taky uchovává hodnotu a jednotku stejně jako třída Money, tak místo tříd Money, Weight a Distance mám jedinou třídu Manipulator, která pracuje s Array a tím pádem nemám v tomto případě 3 trídy, 3 use a tak, ale jen 1 třídu a 1 use.

  1. Pochopil jsem jinak, ano, pokud nezapomenes pouzit nekde v kodu manipulator. Ja zapomenout pouzit Email nemuzu, protoze si ho uz vsude predavam. :)

Nemůžu jej zapomenout, když bez něj nedostanu data z db a ani je tam neuložím. Ale chápu, že třída, která se stará sama o sebe je pohodlnější

Poslední otázka: Jak je tedy možné, že když použiju na získání dat z databáze Nette Database Explorer, a na ta samá data použiji Doctrine, tak u Nette Database Explorer projde i čas i paměť v pohodě, ale u doctrine to nejdřív spadne na paměti a když navýším množství paměti, tak se stejný objem dat jako z Nette Database Explorer místo 1 vteřiny načítá 20 vteřin? (Počítám, že v Nette Database selectuju všechny sloupečky, které selectuje i doctrina.)

Editoval Polki (2. 8. 2019 15:45)

Jan Mikeš
Člen | 771
+
0
-

Money, Weight a Distance mám jedinou třídu Manipulator

Ale ty nechceš mít jednu třídu :-). Protože peníze, váha a vzdálenost jsou 3 různé věci. Kdyby jsi dostal v hodině matematiky příklad:

1km + 2kg + 3kč = x,
Jaký je výsledek X?

Jako velký fanoušek DDD je pro mě tohle velký no-go a celou tu diskusi imho shrnuje dobře @jiripudil – ty žiješ ve světě anémického doménového modelu, ve kterém je úplně jedno co používáš pro připojení k databázi, nedbáš tolik na immutabilitu, striktní datové typy, wrappery pro primitives (VO), pracuješ víceméně jenom s primitivními typy. Nejlepší performance optimalizace vždy stejně dosáhneš s PDO nebo mysqli či s jinou co nejvíc low level vrstvou, která nepřidává mnoho overheadu.

Potom tady je druhá půlka lidí (čísla jsou vymyšlená), která žije ve světe rich domain modelu a pro takové tady je doctrina a podle mě ti se ti tady snažili přínosy doctrine vysvětlit, ale ty je nebereš, protože pro tebe rich domain model není zajímavý a nechceš nebo nepotřebuješ ho.

Editoval Jan Mikeš (2. 8. 2019 18:32)

F.Vesely
Člen | 368
+
0
-

Za me bych to take rad nejak uzavrel.

Ano, pokud takto definujes pole, tak dostanes nejmensi pametovou narocnost, vyhral jsi. :) Pro me ale stale nad tou pametovou narocnosti daleko prevysuji ty ostatni vyhody trid, ktere jsme ti zde vyjmenovali.

Aha, takze ty Manipulator pouzivas na cteni a zapis do DB, takze vlastne tam bys mapoval Money na DB. Tohle mi v Doctrine odpada, protoze to za me dela ona.

Tva posledni otazka zde uz byla nekolikrat zodpovezena: Protoze Doctrine ma nejakou rezii a trva ji, nez namapuje pole na objekt. Dale si taky Doctrina vsechny vytvorene objekty uklada, aby je v budoucnu uz nemusela znovu zbytecne vytvaret a pri flush mohla ukladat jejich zmeny. Taky se ti zde snazili vysvetlit, ze to neni spravny postup pouzivani Doctrine a jak jej muzes resit.