Defaultní hodnota členské proměnné v názvu getteru – PHP – Fórum – Programujte.com
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu

Defaultní hodnota členské proměnné v názvu getteru – PHP – Fórum – Programujte.comDefaultní hodnota členské proměnné v názvu getteru – PHP – Fórum – Programujte.com

 

ondrej39+1
Věrný člen
25. 9. 2015   #1
-
0
-

Řešíte v kódu nějakým způsobem scénáře, kdy se do nepovinné členské proměnné např. v konstruktoru nic nepřiřadí a taková proměnná tedy "spadne" do defaultní hodnoty?

Znázorňujete poté v getteru k dané proměnné, že může taková situace nastat, nebo situaci ignorujete?

final class A
{
    /** @var string */
    private $_prop = '';

    /**
     * @param string $prop
     */
    public function __construct($prop = '')
    {
        $this->_prop = $prop;
    }

    /**
     * @return string
     */
    public function getPropOrEmptyString()
    {
        return $this->_prop;
    }
}
Nahlásit jako SPAM
IP: 78.156.159.–
Inject all the dependencies!
Kit+15
Guru
25. 9. 2015   #2
-
0
-

#1 ondrej39
Gettery ani settery nepoužívám, proto takové problémy ani nemusím řešit. Nepoužívám ani podtržítka v názvu property, protože $this-> je dostatečně výmluvné. Místo getteru používám magickou metodu __toString(), která mi objekt převede do podoby vhodné k prezentaci. Nepoužívám ani "final" - v PHP ho považuji za zcela zbytečné. Nepoužívám ani zkratky v názvu proměnných. Nepoužívám ani zbytečné komentáře.

Po aplikaci zmíněných pravidel by tvůj příklad v mém podání vypadal asi takto

<?php
class Alfa {
    private $property;

    public function __construct($property = '') {
        $this->property = $property;
    }

    public function __toString() {
        return "Hodnota: " . $this->property;
    }
}
Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
25. 9. 2015   #3
-
0
-

#2 Kit
V případě, že chceš ze třídy vyzobat jen určitá data a nechceš vyplivnout všechno ve formátu stringu jsi se svým přístupem ale docela v prdeli. Chci vidět, jak budeš skládat do stringu model, který obsahuje 3-4 submodely, z nich každý má 3-6 specifických vlastností. A chci vidět, jak elegantně z toho stringu potom budeš dostávat jednotlivá data zase nazpátek (Explodování dle určitých pravidel? Operace úplně navíc.)

Zkratky taky nepoužívám, toto byl jen příklad, PHPDoc anotaci rozhodně za zbytečnou nepovažuji, vzhledem k tomu, že IDE z ní tahá informace o návratových typech a stejně se mi generuje automaticky.

Nahlásit jako SPAM
IP: 78.156.159.–
Inject all the dependencies!
Kit+15
Guru
25. 9. 2015   #4
-
0
-

#3 ondrej39
Jsi úplně vedle. Všechny operace s property dělám uvnitř objektu. To znamená, že jen přidám metodu, která mi provede požadovanou operaci a případně ven dodá data v potřebném formátu. Tedy žádné explodování se nekoná.

Obvykle mám ve třídě 2-4 atributy a ostatním může být úplně jedno, jak se jmenují. Z vnějšku na ně nemá nikdo žádný důvod sahat a nic mu není do jejich obsahu. Jsou prostě pevně zapouzdřeny v objektu a skryty před okolím. Pokud někdo něco chce, musí oslovit objekt. Ten rozhodne, který atribut použije jakým způsobem. Bohužel jsi neuvedl žádný příklad použití své třídy, proto ti ani já nemohu kontrovat svým příkladem.

IDE nepoužívám, proto ani nepotřebuji anotace. Pouze zbytečně obfuskují program.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
25. 9. 2015   #5
-
0
-

#4 Kit
Samozřejmě, že za operace s vlastnostmi objektu je zodpovědný objekt samotný a ne nikdo jiný, tím ale vysvětluješ nepoužívání setterů, gettery ti zapouzdření neporušují.

To znamená, že jen přidám metodu, která mi provede požadovanou operaci a případně ven dodá data v potřebném formátu. Tedy žádné explodování se nekoná.

To cos právě popsal je getter, metoda poskytující veřejné API pro získání jinak skrytých členských proměnných objektu.

Zmíněná třída A má být úplně hloupej model, dle specifikací SRP. Zajímá mě prostě názvosloví getteru pro vlastnost, která může spadnout do defaultní hodnoty, zda má smysl do názvu metody tuto defaultní hodnotu zmiňovat, nebo ne. Toť vše.

Nahlásit jako SPAM
IP: 78.156.159.–
Inject all the dependencies!
Kit+15
Guru
25. 9. 2015   #6
-
0
-

#5 ondrej39
Gettery a settery obecně porušují zapouzdření, protože mění atributy objektu "pod rukama". Jsou jen důsledkem neschopnosti programátorů přemýšlet objektově. A ne, neposkytuji hodnoty atributů vně objektu. Držím si je skryté uvnitř, aby mi na ně nikdo nemohl.

SRP bych se raději neoháněl, protože ho ve své třídě A porušuješ. Název metody getPropOrEmptyString() je vším možným, jen ne "single".

Pokud ti jde jen o názvosloví getteru, tak nic lepšího než getProp() v daném případě nevymyslíš. Samozřejmě je z mého pohledu taková metoda zcela zbytečná.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
25. 9. 2015   #7
-
0
-

#6 Kit
Vysvětli mi tedy, prosím, čím vzorová třída A SRP porušuje.

Ten model je úplně blbej, neví nic o persistenci, neví nic o validaci, neví nic o ničem jiném, jen slouží k uchovávání dat. Jestli i takhle hloupej model může porušovat SRP, tak nejspíš chápu SOLID úplně špatně.

Nahlásit jako SPAM
IP: 78.156.159.–
Inject all the dependencies!
Kit+15
Guru
25. 9. 2015   #8
-
0
-

#7 ondrej39
Pokud třída A pouze uchovává jednu hodnotu, kterou v případě potřeby prezentuje, je zcela zbytečná. Hodnota může být stejně dobře prezentována obyčejnou proměnnou a nemusíš předstírat, že děláš OOP.

SRP říká, že třída nebo její komponenta má mít právě jeden důvod ke změně. Co uděláš ve chvíli, kdy atribut $this->prop přejmenuješ na $this->property? Možné scénáře:

  • Název metody getPropOrEmptyString() ponecháš tak jak je, pouze změníš uvnitř název $this->property. Časem tvá aplikace bude vypadat zajímavě.
  • Název metody přejmenuješ na getPropertyOrEmptyString() a vypropaguješ tu změnu nejen do aktuální aplikace, ale zpětně i do všech ostatních aplikací, ve kterých jsi kdy tuto třídu použil. Pozdravuj dr. Chocholouška.

Tím chci říct, že i když ve svých třídách změním názvy atributů, případně nějaké doplním či vyškrtnu, nemusím nijak měnit rozhraní. Zároveň se mi udržují jednoduché názvy metod - zpravidla jednoslovní. Navíc spousta mých tříd má jednotné názvy metod - např. insert(), delete(), create(), notify() apod. Tím se nádherně zjednodušují všechny interface, které používám: Například mohu objekty různých tříd ukládat do kolekcí a hromadně volat jejich metody přes observer.

Kdyby každá třída měla svou sadu getterů a setterů, bylo by to nerealizovatelné. Nemusím si pamatovat, jaké má ta která třída atributy a tedy i názvy getterů/setterů. Prostě zavolám metodu objektu, která má něco udělat s daty. Název té metody dostatečně vystihuje, co se má udělat. Jak to ten objekt udělá a které atributy přitom použije, je pouze jeho starost.

Tyhle postupy mi umožňují používat třídy i bez dokumentace. Zároveň je udržují jednoduché a přehledné - málokdy mi třída překročí 50 řádek a metoda 10 řádek.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
25. 9. 2015   #9
-
0
-

#8 Kit
Ok, Kite...

Single Responsibility Principle, aneb Třída by měla mít jen jeden důvod ke změně

Toto první pravidlo z pěti SOLID zásad nemá nic společného s přejmenováváním vnitřních proměnných, ani s názvy getterů/setterů, nebo jakýchkoliv jiných metod, které třída může jako své veřejné rozhraní poskytovat. SRP znamená, že se má třída starat pouze o jednu věc a tím to hasne.

Typickým narušitelem SRP je Active Row, tedy třída, která data uchovává, získává, a ukládá. Přitom bys správně měl mít úplně blbej model, sloužící jen pro přenos dat, který (pravděpodobně přes konstruktor) předáš DataMapperu implementující interface a ten si data uloží, kam má.

Argumentovat přejmenováním proměnných je nesmysl a vychází právě z toho, že nepoužíváš IDE. Přejmenování mě osobně v kódu běžně vůbec netrápí, protože kód prostě refaktoruji přejmenovávací funkcí a metody se mi přejmenují tak, jak mají.

Možná mě akorát špatně chápeš, já v prvním příspěvku měl na mysli třídu, která slouží pouze pro reprezentaci dat, třída, která v sobě uchovává minimum logiky... Prostě model. Nebudu přeci data přenášet v polích, abych si musel pamatovat, jaké indexy pole má a co se v něm přesně nachází, mnohem pohodlnější je mít model, na němž zavolám get a okamžitě vidím všechny vlastnosti objektu, které mohu získat.

Nahlásit jako SPAM
IP: 78.156.159.–
Inject all the dependencies!
Kit+15
Guru
25. 9. 2015   #10
-
0
-

#9 ondrej39

Jak se ti přejmenuje volání v ostatních aplikacích, ve kterých tu třídu používáš, ale nejsou otevřeny v aktuálním IDE? Nepřeceňuješ schopnosti IDE?

Kde jsi přišel na to, že model nemá logiku? Vždyť v modelu je uložena celá business logika aplikace. Prostě řeknu modelu, aby udělal operaci a v parametrech metody mu jen předám data. Říkej si tomu třeba setter, ale podle názvu vůbec není poznat, které atributy to ovlivní. To ví jen model, kterému ty atributy patří.

Opakuji, že vlastnosti objektu mimo něj k ničemu nepotřebuji. Proto ani nepotřebuji gettery.

Typickým narušením SRP je, když změníš název metody ve třídě a změní se tím všechny třídy, které tu metodu volají. Změním název metody v jedné třídě a IDE zmodifikuje třeba dvacet jiných tříd, do kterých jsem třeba ani neměl právo zasahovat, protože je psal někdo jiný. Tak vznikají nejrůznější kolize v repozitářích.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
25. 9. 2015   #11
-
0
-

#10 Kit
Pokud Kite měníš název metody jedné třídy a změní se tím volání ve dvaceti ostatních třídách ve třech různých aplikacích, máš ty aplikace blbě udělané.

Pokud vyvíjíš nějakou knihovničku třeba pro aktuální projekt, pak můžeš volat její metody přímo, pokud se ale rozhodneš, že knihovnička je dobrá a chtěl bys ji použít v jiném projektu, klidně svém, pak bys tuto knihovnu měl přesunout na dostupné úložiště kvůli verzování, v PHP například packagist pro composer, napsat si v původním projektu pro tuto svoji knihovničku, byť jsi ji udělal ty, adaptér, stejně jako si napsat adaptér ve všech ostatních projektech, které knihovničku mají používat, a používat ve své aplikaci interface adaptéru.

Nehledě na to, že když budeš svou knihovničku takhle verzovat, pouze nahraješ novou verzi právě třeba na zmíněný packagist a v ostatních projektech můžeš stále používat verzi starší, tudíž nemusíš přepisovat vůbec nic.

Píšeš, že vlastnosti objektu mimo něj nikde nepotřebuješ... Dejme tomu, že máš třídu Člověk, která má atributy datum narození, pohlaví, jméno, příjmení, barva pleti. Jakým způsobem na interface (na user interface, presenční vrstvu) vypisuješ vlastnosti o takovém člověku jinak, než že přistoupíš k jeho skrytým proměnným, když tedy nepoužíváš accessory?

Typickým narušením SRP je, když změníš název metody ve třídě a změní se tím všechny třídy, které tu metodu volají. Změním název metody v jedné třídě a IDE zmodifikuje třeba dvacet jiných tříd, do kterých jsem třeba ani neměl právo zasahovat, protože je psal někdo jiný. Tak vznikají nejrůznější kolize v repozitářích.

SRP je zcela o něčem jiném. Chápeš to úplně špatně.

Nahlásit jako SPAM
IP: 79.141.243.–
Inject all the dependencies!
Kit+15
Guru
25. 9. 2015   #12
-
0
-

#11 ondrej39
Názvy veřejných metod neměním, nemají totiž na názvy vnitřních proměnných žádnou vazbu.

Pokud dodržuji OCP, nepotřebuji žádné adaptéry. Prostě tu třídu použiji přímo. Pokud potřebuji novou metodu, tak ji tam prostě přidám. Na předchozí aplikace, které tu třídu používají, to nemá vliv. Totéž jako další atributy. Dokud třídu pouze rozšiřuješ, není s tím žádný problém.

Verzování knihoven je poněkud jiná kapitola. Většinou jen zkopíruji tu jednu (resp. několik) třídu, která se mi hodí do nového projektu. Typicky to bývají definice rozhraní, router, model a adaptér k databázi. Jsou to tak různorodé věci, že se neodvažuji dát je do jedné knihovny (navíc každá z nich je v jiném namespace), ale přitom jsou v každé aplikaci totožné. Tvoří mi její kostru.

A SRP? Když modifikuji jednu třídu, chci modifikovat právě tu jednu třídu a ne všechny, které se na ni odkazují.

Pokud potřebuji data objektu třídy Člověk, tak je z ní pošlu přes Messengera. Typicky pokud potřebuji třeba JSON pro AJAX, stačí do třídy implementovat rozhraní JsonSerializable. Vytvoří se ti nádherný strom, který jen pošleš klientovi. Vůbec nemusíš vyzobávat jednotlivé atributy objektu a slepovat je do stringu. Udělá to za tebe funkce json_encode($clovek).

Možná se zeptáš: Jak z té třídy vygeneruješ jiný strom s jinými atributy? Odpověď zní: Nijak. Porušil bych tím SRP. Ta třída by pak dělala víc věcí, než má. Raději pro nový účel napíši novou třídu. Samozřejmě s respektováním DRY, což není vůbec žádný problém - takové třídy bývají velmi jednoduché, obvykle do 20 řádek. Společné atributy řeším dědičností nebo kompozicí jiné třídy.

Právě proto jsem přesvědčen, že porušuješ SRP. Používáš jednu třídu k mnoha účelům a snažíš se vytvářet maximální rozhraní, aby uspokojila všechny potřeby zbytku aplikace. V konečném důsledku však z toho máš jen jinak pojmenované "asociativní pole", ke kterému jsi jen dopsal gettery a settery - aby to nevypadalo tak blbě a mohl ses chlubit "OOP". Ve skutečnosti to však s OOP nemá mnoho společného.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
25. 9. 2015   #13
-
0
-

#12 Kit
A to rozhraní JSONSerializable chceš implementovat na jaké třídě? Na té třídě člověk?

Nahlásit jako SPAM
IP: 79.141.241.–
Inject all the dependencies!
Kit+15
Guru
25. 9. 2015   #14
-
0
-

#13 ondrej39
Samozřejmě. Kde jinde? Vypadá to asi takhle:  

<?php
class Clovek implements JsonSerializable {
    private $jmeno;
    private $boty;

    public function __construct($jmeno = '', $boty = 0) {
        $this->jmeno = $jmeno;
        $this->boty = $boty;
    }

    function jsonSerialize() {
        return array($this->jmeno, $this->boty);
    }
}

$a = new Clovek('Adam', 42);
echo json_encode($a);
Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
25. 9. 2015   #15
-
0
-

#14 Kit
Takže máš třídu, která uchovává data (první zodpovědnost), je pověřená transformací sebe sama do reprezentace pro JSON (druhá zodpovědnost) a v tvém případě dost možná ještě bude sebe sama transformovat do stringu (třetí zodpovědnost)? A přitom mi říkáš, že uvedení getteru porušuje pravidlo jedné zodpovědnosti, zatímco tvoje třída má tři?

Nahlásit jako SPAM
IP: 46.135.49.–
Inject all the dependencies!
Kit+15
Guru
25. 9. 2015   #16
-
0
-

#15 ondrej39
Moje třída má jen jeden úkol: Vyrobit podklady pro JSON. Je to napsáno v interface vedle jména třídy. Povinnost uchovávat data má každá třída kromě statických.

Transformaci do stringu dělá funkce json_encode(), pokud to nevidíš. Je mimo třídu.

Tvoje třídy ani neví, k čemu budou sloužit. Poskytnout data každému, kdo si řekne, v syrovém stavu? Tomu říkáš SRP? To je spíš ZRP (Zero Responsibility Principle). K čemu ta tvoje třída vlastně je, když nic neumí? Kdo bude dělat její práci?

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
25. 9. 2015   #17
-
0
-

#16 Kit
Za práci s daty je v mém případě zodpovědná servisní vrstva, ta se stará o ukládání, starala by se například o zajištění JSON výstupu pro daný objekt,...

Ukázal bys mi, prosím tě, jak si ty za běhu aplikace předáváš v PHP data, protože tvůj princip bez getterů fakt nepobírám.

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
26. 9. 2015   #18
-
0
-

#17 ondrej39
Data nepředávám, protože není komu. Nikdo je nechce :)

Jestli ses podíval pořádně, tak metoda jsonSerialize() nevrací JSON, ale messenger typu array.

Při tvorbě jsem metodiky jsem se nechal inspirovat trojicí tříd PDO, PDOStatement a PDOException. Metoda PDO::prepare() je vlastně továrnou na objekty třídy PDOStatement. Metody PDOStatement::fetch() a PDOStatement::fetchAll() jsou opět továrnami na array, object nebo objekt nějaké vlastní třídy. Všechno to jsou vlastně messengery. Příjemci si s nimi mohou dělat co chtějí, protože mají veřejné atributy.

Servisní vrstva v mých aplikacích pracuje s databází nebo jiným úložištěm, které jsou jinak od aplikace odříznuty. Jediným, kdo ví, kde je databáze, je Model a ten to nikomu nepoví - pouze servisní vrstvě. Dokáže jí předat požadavek pro čtení či modifikaci od controlleru nebo view. View obdrží messenger s daty získanými z databáze (nebo jiného datového zdroje).

Třída Clovek by se v daném případě jmenovala Clovek_View. Místo popisu příklad:

class Clovek_View implements JsonSerializable {
    private $clovek;

    public function __construct($post) {
        $this->clovek = new Clovek($post);
    }

    function jsonSerialize() {
        return $this->model->select($this->clovek));
    }
}

V případě kompletní stránky těch atributů bývá víc - podle počtu domén, které se mají na stránce zobrazit. A tohle je to mé tajemství: Místo setteru mám konstruktor a místo getteru messenger. Přenáším vždy všechny potřebné atributy naráz. Tím odpadají veškeré obstrukce, kód je kratší, přehlednější a mnohem rychlejší. Zároveň mám vše pečlivě zapouzdřeno - nikde se mi nepotulují volné atributy, které by nebyly vázány na nějaký objekt nebo messenger.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
26. 9. 2015   #19
-
0
-

#18 Kit
Pořád nechápu, jak teda data vypisuješ do grafického rozhraní... V případě byť i jednoduché stránky, například takové...

<html>
<head>
    <title>Informace o člověku: </title>
</head>
<div id="zakladni-informace">
    <strong>Jméno:</strong>
    <strong>Příjmení:</strong>
    <strong>Věk člověka:</strong>
    <strong>Národnost:</strong>
</div>
</html>

Jak vypisuješ data získané z DB tedy ty?

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
26. 9. 2015   #20
-
0
-

#19 ondrej39
Však zalistuj výš a všimni si metody __toString().

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
26. 9. 2015   #21
-
0
-

#20 Kit
Ale __toString metodu můžeš mít jenom jednu a vrátí ti string... To jako přímo v __toString metodě máš HTML kód?

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
26. 9. 2015   #22
-
0
-

#21 ondrej39
Však stačí jedna metoda __toString(). Jinak bych porušil SRP.

V té metodě obvykle jen volám šablonovací systém, kterému jako parametr předhodím datový strom. Šablona podle struktury dat ve stromu vygeneruje kompletní HTML. V hlavním modulu pak mám už jen "echo $view;" Je to prakticky jediné echo v celé aplikaci.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
26. 9. 2015   #23
-
0
-

#22 Kit
Dejme tomu, že máš třídu SystemUser, kterou máš uloženou v SESSION a která obsahuje data o aktuálně přihlášeném uživateli (obsahuje jeho IP adresu, informace o prohlížeči, uživatelské jméno, datum a čas registrace, datum a čas poslední aktivity,...).

Takový uživatel půjde na svůj profil a nechá si zobrazit informace o svém profilu, například pro editaci. Navíc k tomuto chceš mít v hlavičce stránky napsaný text například:

Vítejte zpátky, <uživatelské jméno přihlášeného uživatele>! Naposledy jste zde byl <výpis data a času poslední aktivity>.

Jak řešíš tento problém, když chceš v obsahu stránky zobrazit celkový obsah objektu a nahoře v hlavičce pouze některé jeho informace? Jak k datům přistupuješ?

Eventuálně, pokud je to možné, mohl bys napsat reálný příklad, jak tvá __toString metoda, tedy metoda, kterou používáš (předpokládám) pro šablonovací nástroj, vypadá?

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
26. 9. 2015   #24
-
0
-

#23 ondrej39
Té otázce moc nerozumím. Pokud chci na stránce jiná data, použiji jiný view, ale zpravidla stejnou šablonu. Pokud view nedodá šabloně data pro hlavičku stránky, tak se ta část vůbec nezobrazí nebo se zobrazí nějaká alternativa. To už si řeší šablona.

Jeden view tedy pošle šabloně pouze $data, jiný view jí pošle i $profil. Další view nemusí šabloně poslat vůbec nic a zobrazí se jen nepříliš užitečná holá kostra stránky.

Metoda vypadá (jako vždy) poměrně jednoduše: 

class View {

    function __toString() {
        $sablona = new DOMDocument();
        $sablona->load($this->template);
        $sablona->formatOutput = true;
        $xsl = new XSLTProcessor();
        $xsl->importStyleSheet($sablona);
        return $xsl->transformToXML($this->doc);
    }
}

Třída View  je rodičem všech viewů, které si vždy vyrobí vlastní datový strom $this->doc. Proto nepotřebují vlastní metodu __toString() - použije se rodičovská.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
26. 9. 2015   #25
-
0
-

#24 Kit
A v $this->template máš co?

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
26. 9. 2015   #26
-
0
-

#25 ondrej39
Jak název proměnné napovídá, v $this->template mám název souboru s výstupní šablonou. Pro každý view mám jinou, ale základní kostru mají společnou v dalším template, který si umí naimportovat a případně některé části překrýt. Funguje to podobně jako dědičnost.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
26. 9. 2015   #27
-
0
-

#26 Kit
Dobře, v tom souboru s šablonou máš již tedy rozložení konkrétní stránky, prostě design, v tom konkrétním rozložení vypisuješ data jakým způsobem? Ta data přece musí vědět, kde se přesně mají zobrazit.

Edit: Mohl bys demonstrovat, jak vypadá obsah toho souboru s šablonou?

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
26. 9. 2015   #28
-
0
-

#27 ondrej39
Kousek XSLT sem můžu poslat: 

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output
    method="html"
    doctype-system="about:legacy-compat"
    encoding="UTF-8"
/>

<xsl:template match="/data">
<html lang="cs-cz">
    <head>
        <meta charset="utf-8" />
        <title><xsl:value-of select="@title"/> - <xsl:value-of select="@project"/></title>
        <link rel="stylesheet" href="style.css" type="text/css"/>
        <script src="jquery-1.11.0.js"/>
    </head>
    <body>
        <xsl:apply-templates/>
    </body>
</html>
</xsl:template>
</xsl:stylesheet>

Data vůbec netuší zda, kde a v jakém formátu se budou zobrazovat. To má na starosti šablona, případně CSS. Aplikace pouze produkuje data, o víc se nestará. SRP.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
26. 9. 2015   #29
-
0
-

#28 Kit
Tady v konkrétním příkladě se odkazuješ na proměnné @title a @project, přes PHP ale do těchto proměnných musíš něco nahrát, jinak by v nic nic nebylo. Stále nějak nechápu, kde se tohle děje.

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
26. 9. 2015   #30
-
0
-

#29 ondrej39
Jistě. Na to jsou ve třídě DOMDocument vhodné metody createElement(), createAttribute(), appendChild(),... Stačí je jen použít a dát jim správné parametry. Je to jen naplnění seznamu (resp. stromu) objekty. Jsou v něm pouze data.

Když si ten strom necháš vypsat ve formátu XML, dostaneš tohle: 

<?xml version="1.0"?>
<data title="Titulek podstránky" project="Název projektu">
    <userMenu>
        <item href="/Profile/">Profil</item>
        <item href="/Catalog/">Katalog</item>
        <item href="/Cart/">Košík</item>
        <item href="/Contact/">Kontakt</item>
    </userMenu>
    <catalog>
        <item href="/Catalog/HomeGarden/">Dům a zahrada</item>
        <item href="/Catalog/Electronics/">Elektronika</item>
        <item href="/Catalog/Tools/">Nářadí</item>
    </catalog>
    <article title="Nadpis článku" perex="Perex článku">
        Text článku
    </article>
</data>

Strukturu jsem si vymyslel pro účely demonstrace. Element userMenu si naplním ve třídě UserMenu, ale element catalog naplním z databáze - podle aktuálních kategorií.

Každá třída obsluhující položku menu se vždy jmenuje stejně jako první slovo v href, tedy v daném případě ProfileView, CatalogView, CartView, resp. ContactView a je potomkem třídy View. Díky tomu nepotřebuji switch při rozhodování, které třídy bude nový objekt. Název té třídy poskládám z URL a provedu "new". Všechny mají stejné parametry konstruktoru.

Podobně mám udělané i controllery, které jsou určeny k modifikaci dat (samozřejmě opět prostřednictvím modelu). Na konci však vždy místo prezentace dělám přesměrování do patřičného view prostřednictvím HTTP 302.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
26. 9. 2015   #31
-
0
-

#30 Kit
Takže máš vlastně model, který ti naplňuje view daty získanými z databáze? Jak pak máš takovou třídu pojmenovanou? class DatabaseUserModelWhichAlsoPopulatesViewWithData?

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
26. 9. 2015   #32
-
0
-

#31 ondrej39
Kterou třídu máš na mysli? Model se jmenuje Model. View na prohlížení katalogu se jmenuje CatalogView. Příslušná doména v servisní vrstvě se jmenuje Catalog a controller se jmenuje CatalogController.

Třída CatalogView má na starosti prezentaci stránky z katalogu včetně záhlaví s profilem (dostane ho z domény Profile) a bočního menu s ostatními katalogy (dostane ho z domény Catalog). To jsou tři stromy, které jen spojím do jednoho stromu $this->doc.

Název třídy by neměl obsahovat informaci o tom, co nedělá, ale o tom, co dělá. Tato třída prezentuje katalog, proto se jmenuje CatalogView.

Jak vidíš, jednotlivé kompetence mám hezky zapouzdřeny do poměrně malých bloků tak, aby každý z nich splňoval nejen SRP, ale i ostatní záležitosti ze SOLID.

Ještě jsi nenapsal, jak bys to řešil ty. Z mého pohledu každá tvá třída ten SRP porušuje. Zatím ses mi to ani nepokusil vyvrátit. Ukaž mi třeba některou ze tříd, která využívá tu tvou finální třídu A.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
Kit+15
Guru
26. 9. 2015   #33
-
0
-

#31 ondrej39
Ještě zkusím rozložit tvůj obrozenecký název třídy:

  • Database - View neví nic o existenci databáze. Pryč s tím.
  • User - View má prezentovat stránku. Může na ní být třeba 7 domén. Je zbytečné uvádět všechny, stačí tu skupinu nazvat podle nejdůležitější z nich.
  • Model - Každý View pracuje s modelem. Je tedy zbytečné tuto samozřejmost uvádět v názvu. Stačí, když ten model dostane v konstruktoru.
  • Which -
  • Also - pouze spojovací slova
  • Populates - to dělá každý view, je to v názvu
  • View - Ano to tam mám.
  • With - pouze předložka
  • Data - Místo nic neříkajícího "Data" jsem použil "Catalog", ale stejně dobře by v jiných situacích posloužila slova User, Profile, Cart, Item,...

Zbylo mi z toho skutečně jen CatalogView, UserView, ProfileView, CartView, ItemView,...

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
26. 9. 2015   #34
-
0
-

#33 Kit
Tady máš ukázku víceméně toho, jak strukturuji své projekty já, co se tedy modelů a persistence týče. Potřebné data mappery, služby,... v běžných projektech generuji přes továrničky, k nimž přistupuji v kontrolerech skrze DI kontejner, do něhož potřebné věci registruji přes bootstrap.

K šablonování používám twig šablonovací engine, do něhož přes příslušný controller vkládám modely.

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
26. 9. 2015   #35
-
0
-

#34 ondrej39
Tak se tedy do tebe pustím:

  • UserModel: metody changeEmail(), changePassword() - to jsou dvě aktivity. Porušuješ SRP.
  • UserMapper: máš ho podobně jako já, jen místo názvu metody get() používám select() končící řádkem "return $select->fetch(\PDO::FETCH_OBJ);" Na rozdíl od tebe v této fázi DateTime neřeším a všechny metody mají jen jediný parametr - databázi. Pro každý typ databáze mohu mít jiný SQL dotaz nebo v jednodušších případech klidně jeden společný.
  • DbMapper: používáš protected? Proč? Porušuješ tím zapouzdření. V daném případě zbytečně.
  • DataMapper: 4 metody v interface? Na co tolik? To pokaždé povinně implementuješ všechny čtyři? Tomu říkáš SRP?
  • DbDriver a jeho implementace MySQLPdoDriver: na můj vkus příliš primitivní. Neřeší lazy inicializaci (co když tu databázi nebudu potřebovat?) ani prepared statements (např. potřebuji vložit 10 záznamů. Na to mi stačí 1× prepare() a 10× execute()).

Nenechal jsem sice na tvém řešení ani bit suchý, ale celkově to od mého řešení nemáš příliš daleko. Jen těch skrytých závislostí máš v každém modulu podstatně víc. Jak je mockuješ v testech?

DI kontejner ani bootstrap jsem tam nepostřehl. Nepoužívám je a vlastně ani nevím, jak vypadají. DI a závislosti si sestavuji za běhu sám, protože to není nic obtížného a značně to šetří výkonem.

Proč před každým názvem atributu používáš podtržítko? Má to nějaký hlubší význam? Dle mého názoru zcela stačí $this->variable. Je to jasný odkaz na privátní atribut. Nebo to snad vyžaduje IDE?

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
26. 9. 2015   #36
-
0
-

#35 Kit
V mých návrzích jsou třídy buď abstraktní, nebo finální. Pokud mám abstraktní třídu, musím mít vlastnosti protected, jinak se k nim děcka nedostanou.

10x prepare dělat nemusím, když dobře poskládám query, to už je ale spíš o tom, kdo to jak dělá než o tom, co je dobré a správné.

Podtržítko je moje konvence z dob, kdy jsem pracoval na třídách mající public i protected/private vlastnosti, používám protože jsem na to zvyklý.

K ostatnímu se vyjádřím zítra, na mobilu se blbě píše.

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
ondrej39+1
Věrný člen
27. 9. 2015   #37
-
0
-

#35 Kit
Model, metody changeEmail a changePassword a Single Responsibility Principle

Asi záleží na úhlu pohledu, jak na konkrétní třídu nahlížíš, v mém případě jsou instance třídy Model objekty, které obsahují data, starají se o ně a dělají svoji práci dobře. Poskytují jednoduché veřejné rozhraní, které uživatel může volat, aniž by ho zajímalo, co se ve skutečnosti děje uvnitř objektu s daty, které například přes mutator nastavit nejdou.

To je jejich zodpovědnost, starat se o svá data. Že poskytují moje modely veřejné rozhraní skrze accessory či mutatory osobně nepovažuji za porušení SRP, protože stále třída pracuje pouze se sebou sama a svými vlastnostmi. Například pro lastUserActivity setter ani není, protože uživatel používající model nemá mít možnost tuto hodnotu nastavit jen tak, protože se mu chce, tato hodnota se má nastavit při volání určitých metod, které tento stav mohou ovlivnit (například konkrétně změna hesla, což je proces, kdy se aktivně mění vnitřní struktura objektu a je tedy dobré změnu zaevidovat).

Pokud máš dojem, že tebou zmíněné dvě metody porušují SRP, zkus navrhnout řešení, jak bys to udělal ty. Napadá mě mít zcela immutabilní objekt, který by mutatory vůbec neměl a kdybys chtěl změnit vlastnost, tak musíš vytvořit novou instanci toho objektu, což je šileně špatné řešení. Kdybys na druhou stranu měl službu, například UserChangingPasswordService(User $user) : User, i zde nakonec musíš do třídy User ta data nějak dostat -> opět přes mutator, jinak by ses přece k vnitřním částem třídy vůbec nedostal.

Samozřejmě, jak jsi zmínil výše, mít anemický model, kde ke všem protected/private vlastnostem napíšeš accessory/mutatory, je svým způsobem nesmysl a je dobrý jen pro zlepšení přehlednosti, v tom ti nebudu odporovat. Mé modely obsahují i business logiku (například pokud bys měl zboží, spadající do N kategorií, pak bych měl model:

final class Goods
{
    /** @var string */
    private $_name;

    /** @var string[] */
    private $_categories = array();

    /** @var int */
    private $_countOfCategories;

    /**
     * @param string $name
     * @param array $categories
     * @throws InvalidArgumentException
     */
    public function __construct($name, array $categories = array())
    {
        if ($name === null || $name === "") {
            throw new InvalidArgumentException("Name must mot be null or empty string.");
        }

        $this->_name = $name;
        $this->_categories = $categories;
        $this->_countOfCategories = count($categories);
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->_name;
    }

    /**
     * @return string[]
     */
    public function getCategories()
    {
        return $this->_categories;
    }

    /**
     * @return int
     */
    public function getCountOfCategories()
    {
        return $this->_countOfCategories;
    }

    /**
     * @param string $newCategory
     */
    public function addCategory($newCategory)
    {
        if ($newCategory === null || !is_string($newCategory) || $newCategory === "") {
            throw new InvalidArgumentException("A category must be a non-empty string.");
        }

        $this->_categories[] = $newCategory;
        ++$this->_countOfCategories;
    }

    /**
     * @param string $categoryName
     * @throws Exception
     */
    public function removeCategory($categoryName)
    {
        $categoryExists = false;

        if ($key = array_search($categoryName, $this->_categories) !== false) {
            unset($this->_categories[$key]);
            --$this->_countOfCategories;
            $categoryExists = true;
        }

        if ($categoryExists === false) {
            throw new Exception("Goods does not belong to specified category which therefore could not be removed.");
        }
    }
}

Všimni si, že ačkoliv dávám uživateli možnost přidat nebo odebrat kategorii, dávám uživateli také možnost získat počet kategorií, do nichž zboží patří, ale uživateli nedávám možnost tuto hodnotu zvnější změnit. Protože by tím mohl porušit vnitřní stav objektu. Uživatele veřejného API objektu zboží nezajímá, kdy se hodnota proměnné _countOfCategories mění, ale zajímá ho, aby v ní byla vždycky správná hodnota.

DataMapper: 4 metody v interface? Na co tolik? To pokaždé povinně implementuješ všechny čtyři? Tomu říkáš SRP?

Pokud by už můj interface porušoval jedno z pravidel SOLID designu, pak porušuje interface segregation principle (kdy jsem možná mohl rozdělit interface na dvě, DataMapperSave a DataMapperGet), nikoliv SRP. Vždyť ten interface má opět jedinou zodpovědnost. Mapovat objekty na persistenční vrstvu, to je jediná věc, kterou ten interface má dělat, a dělá ji opět dobře (předáš mu Model, on ti ho uloží, kam budeš potřebovat, potřebuješ model, tak ti ho najde a vrátí, pokud k němu budou data).

K 10x prepare jsem se již vyjádřil včera s tím, že já prepare a execute zavolám pouze jednou, akorát musím do doQuery poslar SQL ve správném formátu. Standardně používám pouze jednou prepare a jednou execute, protože se mi osvědčilo, že je tak operace rychlejší, než volat execute 10x, nicméně jsi mi nasadil brouka do hlavy, trošku jsem to studoval a prý by mi na tomto mohla aplikace kleknout, kdybych chtěl například vložit X tisíc záznamu najednou (nezkoušel jsem, zatím jsem se s tím problémem nepotýkal).

Co se lazy iniciace týče, ta se neřeší v tom Driveru. Ty ten samotný driver ani nikdy používat nebudeš, na to máš DataMapper. A DataMapper si běžně získávám přes továrničku, kterou mám uloženou v DI kontejneru a získávám ji teprve tehdy, kdy ji potřebuji, ne dřív, ne později. Vím, že budu pracovat s uživateli, tak mi továrnička na základě specifikací v configu vyplivne instanci DataMapperu takového, aby ukládal a získával data z místa, odkud má (ať je to relační databáze, noSQL databáze, nebo soubor).

Každopádně Kite díky za připomínky, s některými tvými poznatky souhlasím, s jinými ne a co se mi nelíbí je tvůj přístup k šablonování, ale to spíš vychází z toho, že já sám používám na šablony Twig a jsem na něj zvyklý.

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
27. 9. 2015   #38
-
0
-

#37 ondrej39
Předpokládám, že nikdy neměníš současně (přes jeden formulář) e-mail i password. Pokud měníš vždy jen jedno, vychází mi z toho dvě nezávislé třídy: PasswordModel a EmailModel. Každá se stará jen o to jedno a každá má metodu change($email), resp. change($password).

Druhou variantu vidím v případě, kdy v jednom formuláři měníš obojí. V tom případě to ponechám v jedné třídě UserModel, ve které budu mít opět jednu metodu, kterou budu volat: 

$user->change(array('email' => $email, 'password' => $password'));

Prakticky se však to volání smrskne na: 

$user->change($_POST);

protože $_POST má přesně takovou strukturu, jakou k danému účelu potřebuji. Metoda pak vypadá následovně: 

public function change(array $post) {
    $this->updateLastActivity();
    $this->_password = $post['password'];
    $this->_email = $post['email'];
}

Opět mám jen jednu metodu, která naráz mění jednu dvojici hodnot.

Mám v tom samozřejmě i formální validaci, ale nechtěl jsem ten příklad zbytečně komplikovat. Jde o princip.

Příznakem, zda to je či není SRP, je pro mne skutečnost, zda se název proměnné nebo metody skládá z jednoho nebo více slov. Došel jsem totiž k závěru, že všechny názvy tříd, objektů, interface a metod se dají téměř vždy vystihnout jedním slovem. Pokud mám potřebu použít dvojslovní název, zřejmě porušuji SRP.

Tímto (na naše poměry hodně kontroverzním) názorem se liším od názoru velikánů Roberta C. Martina a Martina Fowlera, které jinak respektuji. Tuto odlišnost si vysvětluji tím, že oni vyrůstali v anglosaském prostředí, ve kterém je přirozenější tvořit nová slova skládáním. Slovanské jazyky však tvoří nová slova odvozováním, dávají přednost novému krátkému slovu před slepencem.

Vím, že jsem se svým názorem značně osamocen. Na druhou stranu mi tato metodika pomohla vyčistit a zkrátit hromadu programů. Pomohla mi najít polymorfismy i tam, kde si jich původní autor vůbec nevšiml a složitě používal switche.

Ad lazy inicializace: Ano, DIC to řeší vně třídy, já to řeším uvnitř třídy - prakticky na úrovni stavu jedné proměnné. Mně se to mé řešení jeví jako lepší a spolehlivější. Ten můj driver umí i prepare(), což je továrna na objekty třídy PDOStatement. Tvůj zápis 

$result = $this->_dbDriver->doQuery($sql, array( ":id" => $id,));

v mém podání: 

$select = $this->_dbDriver->prepare($sql);
$select->execute(array( ":id" => $id,));
return $select->fetchAll(\PDO::FETCH_ASSOC);

Je sice o 2 řádky delší, ale umožňuje mi zvolit si, zda chci fetch() či fetchAll() a zda chci výsledek ve formě seznamu(\PDO::FETCH_ARRAY), slovníku (\PDO::FETCH_ASSOC) či anonymního objektu (\PDO::FETCH_OBJ). To je celkem 6 kombinací, ze kterých sis vybral ve svém driveru jen jednu.

Jak vidíš, snažím se překrývat i systémové metody. Místo doQuery() píši pouze query() či prepare(). Zároveň však hledím používat stejné rozhraní, aby nedocházelo ke zbytečným omylům, aby výsledek odpovídal očekávání. Proto má metoda MyPDO::prepare() má stejné parametry a skutečně produkuje objekt třídy PDOStatement.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
Kit+15
Guru
27. 9. 2015   #39
-
0
-

#37 ondrej39
Chvilku jsem válčil s tvou třídou Goods, než jsem si uvědomil, že takové věci vůbec v PHP neřeším. Tohle dělám přímo v databázi, která je na to skvěle vybavena. PHP používám pouze jako wrapper.

Jedním z prvních úkonů však bylo vytržení Category do samostatné třídy. Tvoje třída Goods se totiž stará nejen o zboží, ale i o jeho kategorie, což je opět porušením SRP.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
Kit+15
Guru
27. 9. 2015   #40
-
0
-

#37 ondrej39
XSLT jsem si zvolil proto, že je zcela nezávislým na PHP, dá se použít v jakémkoli jazyce nebo klidně i samostatně, je velmi rychlý a je velmi dobře zdokumentován. Jeho podstatnou nevýhodou je, že jeho naučení trvá déle, než je tomu u jiných šablonovacích systémů, což mnoho začátečníků odradí. Mně trvalo asi rok, než jsem do něj začal "vidět" tak, že většinu šablon mohu psát i bez nahlížení do manuálu. Nádherně vysázené validní HTML je však skvělou odměnou.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
27. 9. 2015   #41
-
0
-

#38 Kit
Mít jednu metodu change, nebo mít dvě metody changeEmail($newEmail), changePassword($password), to už je opravdu úplně jedno. Já to používám rozdělené, protože si nemusím pamatovat, v jakém přesném formátu bych měl pole do metody change(array $parameters) posílat. Prostě si rozkliknu definici metody changeEmail, vidím, že parametr je string, tak já pošlu string. A jestli máš metodu jednu, nebo dvě, opět nemá nic společného se SRP.

K možnosti nastavení návratového typu PDO se vyjadřovat nebudu, jsme prostě dva lidé, kteří to dělají každý jinak, ale ani jeden z postupů není, myslím si, špatný.  

#39 Kit

To se mi Kite moc nelíbí, posílat do DB špatná data a čekat, že za tebe databáze provede ověření, zda jsou data OK, nejen vede ke zbytečnému insertu v případě invalidních dat, ale řekl bych, že by to způsobilo akorát problémy.

Takhle nějak funguje proces ukládání u mě:

<?php

use App\Controllers\Abstraction\Controller,
    App\Lib\Helpers\Encryption\Abstraction\Encryption,
    App\Models\Exceptions\ModelDataNotFoundException;

final class ProfileController extends Controller
{
    public function actionSave()
    {
        if ($this->request()->isPost() === false) {
            $this->DI()->logger()->logError("A user tried to access the save action using other than post method");
            $this->presenter()->flashError("Something went wrong. Please try again.");

            $this->router()->redirectToSamePage();
            return;
        }

        $dataMapper = $this->DI()->createDataMapper("user");

        $userId = $this->request()->get("id");

        try {
            $user = $dataMapper->get($userId);
        } catch (ModelDataNotFoundException $e) {
            $this->DI()->logger()->logError($e);
            $this->presenter()->flashError($e->getMessage());

            $this->router()->redirectToSamePage();
            return;
        }

        $password = $this->request()->post("password");
        $email = $this->request()->post("email");

        $encryptionHelper = $this->DI()->createEncryptionHelper(Encryption::SHA_2);

        $encryptedPassword = $encryptionHelper->encrypt($password);

        try {
            $user->changePassword($encryptedPassword);
            $user->changeEmail($email);
        } catch (InvalidArgumentException $e) {
            $this->DI()->logger()->logError($e);
            $this->presenter()->flashError($e->getMessage());

            $this->router()->redirectToSamePage();
            return;
        }

        $validator = $this->DI()->createValidator("user");
        $validator->validate($user);

        if ($validator->validationHasFailed() === true) {
            $this->DI()->logger()->logError($validator->getErrors());

            foreach ($validator->getErrors() as $validatorError) {
                $this->presenter()->flashError($validatorError);
            }

            $this->router()->redirectToSamePage();
            return;
        }

        $dataMapper->save($user);

        $this->DI()->logger()->logInfo("User {$user->getName()} ({$userId}) has edited his profile.");
        $this->presenter()->flashSuccess("Your profile has been successfully edited.");

        $this->router()->redirectToSamePage();
    }
}

Tvůj návrh, kdy se validace provádí na úrovni úložiště, má jeden neskutečně velký nedostatek. Když nahradíš například MySQL databázi za Redis/MongoDB, které validaci dat nemají (nebo rozhodně ne v takovém formátu, jak bys to potřeboval), tak jsi lidově řečeno v prdeli, a to se mně osobně teda vůbec, ale vůbec nelíbí. Pokud se rozhodneš tu databázi vyměnit například za Redis, pak ti z formuláře do databází budou padat data, která tam nemají co dělat, protože Redis validaci nemá, prostě ti uloží to, co mu pošleš, ty ale čekáš, že se databáze o validaci postará a proces ti zabije. Whoops...

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
27. 9. 2015   #42
-
0
-

#41 ondrej39
Do metody change() dávám jako parametr celý $_POST. Však ona si vybere, co z toho potřebuje. Nechce se mi pamatovat, jestli se metoda jmenuje changePassword(), changeEmail() nebo changeName(). Musel bych mít v rozhraní všechny tři a to je pro mne příliš složité. Stačí mi jedna metoda change() na všechno.

Psal jsem, že jsem z příkladu kvůli jednoduchosti vypustil formální validaci. Možná jsi to jen přehlédl...

Ke třídě ProfileController: Podmínku $this->request()->isPost() považuji za zbytečnou, protože bez HTTP metody POST vůbec controller nevybírám, ale vybírám view.

Místo kódu 

$dataMapper = $this->DI()->createDataMapper("user");

mám o něco jednodušší 

$user = new User($post);

který si z POSTu vezme data, která potřebuje. V testech mu samozřejmě podstrkuji různá data.

No a pak už jen zavolat model, aby změnu provedl: 

$model->change($user);

Logování řeší model observerem, který projde všechny logovací abonenty, které byly modelu injektovány při jeho inicializaci. Controller tohle neřeší. Je ti jistě jasné, že mé controllery jsou podstatně kratší: 

<?php

class ProfileController {
    public function __construct(Modelable $model, array $post) {
        $user = new User($post);
        if (isset($post['Insert'])) {
            $model->insert($user);
            return '.';
        }
        if (isset($post['Update'])) {
            $model->update($user);
            return '.';
        }
        if (isset($post['Delete'])) {
            $model->delete($user);
            return '.';
        }
    }
}

Tento kompletní controller umí vložit, změnit nebo smazat uživatele podle toho, na které tlačítko admin klikl. Třída User má implementovány potřebné metody insert(), update() a delete(). Model jí ještě dodá databázi, aby věděla, kam to má zapsat.

Výjimky zachytávám a ošetřuji v modelu a v routeru. Controllerů a viewů mám hodně a nechce se mi to tam plevelit. Není to ani potřebné. Router také provede závěrečné přesměrování podle hodnoty v return.

Tohle všechno jsem vymyslel hlavně proto, aby se mi jednotlivé komponenty dobře testovaly. Jednotlivé komponenty se snadno injektují a dají se jednoduše nahradit mockem. Díky velmi slabým vzájemným vazbám dají snadno vyměňovat na produkčním serveru i za plného provozu.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
Kit+15
Guru
27. 9. 2015   #43
-
0
-

#41 ondrej39
Ještě k ostatním databázím: Validaci dat řeším ve třídě User. Dřív to nemá valného významu, protože validační pravidla mám ve třídě User nebo v databázi - a do té se nikdo jiný než třída User nedostane. Snad jen model, ale ten do databáze neleze.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
27. 9. 2015   #44
-
0
-

#42 Kit

Třída User má implementovány potřebné metody insert(), update() a delete().

Na co, proboha? Na co má User metodu insert, update nebo delete? Co ten má vědět o jakékoliv z těch zmíněných operací? Docela to smrdí Active Recordem, což je nakonec návrhový vzor, který SRP porušuje.

Validace uvnitř modelu ti opět způsobuje problém, že nemůžeš model validovat podle různých pravidel (třeba pro MySQL databázi budeš vyžadovat validaci jednoho typu a pro Redis validaci jinou).

Ověření isPost tam dávám pro jistotu, kdy byl uživatel blbej a rozhodl se dát přímo do adresního řádku například:

www.domain.com/Profile/id/1234/username/JohnSmith/Save
Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
27. 9. 2015   #45
-
0
-

#44 ondrej39
Někdo přece musí do té databáze zapsat. Kdo jiný? Samotný model to neumí, potřebuje na to servisní vrstvu. A není to Active Record, ten by vypadal úplně jinak.

Právě že třída User si dokáže zjistit, se kterou databází pracuje a podle toho provést validaci.

Pokud blbej uživatel dá něco do příkazového řádku, tak to odešle metodou GET a nikoli POST. Tím pádem se mi to do controlleru vůbec nemůže nasměrovat, ale v uvedeném příkladu se použije třída ProfileView. Ta jen zobrazí uživatele ID 1234 (pokud na to bude mít user právo) a zbytek URL bude v klidu ignorovat.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
27. 9. 2015   #46
-
0
-

#45 Kit
Když si ale třída zjišťuje, s jakou DB má pracovat, tak jaký je potom tedy účel třídy User? Mít svoje data? Starat se o jejich validaci? Zjišťovat si, s jakou DB má pracovat? Navíc k tomu má třída metody insert, update, delete? Co ta třída v tvém případě teda dělá?

Co se toho controlleru týče, tak už to tvoje chápu, ty používáš úplně jiný routování, jak já :). Já prostě mám takovej ten klasickej Controller, když vlezeš třeba na <domena>/Article/id/1234, zavolá se ArticleController standardně actionIndex, když vlezeš na <domena>/User/id/1234, zavolá se UserController...

Kromě přimého nadefinování metod přístupných pro konkrétní akci přímo v ní mám ještě možnost přetížit metodu init, která se volá v konstruktoru abstraktní třídy Controller, kde mohu například nastavit povolené přístupové metody pro jednotlivé akce a mít je tak na jednom místě.

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
27. 9. 2015   #47
-
0
-

#46 ondrej39
Třída drží pouze data zaslaná metodou POST. Ta sanituje nebo zvaliduje buď přímým dotazem do DB nebo to v jednodušších případech zvaliduje sama, případně si na pomoc vezme validátor. Záleží na tom, co chceš zvalidovat. Pokud chceš zvalidovat, že nový user v databázi ještě neexistuje, tak to jinak než insertem neuděláš.

Inicializaci dělám tak, že vytvořím databázový objekt(y), a vložím do konstruktoru modelu. Přidám abonenty pro logování. Model předám routeru.

Router podle použité metody GET či POST (podle proměnné $_SERVER['REQUEST_METHOD']) vezme slovo "View" nebo "Controller" a přilepí ho za první slovo za doménou. V našem případě to bude "Profile". Tím mi vznikne název třídy "ProfileController". Pak už jen zavolá 

// volání controlleru
$controller = new $class($model, $_POST);
$url = $controller->execute();
header("Location: $url");
exit;

// volání view
$view = new $class($model, $_GET);
echo $view;

Volá se jedno nebo druhé - nikdy obojí.

Celý router je uzavřen do bloku try .. catch pro ošetření chyb a zlomyslností uživatelů.

Jak vidíš, všechno jsem napsal tak, aby to bylo co nejkratší a abych nezasahoval do pravomocí jiných objektů. Závislosti předávám v maximální možné míře pomocí Dependency Injection. Tím pádem nepotřebuji DIC. Nikde neuvidíš switch ani else, každou podmínku vyhodnocuji právě jednou.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
27. 9. 2015   #48
-
0
-

#47 Kit
Co takhle mít třídu UserDbValidator implementující interface HasErrors a Validation, která se ti o unikátnost záznamů postará? Rychlý příklad: 

use \App\Models\Abstraction\Model as AbstractModel;

interface HasErrors
{
    public function getErrors();
}

interface Validation
{
    public function validate(AbstractModel $model);
}

abstract class Validator implements
    HasErrors,
    Validation
{
    private $_errors = array();

    public function getErrors()
    {
        return $this->_errors;
    }

    abstract public function validate(AbstractModel $model);

    protected function _addErrorMessage($message)
    {
        $this->_errors[] = $message;
    }
}

use \App\Libs\DataMappers\Database\Abstraction\DbMapper as AbstractDbMapper;

abstract class DbValidator extends Validator
{
    protected $_dbMapper;

    public function __construct(AbstractDbMapper $dbMapper)
    {
        $this->_dbMapper = $dbMapper;
    }
}

final class UserDbValidator extends DbValidator
{
    private $_userModel;

    public function validate(AbstractModel $model)
    {
        if (!($model instanceof User)) {
            // throw an InvalidUserModelType exception
        }

        $this->_userModel = $model;

        $validationWasSuccessful = true;

        if ($this->_dbMapper->actionIsInsert() === true) {
            $validationWasSuccessful = $this->_checkIfEmailIsUnique();
        }

        return $validationWasSuccessful;
    }

    private function _checkIfEmailIsUnique()
    {
        $results = $this->_dbMapper->find(array(
            "email" => $this->_userModel->getEmail(),
        ));

        $emailIsUnique = true;

        if (count($results) !== 0) {
            $email = $this->_userModel->getEmail();
            $this->_addErrorMessage("A user with the e-mail {$email} already exists.");
            $emailIsUnique = false;
        }

        return $emailIsUnique;
    }
}

HasErrors by nemuselo být rozhraní, možná by se spíš hodil Trait, ale s Traity neumím moc pracovat, tak je nepoužívám. :D

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
27. 9. 2015   #49
-
0
-

#48 ondrej39

Co to jako má být? To myslíš vážně?

  1. Je to příšeně dlouhé
  2. Znamenalo by to 2 SQL dotazy místo jednoho
  3. Vidím v tom Race Condition

Mé řešení je na 3 řádky, je rychlejší a je bez chyby. V tom mi nemůžeš konkurovat.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
28. 9. 2015   #50
-
0
-

#49 Kit
Jaké řešení na tři řádky máš konkrétně na mysli?

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
28. 9. 2015   #51
-
0
-

#50 ondrej39
Prostě to uložím do databáze, viz výše. Pokud potřebuji i formální validaci např. e-mailu, přidám ji:

$email = filter_var($post['email'], FILTER_SANITIZE_EMAIL);
if (filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE) {
    throw new InputException("Chybně uvedený e-mail {$email}");
}

Ten tvůj kód se může hodit například pro průběžnou validaci přes AJAX - je nezávazná, ale může uživatele upozornit, kterou položku má ve formuláři špatně. Při ukládání záznamu však musí nastoupit tvrdá validační pravidla, kterou má databáze.

Pokud použiješ Redis nebo jinou NoSQL databázi, tak těch úkonů musíš samozřejmě provést více a to ve vlastní režii. Třída User s tím musí počítat a pokud zjistí, že dostala takovou databázi, měla by nějaký takový validátor použít.

Jenže tohle jsou důvody, proč se SQL databáze používají - umí udělat spoustu věcí na vstupu a výstupu. Umí to udělat rychle, efektivně a umí se ochránit před kolizemi. Kdo používá NoSQL, musí to dělat ručně v aplikaci.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
28. 9. 2015   #52
-
0
-

#51 Kit
[...]

Třída User s tím musí počítat a pokud zjistí, že dostala takovou databázi, měla by nějaký takový validátor použít.

[...]

V tomhle vidím osobně zásadní problém... Třída User podle mě nemá o databázi vědět absolutně nic, jakožto je to datový model obsahující business logic, a pokud neví nic o databázi, pak samozřejmě nemůže vědět nic ani o validaci pro příslušnou databázi, protože neví, se kterou databází bude zrovna pracovat -> není schopna si správný validátor získat).

A DataMapper funguje právě na tomto principu, kdy datový model o databázi neví.

With Data Mapper the in-memory objects needn't know even that there's a database present; they need no SQL interface code, and certainly no knowledge of the database schema.

Pokud tvoje datové objekty o databázi vědí, pak používáš návrhový vzor nějaký jiný. Příklad z knížky Patterns of Enterprise Application Architecture od M. Fowlera, jak používá Data Mapper on (vzhledem k tomu, že on ten DP navrhl, předpokládám, že ho používá tak, jak se používat má):

class PersonMapper ...

    private static final String updateStatementString = 
        "UPDATE people " +
        "  SET lastname = ?, firstname = ?, number_of_dependents = ? " +
        "  WHERE id = ?";

    public void update(Person subject) {
        PreparedStatement updateStatement = null;
        try {
            updateStatement = DB.prepare(updateStatementString);
            updateStatement.setString(1, subject.getLastName());
            updateStatement.setString(2, subject.GetFirstName());
            updateStatement.setInt(3, subject.getNumberOfDependents());
            updateStatement.setInt(4, subject.getID().intValue());
            updateStatement.execute();
        } catch (Exception $e) {
            throw new ApplicationException(e);
        } finally {
            DB.cleanUp(updateStatement);
        }
    }

Na základě toho, co tu píšeš, jak třída User má metody insert, update, delete, takový kód asi u sebe nemáš (anebo jsi svůj kód akorát špatně popsal a já to akorát blbě pochopil).

A dokonce i jeho datové modely mají tebou tolik nenáviděné gettery, M. Fowler nevypadá, že by s nimi měl nějaký problém.

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
28. 9. 2015   #53
-
0
-

#52 ondrej39
Data Mapper je jen jedním z mnoha použitelných vzorů. Mám to však podobně, pouze o dost jednodušší: 

<?php
class User implements Updatable {
    private $post;

    function __construct(array $data) {
        $this->data = $data;
    }

    function update(MyPDO $db) {
        try {
            $update = $db->prepare('UPDATE uzivatel
                SET user=?, pass=?, first_name=?,
                    last_name=?, role=?
                WHERE ID=?'
            );
            $data = array(
                 $this->data['user'],
                 crypt($this->data['pass']),
                 $this->data['jmeno'],
                 $this->data['prijmeni'],
                 $this->data['role'],
                 $this->data['ID'],
            );
            $update->execute($data);
            return $update->rowCount();
        } catch (\PDOException $e) {
            throw new ModelException(__METHOD__ . $e->getMessage());
        }
    }
}

Jak vidíš, třída User si vůbec nedrží databázi jako atribut, ale metoda ji dostane (resp. její wrapper) jako parametr. Obejdu se bez getterů, protože všechna data mám lokální a privátní. Ta třída User je totiž dostala v konstruktoru.

Formální validaci jsem pro jednoduchost vypustil. Objekt třídy MyPDO má stejné rozhraní jako třída PDO, ale může se v něm skrývat jakákoli databáze s příslušným wrapperem.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
28. 9. 2015   #54
-
0
-

#53 Kit
Kite, nevím, kdo ti co bohužel nakukal, ale to, co jsi sem právě dal, je Active Record. Pokud ti někdo někdy tvrdil něco jiného, tak dotyčné osobě omlať ten svůj kód o hlavu a poděkuj mu, za chybné vysvětlení.

Nejenom, že se tato tvá třída stará a ví o persistenci, stará se také o šifrování, v tvém případě se stará o validaci a stará se dokonce i o data. Jediná zodpovědnost, kterou DataMapper má mít je starat se o persistenci, nic jiného DataMapper nezajímá (ani šifrování, ani validace). Prostě mu předáš data a on ti je uloží. Pochopitelně můžeš během insertu vyhodit exception, když se insert nepovede, to je v pořádku, a můžeš to tam mít pro jistotu, že uživatel neprovede validaci tam, kde má...

Pokud by ti vadilo, že musíš objekt zvalidovat před tím, než ho DataMapperu pošleš, není nejmenší problém vyrobit si fasádu přebírající datamapper a validátor v konstruktoru, který bude mít metodu validateAndPersist(AbstractModel model) a uvnitř funkce ti z validátoru pustí validace a teprve poté model uloží.

Nahlásit jako SPAM
IP: 46.39.172.–
Inject all the dependencies!
Kit+15
Guru
28. 9. 2015   #55
-
0
-

#54 ondrej39
Mýlíš se, není to Active Record. Neumí pracovat s databází (vidíš nějaký DQL, který se jen náhodou podobá SQL). Neukládá si nic z toho, co získá z databáze (SELECT v příkladu není, ale výsledek fakt do objektu neukládám). Dokonce se ani nestará o data uživatele - v objektu $data jsou pouze data ze vstupního formuláře. Co na tom, že je to implementováno přes array? Mohu ho v konstruktoru přetypovat na object, pokud se ti to bude líbit víc. Ne dříve. Pořád to však bude jen běžný Messenger.

Co tedy zbývá? Šifrování a validace. Obojí řeší externí funkce, které bych mohl injektovat, ale neměl jsem k tomu důvod. Funkci crypt() používám v aplikaci na třech místech. Myslíš si, že bych ji měl vyčlenit do samostatné statické třídy jenom proto, abych ji mohl kdykoli zaměnit? Validaci bych mohl vystrnadit do fasády, ale ztratil bych tím kontext a zpravidla to ani není potřebné.

Vždy je dobré rozčlenit model do tolika vrstev, kolik jich potřebuji. Tichý model znám - ten je pětivrstvý. Sám autor píše, že klidně spojí více vrstev do jedné třídy, ale že ví, že tam těch pět vrstev je. Pokud mi začne vadit délka třídy (typicky >65 řádek) nebo mám opakující kód v různých třídách, neváhám a refaktoruji. Je zbytečné aplikaci od počátku vytvářet s kompletní kostrou.

SRP je dobré zaklínadlo. Použito do důsledků by kdejaký program o 50 řádcích rozdrobilo na 50 tříd. To však nechceme. Musíme si stanovit, co je to ta "odpovědnost" a zda se týká objektu, atributu či metody. Pokud mám ve třídě User objekt $data a metody insert(), update(), delete(), jsou to 4 uzavřené odpovědnosti zapouzdřené do jednoho objektu. Ten je společně s jinými odpovědnostmi zapouzdřen do jiného objektu.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
28. 9. 2015   #56
-
0
-

#55 Kit
A kde máš domain logic objektu User? Tu eviduješ konkrétně v jaké třídě?

Nahlásit jako SPAM
IP: 79.141.243.–
Inject all the dependencies!
Kit+15
Guru
28. 9. 2015   #57
-
0
-

#56 ondrej39
Doménovou logiku mám v databázi. Cítí se tam velmi dobře :-)

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
28. 9. 2015   #58
-
0
-

#57 Kit
Ale to se pak přece takové objekty hrozně blbě testujou, ne?

Nahlásit jako SPAM
IP: 79.141.243.–
Inject all the dependencies!
Kit+15
Guru
28. 9. 2015   #59
-
0
-

#58 ondrej39
Proč by měly? Databázi testuji úplně stejně jako PHP. Je to jen jiný jazyk.

Oba dobře víme, že PHP neumí sčítat finanční hodnoty. Proč bych ho tím měl zatěžovat přes nějakou extra knihovnu, když databáze to umí naprosto precizně?

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
28. 9. 2015   #60
-
0
-

#59 Kit
Snad proto, že třeba již mnou zmíněný Redis neumí vůbec nic (MongoDB také ne)... Pokud bys jel podle doménového vývoje namísto datového, pak máš veškerou logiku v doméně a jakmile takové modely otestuješ a testy projdou, můžeš je použít úplně pro všechno, pro jakékoliv úložiště, dokonce model vzít a použít ho v jiném projektu, protože tu logiku má  v sobě (a víš, že prostě bude fungovat dobře, pokud projde mock testy).

To ti nepřijde jako dostatečná výhoda?

Nahlásit jako SPAM
IP: 79.141.243.–
Inject all the dependencies!
Kit+15
Guru
28. 9. 2015   #61
-
0
-

#60 ondrej39
Pokud bych něco takového potřeboval, doimplementoval bych to do třídy MyPDO, resp. přidal bych to jako další driver. Zatím jsem tu potřebu neměl, neměl jsem tedy ani důvod tuto implementaci psát.

Třídu MyPDO hodlám časem přejmenovat, resp. místo názvu třídy dám do hlavičky metody název rozhraní. Přece jen ten název příliš evokuje, že obsluhuje MySQL a přitom ji používám i s databázemi PostgreSQL a SQLite. Když přidám Redis nebo MongoDB, budou to jen další ovladače.

DQL ponechám beze změny, protože se to parsuje docela dobře a není problém ho transformovat do Lua pro Redis. Jen jsem zatím neměl tu potřebu.

Nebudu degradovat SQL databázi na prosté datové úložiště. Takové zacházení si nezaslouží. Raději udělám mezivrstvu pro MongoDB a Redis.

Hlavně se mi nesnaž namluvit, že Redis neumí vůbec nic. Je to velmi šikovná databáze, jejíž hlavní předností jsou kolekce. A to je hodně užitečná přednost.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
28. 9. 2015   #62
-
0
-

#61 Kit
Neumí nic z té potřebné validace, na které celý tvůj systém stojí... Když máš doménovou logiku přímo v modelech a rozhodneš se používat něco jiného než je relační databáze (ať je to MySQL, PostgreSQL či SQLite), stačí ti rozšířit aktuální funkcionalitu v příslušné vrstvě a opět ti to pojede.

Protože ale aktuálně přenecháváš validaci na databázi, což je úplně oddělený ekosystém od tvého projektu, tak mi přijde, že ti vlastně chybí v projektu ten validační proces a budeš ho tam muset v případě uvedení úložiště, který interní validaci nemá, tak jako tak nacpat.

Nahlásit jako SPAM
IP: 79.141.243.–
Inject all the dependencies!
Kit+15
Guru
28. 9. 2015   #63
-
0
-

#62 ondrej39
S tím musí počítat každý vývojář, který pracuje s NoSQL, že bude muset něco doprogramovat navíc. Zatím jsem to nepotřeboval, vystačím si s databázemi SQL. Až to budu potřebovat, dopíši patřičný driver i s validací. Dřív to nemá smysl.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
28. 9. 2015   #64
-
0
-

#63 Kit
Vzhledem k faktu, že ale validaci neprovádíš v PHP, ale v databázi, tak bys ani u noSQL databáze neměl provádět validaci v PHP (a pokud bys to tak dělal, pak máš nekonzistentní kód, kdy se ti v jednom případě stará o validaci PHP a jindy Databáze,... pak by někdo nahlédl do validátoru pro Redis, viděl tam stovky pravidel, nahlédl by do driveru pro MySQL, neviděl by tam vůbec nic a přitom obě služby validují stejné věci).

A třeba do Redisu musíš proto provádět validaci v LUA, což by pro mě byl kámen úrazu, jakožto si s tím jazykem moc nerozumím.

Nahlásit jako SPAM
IP: 79.141.243.–
Inject all the dependencies!
Kit+15
Guru
28. 9. 2015   #65
-
0
-

#64 ondrej39
Jenže validační pravidla pro Redis nebudou v PHP, ale opět v databázi. Tam ostatně patří bez ohledu na typ databáze. PHP si stáhne z Redisu vždy ta pravidla, která se týkají právě obsluhované domény, zvaliduje data a uloží je do DB. V daném případě si z databáze vytáhne pravidlo 'user_update'. Má napsáno v DQL, že má vytáhnout právě toto pravidlo. Z něj zjistí, že Redis chce položky user, password, prijmeni, jmeno a roli. U každé položky je uveden i způsob validace. PHP to provede a pokud to skončilo úspěšně, provede i zápis do databáze.

Jak vidíš, v PHP nepotřebuji znát žádné validační pravidlo ani strukturu ukládání dat a přesto mohu celou validaci provést kompletně v driveru (PHP) a sestavit správný skript (Lua) pro provedení zápisu do DB.

Jen se mi to zatím nechtělo psát, protože to zatím nepotřebuji. Jestli mě však budeš ještě dostatečně dlouho provokovat, tak ten driver skutečně udělám.

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
ondrej39+1
Věrný člen
30. 9. 2015   #66
-
0
-

#65 Kit
Takže abych mohl zvalidovat data, musím se naučit LUA :D. To je opravdu bomba a strašně se mi chce, jako lenivýmu člověku, dělat, když se data dají úplně stejně dobře zvalidovat v PHP.

Nahlásit jako SPAM
IP: 79.141.243.–
Inject all the dependencies!
Kit+15
Guru
30. 9. 2015   #67
-
0
-

#66 ondrej39
Nemusíš se učit Lua. Bude ti stačit můj driver, který tu validaci udělá sám. Ten driver bude zároveň v Redisu udržovat databázové schéma i další metadata. Jen ho musím nejprve napsat :)

Nahlásit jako SPAM
IP: 2a00:1028:83a0:37a6:207:e...–
Komentáře označují místa, kde programátor udělal chybu nebo něco nedodělal.
Zjistit počet nových příspěvků

Přidej příspěvek

Toto téma je starší jak čtvrt roku – přidej svůj příspěvek jen tehdy, máš-li k tématu opravdu co říct!

Ano, opravdu chci reagovat → zobrazí formulář pro přidání příspěvku

×Vložení zdrojáku

×Vložení obrázku

Vložit URL obrázku Vybrat obrázek na disku
Vlož URL adresu obrázku:
Klikni a vyber obrázek z počítače:

×Vložení videa

Aktuálně jsou podporována videa ze serverů YouTube, Vimeo a Dailymotion.
×
 
Podporujeme Gravatara.
Zadej URL adresu Avatara (40 x 40 px) nebo emailovou adresu pro použití Gravatara.
Email nikam neukládáme, po získání Gravatara je zahozen.
-
Pravidla pro psaní příspěvků, používej diakritiku. ENTER pro nový odstavec, SHIFT + ENTER pro nový řádek.
Sledovat nové příspěvky (pouze pro přihlášené)
Sleduj vlákno a v případě přidání nového příspěvku o tom budeš vědět mezi prvními.
Reaguješ na příspěvek:

Uživatelé prohlížející si toto vlákno

Uživatelé on-line: 0 registrovaných, 5 hostů

 

Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032024 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý