PHP MVC - što staviti u controller, što u model?

Ma znam, samo primjer navodim, vjerojatno se neće ništa mijenjati, ali eto moram spomenuti :slight_smile:

Meni se isto sviđa takav dizajn, 1 model = 1 tablica osim u slučaju kada se rade nekakvi reporti pa je moguće unutar jednog modela implementacija više tablica. Ili recimo čupanje posta i kategorije:

SELECT post.*, category.name AS category_name, category.slug AS category_slug
FROM post
LEFT JOIN category ON category.ID=post.ID_CATEGORY

A što se tiče settera i gettera ne čini mi se to baš kao dobra logika, ako ima 20-30 ili više polja u tablici neću valda za svako polje koristiti setter i getter. No, ako ima toliko puno polja možda onda baza i tablice nisu dobro struktuirane… Iako getter je nekad i dobro koristiti npr kod čupanja cijene iz tablice u getteru odma mogu zadati format cijene i valutu. Recimo:

public function getPrice()
{
      return number_format($this->price, 2, ".", ",") . " " . $this->getCurrency();
}

Tako da je za neke stvari i dobro koristiti, ali baš za svako polje u tablici mi je malo previše. Ne znam, morat ću još malo o tom dijelu razmisliti. Pitanje isto, ako koristim gettere u modelu, kad iz controllera šaljem varijable na view onda bi zapravo i unutar viewa mogao/trebao koristiti gettere ?

<h1><?php echo $post->getTitle(); ?></h1>
<p><?php echo $post->getDescription(); ?></p>

Ok, model je nekakav objekt kojeg koristis u svojoj aplikaciji, i kojeg ces ili dohvatit iz baze, ili pospremit u bazu zajedno sa svom njegovim parametrima. To je ideja dizajna iza model=tablica. Jasno da ti sve varijable modela mozes proglasit public i mijenjat ih bez getera i setera, al to je los pristup OOP-u. Razloga zasto je dobro odmah pisat accessore, a ne pristupat direktno varijablama je pun internet. Ako je tebi razlog NEkoristenja accessora to sto ih je tlaka natipkat, prakticki svaki IDE ima funkcionalnost generate getters/setters, koji ce ti sve to ispisat prema definiranim varijablama u fileu. Ne treba radi lijenosti pisat inferioran kod. Da, tablica moze imat 20 kolona, i to ce generirat 40 metoda, i aplikacija moze imat 20 tablica, i mozda ces imat 800 metoda gettera i settera, ali to je non-issue. Veci je issue kad nakon godine dana moras obavit nekakavu manju radnju nad nekom vrijednosti nakon dohvacanja iz baze, i skontas da si toj varijabli direktno pristupao na 800 mjesta kroz aplikaciju.

Sto se viewa tice, ovisi kako si implementirao templating. Ali da, ako ti je varijabla protected, trebas koristit gettere.

Međutim, kad vec pises svoj framework, ubacit cu ti jos jednu bubu u uho… Izbaci view dio iz svog frameworka, sibaj sve podatke vani preko nekakvih endpointa (REST API-a) u json formatu, i za frontend uzmi nekakav moderan frontend framework, ili (kad si vec u pisanju svojih frameworka), napisi svoj :slight_smile:

2014 godina je, vec i na zalazu. REST je popularan, mobilni uređaji su popularni, cloud arhitektura je svugdi. Separacija servera i klijenta je logican korak.

Čitam baš po netu, pogotovo Stackoverflow i svašta se piše na tu temu, netko je za, netko protiv. Većinom govore ako se koristi samo $post->setTitle(), $post->getTitle() bez ikakvih dodatnih radnji, provjera i sl, da je to isto kao i kada se pristupa preko $post->$name, sama klasa onda nema smisla i da to antipattern i ne znam ni ja šta već.

No ovo što ti kažeš definitivno ima smisla (za godinu dana kada treba napraviti izmjenu), a i naveo sam primjer iznad s getPrice() gdje izvlači odgovarajući number_format i recimo valutu, što inače ne bi mogao preko public $price. Ili primjer gdje trebam povući sliku autora - $post->getAuthorImage(); a tamo stoji return “upload/images/author/” . $this->getAuthorId() . “.jpg”; recimo tako da se ipak može naći primjena osim čistih gettera i settera koji samo postavljaju i dohvaćaju varijable… Mislim da bi ipak mogao ići na tu varijantu…

E sad još ostaje povezivanje Modela i Repositorija, u Repositoriju dohvaćam sve postove (znači čisti SQL upit ili upit prema ORM-u), a u modelu bi trebao obraditi te podatke i vratiti u controller u neki array. I Repository i Model pozivam iz Controllera ili kako ?

Haha pa jao kud još i to :slight_smile: ajde hvala, pogledam i taj dio, svakako mi je i to zanimljivo jer onda podatke mogu slati i na ios, i na android i ne znam ni ja gdje već…

Da, ako getteri i setteri ne rade nista posebno, rezultat je isti kao i direktno pristupanje varijabli. Međutim, to ne znaci da je pisanje gettera i settera antipattern, ili lose. Nisi nista izgubio pisanjem get/set metoda. Cak ni vrijeme; ako koristis ista pametnije od notepada za tipkanje koda, vjerojatno ima opciju auto generiranja gettera i settera.
Ako zbog niceg drugog, isplati se pisat radi testiranja ili buduce prosirivosti. A i razvoj je brzi ako ne moras provjeravat je li ti neka varijabla dostupna direktno ili moras pisat getter (jer u nekim situacijama ce ti getter sigurno bit neophodan). Jednostavno pises getter i tocka :slight_smile:

Kako ces implementirat repozitorij s modelom je tvoja stvar; ovisi koliko kompleksan sustav zelis napravit. U nekom najosnovnijem obliku, repo moze jednostavno extendat pripadajuci model, i onda zapravo uvijek iz kontrolera zoves repozitorij. Doctrine recimo koristi anotacije da poveze repozitorij sa modelom.

Da, sad je jasnije, čak mi kontam općenito više trebaju getteri nego setteri, no to ću još sve vidjeti kad krenem s novom logikom modela…

Dobro, to čak ima smisla - proširiti repo s modelom, onda svi getteri pripadaju i repou (ako koristim protected) koje mogu koristiti unutar klase i van klase tipa u viewu… Ajde hvala, ako još gdje zapnem javim se! :smiley:

Pozdrav, evo mene opet! :slight_smile:

Čitao sam malo i skontao da je Data Mapper zapravo Repository ? Tj. neki kažu da je isto, neki koriste Repository umjesto Data Mappera, neki Data Mapper umjesto repositorija. E sad šta je tu točno šta ne ? Ja sam kontao možda koristiti Data Mapper za CUD (create/update/delete) a repository za read, ima li to smisla ? Ili je stvar samo u nazivlju :smiley:

Kontao sam zapravo odvojiti insert, update i delete od samog čitanja gdje bi išli pravi MySQL upiti, a ako jednog dana promjenim bazu ili čitam podatke iz XML-a, JSON-a samo zamjenim Repository i Data Mapper i to je to, struktura Modela ostaje ista, kao što si i ranije mislim bio rekao.

E sad tu stoji jedan problem, prilikom korištenja više jezinčne stranice, polja u bazi mi stoje ovako:
title_hr, title_en, title_de, description_hr, description_en, description_de. Moram li raditi tako i varijable ? Znači protected title_hr; protected title_en… To bi mi oduzelo previše vremena ako bi dodao novi ili izbrisao stari jezik, sada samo promijenim bazu (dodam/izbrišem polja), i stavim novi lang fajl i to je to).

Čak sami getTitle(), setTitle() nije problem:

public function getTitle($lang)
{
     return $this->title_{$lang};
}

Ali mi se čini da ću morati stavljati varijablu po varijablu u deklaracija :S osim ako imaš koji drugi način ?

Dalje, prilikom čitanja iz Repositorija (ili Mappera), odnosno čupanja svih postova - loadAll(), kako vraćam podatke nazad ? Jasno mi je kod čitanja jednog posta, vratim $this->title; $this->description… Ali ovdje imam zapravo array ili kako ? Mislio sam možda vratiti array objekata, ali tada onda za svaki red kreiram objekt, tipa zadnjih 20 postova, nije li to previše, kako se ponaša brzina stranice i sama količina memorije kad radi s puno objekata ?

Pa bi onda čupanje išlo ovako:

foreach ($posts as $post) {
     echo $post->getTitle();
}

Malo si sad svasta nabroja… :smiley:

Data mapper, u mom svitu, predstavlja pattern mapiranja tablica u programske objekte. To je tvoj model. Repository je klasa koja sadrzi metode koje NE odgovaraju tablicama u bazi, nego sluze obavljanju nekog posla koji ima smisla sa business strane (ie, getMostRecentPosts(); ). To je arhitektura kakvu ja zagovaram.
S druge strane, “data mapper” i “repository” su tako genericke rijeci da mogu predstavljati vise manje bilo sta, ovisno o kontekstu :smiley:

Sto se tice jezika, ili krusaka, nebitno, i dalje zagovaram da je svaka kolona u tablici svoja varijabla sa svojim getterima i setterima. Ne razumin to “puno vremena oduzima”. S obzirom na posao kojeg zelis obavit (dodavanje supporta za potpuno novi jezik, odnosno dodavanje hrpetine kolona u tablicu), programski dio uopce ne oduzima puno vrimena. Ako ti je pisanje takvog koda problem, nabavi bolji IDE. Ne mozes programirat svoj framework i bit lijen tipkat kod :smiley: Mislim mozes, al koja je poanta onda? Mozes uzet postojeci framework i prilagodit ga.

Array objekata (ili kolekcija) je skroz normalan set podataka za vratit nekim SQL-om. I 20 objekata postova je zaista bezznacajno mala kolicina podataka. :slight_smile: I to je skoro 20 puta bolje nego da 20 puta ides nazad u bazu po jedan objekt. (hint, najskuplji dio procesa je dohvat podataka iz baze). Ukoliko je content posta nesto sto ocekujes da je “veliko”, uvik mozes vracat objekt bez contenta. Recimo za izlistavanje clanaka u arhivi/naslovnici.

Dobro, malo je jasnije sad, ali gdje onda obavljam operacije create, update, delete ? U Modelu ? Onda u modelu pišem SQL što bi htio izbjeći… Tipa ako umjesto SQL-a krenem koristiti MongoDB ili bi htio iz vanjskog XML-a insertati podatke u svoju bazu / ili ih samo prikazati, meni treba drugi Data Mapper koji će počupati podatke (koristeći strukturu Modela) i spremiti ih u moju bazu. PDODataMapper, XMLDataMapper, SQLDataMapper i sl. A model mi je, što si napisao, preslika trenutnog stanja moje tablice…

Kako sam ja zamislio (kreiranje novog korisnika)

$user = new User();
$user->setName("Ivan");
$user->setLastname("Ivić");

$userMapper = new UserMapper();
$userMapper->save($user);

Izmjena korisnika

$userMapper = new UserMapper();
$user = $userMapper->findById(1);
$user->setLastname('Ivanišević');
$userMapper->save($user);

Taj UserMapper može biti PDO, SQL, XML, JSON ili neznam ni ja šta, a ja tu pozivam samo save, delete, findById, a sami model nema pojma šta se nalazi iza toga, niti ga je briga…

Aha, kuzim sto te muci. Na ok si tragu, ali nema smisla pisat poseban mapper za svaki model. Zapravo kako bi to bilo dobro rijesit, je da tvoj model extenda mapper.
Doctrine recimo umisto modela ima entity, i onda imas ovako nesto:
class User extends Entity { …
$user = new User();
$em = …(dohvati Doctrine)… ->getEntityManager();
$em->persist($user);
$em->flush();

Kod eloquenta (ORM kojeg koristi Laravel i Ruby ekipa)…
class User extends Eloquent { …
$user = new User();
$user->save();
… i ubij me ne mogu se napamet sjetit jel to odmah persistano, ili trazi opet nekakav flush() ili nesto slicno.

Sto ne koristis neki od postojecih ORM-ova? :slight_smile:

E to to baš spominju Entity umjesto Modela… Zar ne bi Mapper trebao extendati Entity ? Kad čupam $userMapper->findById(1); spremam polja iz baze u protected varijable koje koristim u Modelu, i ista stvar i obrnuto vrijedi, postavim varijable u $user koji spremim u Mapper koji počupa sve postavljene podatke i spremi ih u bazu… Tako da bi i trebao pisati poseban Mapper jer se polja razlikuju kao i logika Modela tj. Entityija. Pa tu možda napraviti convert array u $obj->var ili obrnuto kako bi lakše mogao koristiti čupanje u bazu i iz baze…

http://www.devshed.com/c/a/php/implementing-the-data-mapper-design-pattern-in-php-5/ - ovo mi se čini ok jedino što koriste magične __set i __get methode što bi ja fiksirao po poljima…

http://blog.tekerson.com/2008/12/17/data-mapper-pattern-in-php/ - ovo također ali je članak star ko biblija…

http://akrabat.com/php/objects-in-the-model-layer-part-2/ - ovo mi je još najbolje ali ne bih koristio populate i toArray u samom entityiju…

Ma nema potrebe za vanjskim ORM-om :slight_smile: gledao sam, ima zanimljivih, ima dobrih ali mislim da nema potrebe, sve više manje već imam osim ogromnih queryija koji su problem i za gotove ORM-ove… I da, svakako bi morao nabaviti neki bolje IDE :smiley: imaš koji prijedlog, gledao sam PHPstorm čini mi se super ?

Kao sto si i sam nasao, postoji puno rjesenja istog problema. Ja osobno nisam nikad pisao vlastiti ODM ili ORM (jer nema smisla :smiley: ), pa da ti dam “najbolje” rjesenje iz glave.
Kad krenes malo zesce koristit to sta si napisa, naletit ces na jos N edge case-ova di ces sam sebe proklinjat sta ne koristis gotovo rjesenje. Ali cijenim trud :slight_smile:

IDE… Ja koristim NetBeans na poslu; dosta je jednostavan za krenit, a mocan u pozadini. O PHPStormu citam samo pozitivne stvari, ali meni nije drag. Siguran sam da je kvalitetan IDE, ali ne pisem samo PHP, i nisam naletio na niti jednu korisnu stvar zbog kojeg bi imao volje uciti raditi u novom IDE-u (i platit nemale novce za njega).
Koristim jos i Atom (OSX), SublimeText (Linux/Win) i Notepad++ (Win), koji su tekst editori, i Sublime je fantastican i nenormalno brz. Atom je moderna kopija Sublimea kojeg je napravila ekipa GitHuba, i dobar je ali sporiji od Sublimea.

Svi nabrojani, osim PHPStorma su besplatni (Sublime ima neogranicen trial period). Probao sam jos par koji se placaju, ali nema smisla.

Hehe vjerojatno, ali eto, ne mogu si pomoći, pa kad dođe vrijeme za proklinjanje onda ću se proklinjati :smiley: ali što se tiče nekih kompliciranijih queryija i gotovi ORM-ovi imaju problema pa se cijela kobasica od queryija mora ručno pisati…

Da, super mi se čini Sublime iako ga nikada nisam baš koristio koristio, većinom Notepad++ za brzo pisanja koda, ispravke i slično. Tražim baš pravi IDE kao što je NetBeans ili PHPstorm za projekte…

Hvala!

Evo opet ja :slight_smile:

Daklem, imam problem s čupanjem child tablice u Modelu, tj. Entityiju u mom slučaju… Imam prikaz jednog posta gdje bi trebao izvuči kategoriju i podkategoriju posta i malo me muči logika čupanja. Trenutno imam ovako nešto:

$postMapper = new PostMapper();
$post = $postMapper->load($id);
echo $post->getTitle();
echo $post->getText();
echo $post->getCategory()->getName(); // naziv kategorije
echo $post->getCategory()->getCategory->getName(); // naziv podkategorije


class Post extends Entity
{
    protected $title;
    protected $text;

    protected $category;

    public function getTitle() { return $this->title; }
    public function getText() { return $this->text; }

    public function getCategory()
    {
        if(!$this->category) $this->category = new Category();
        return $this->category;
    }
}

Sad je problem kako da pošaljem podatke koju kategoriju mora počupati pošto preko PostMapper i CategoryMapper se nalazi SQL s load($id) i loadAll() metodama za čupanje iz baze te vraćanja arraya odnosno punjenja Entityija -> $post->setTitle() i sl… Možda preko toga, pa bi ovako nekako išlo:

class PostMapper extends Mapper
{
    protected $table_id;
    protected $table_name;

    public function load($id)
    {
        $data = $this->db->load($this->table_name)
                          ->select("post.*, category.name AS category_name")
                          ->leftJoin("category ON category.id = post.id_category)
                          ->where("id=$id");
        if ($data) {
	        $post = new Post($data);
        }
    }
}

Pa bi onda u Post Entity prilikom kreiranja novog entitija - new Post($data); pregledavao $data array i upisivao podatke -> setTitle(), setText(), setCategory() i slično… ima li to smisla ?

EDIT: proučavao sam bio malo jučer, problem mi je postaviti relacije između one-to-one, one-to-many, many-to-one, many-to-many između Entityija, tj. različitih Modela. Jedan post možete biti u jednoj kategoriji, a jedna kategorija može sadržavati više postova… I sad kako to povezivanje ide mi malo šteka, morat ću još malo to proučiti…