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

Evo pitanje za malo iskusnije programere, radim neki svoj hibrid MVC-a i zanima me što točno ide u controller, a što u model, meni je trenutno situacija ovakva:

userController.php

userController extends mainController{
    function index(){
        $users = new user();
        $users->limit = 10;
        $users->where = "is_active='1'";
        $users_list = $users->loadAll();
    }

   function single($id){
      $user = new user();
      $user->where = "ID=$id";
      $user_single = $user->loadSingle();
   }
}

userModel.php

userModel extends mainModel{
    public $tableName = "users";
    public $limit = 20;
    public $where = '1=1';

    function loadAll(){
        global $db;

        $load = $db->fetch_all('SELECT * FROM $tableName WHERE $where LIMIT $limit');

        return $load;
    }

   function loadSingle(){
        global $db;

        $load = $db->fetch_all('SELECT * FROM $tableName WHERE $where LIMIT 1');

        return $load;
    }
}

Nakon toga pozivam template tj view -> users.temp.php ili usersSingle.temp.php… pa eto, zanima me imali ovo smisla ili bi morao mijenjati logiku ? Također, gdje da ubacim stvari koje se vrte na svakoj stranici npr. gornja navigacija ili informacije iz footera, u mainController ili ?

Na dobrom si putu, Controller je ok, no metode koje koristiš u Modelu trebalo bi premjestiti u neku ORM klasu kako bi izbjegao ponavljanje pisanja koda za svaki model, a u samoj model klasi ostaviti zadana svojstva (table name, relacije, validacija i sl). Ukoliko ne misliš pisati vlastiti ORM možeš koristiti Doctrine , Proper i sl

Stvari koje se vrte na svakoj stranici ubaciš u tvoj mainController ako zahtijevaju interakciju sa Modelima i sl, ili u tvoju bootstrap klasu ako je riječ o globalnim postavkama.

Hvala i mislio sam takve stvari ubaciti u mainController…

Također, malo me muči ovaj global $db, jel ne bi model trebao učitati bazu kojom se barata tipa $user = new user($db); ili nekako drukčije prenijeti $db konekciju u model ? Pošto mi $db ne bi trebala biti vidljiva niti u controller-u niti u view-u.

Još nešto, što je s jezicima, user privelgijama i sl ? To bi trebalo u routeru ? Tipa stranicu /users/login/ mogu vidjeti samo odjavljeni korisnici, dok /users/profile/ mogu vidjeti samo registrirani i prijavljeni korisnici. O tome brine router ili ? I onda se zove kontroler i određena metoda unutar njega - login(); single();

Ili prvo otvorim /users/login/ u controlleru pa provjerim ako je korisnik već prijavljen onda ga redirektam na /users/single/ ili negdje dalje… Možda bolje tako jer nekako sumnjam da router može imati podatke o korisniku… Kakvi su prijedlozi ?

Zavisi kako si krenuo sa razvojem svog MVC, na koji način vršiš include datoteka.

Za korisničke ovlasti pristupa određenim controller metodama napravi neku ACL klasu. Ovlasti provjeravaš prije nego što se pozovu ostale metode u tvom controlleru, dakle treba ti tzv beforeFilter() metod koji pozoveš prije npr tvog single() metoda. U isti metod možeš staviti i provjeru jezika na osnovu url parametra i sl.

Znači negdje između routera i pozivanja same metode koliko sam shvatio. Možda u __construct() ? Iako teško da tamo može ići pošto recimo userController.php?method=index mogu svi vidjeti (listu usera)…

Uglavnom hvala, poigrat ću se malo :wink:

Uspio nekako rješiti bazu i usera, ostaje mi jezik za kojeg imam ideju, ali i još jedno bitnije pitanje, kod routera koji mi treba povlačiti linkove iz baze pa je to malo problematično…

znači linkovi moraju biti ovakvi:

textController.php?method=categ (jedna kategorija, više tekstova i paginator)

categ/
categ/2/
categ/subcateg/
categ/subcateg/2/

textController.php?method=single (jedan tekst)

categ/text-ID/
categ/subcateg/text-ID/

E sad je tu ogroman problem provjere tih linkova i koji controller se mora pozvati… Jedino da napravim dodatnu funkciju (metodu) unutar routera koji se spaja na bazu i provjerava strukturu linka s onom u bazi te vraća informaciju o controleru i metodi koji se moraju učitati…

Spremaš punu putanju u bazu podataka ili za svaki unos kategorije ili članka praviš ‘slug’ na osnovu naslova i poslije preko php i sql-a provjeravaš ispravnost putanje?

Jedino tako nekako, samo je problem unosa sluga jer se koristi gotov administracijski sustav te nije moguće odraditi PHP dio, jedino možda direkt preko SQL-a ako je moguće… Korisnik kroz administraciju unosi kategorije razine 1, 2, 3 i veže ih za parent pa da prilikom inserta/updatea u tablicu categs, zavrtim trigger tj. neki query koji napravi slug na osnovu unešenog…

pogledaj ovo http://theoryapp.com/generate-slug-url-in-mysql/

E to je to, hvala! Naišao sam na još jednu gotovu MySQL funkciju - http://stackoverflow.com/questions/5409831/mysql-stored-function-to-create-a-slug

Pitanjce još jedno :slight_smile:

Model bi trebao izgledati ovako? :

categModel extends mainModel{
    public $tableName = "categ";

    public function loadAll(){
        $load = $db->loadAll($this->tableName);

        return $load;
    }

   public function loadAllSubcategs(){
        $load = $db->loadAll($this->tableName, "veza_kategorija='$id'");

        return $load;
    }

    public function load($id){
        $load = $db->load($id);

        return $load;
    }
}

Znači da imam gotove metode u modelu koje izvlače točno određene podatke - sve kategorije, sve podkategorije, jednu kategoriju, dodavanje, izmjena, brisanje…

A onda unutar controller samo pozovem:

$categ = new categ();
// i onda šaljem na template
$this->template->categ = $categ->load($id);

i to je to, i onda unutar template samo koristim varijablu $categ. Jedna tablica u bazi bi bila vezana na jedan model, sad ne znam koliko je to dobro/praktično… A svakako ću imati druge modele (klase) koje se bave bazom (ORM), autorizacijom korisnika, cookijima i sessionsima, multijezičnosti i sl,

Tablica = model je u sustini dobar dizajn.
Inace nisam fan natrpavanja modela s razlicitim metodama dohvacanja podataka iz baze… model treba sadrzavati ciste gettere i settere osnovnih stvari (kolona, vezanih tablica), a sve ostalo ide u zasebnu repozitorij klasu. Razlog je to sto tako model uvijek opisuje tocno ono sto je u bazi, a repozitorij klasa se mijenja po potrebama business logike.

Malo offtopic, ali moram rec… zanimljivo je pisat svoj framework, dosta se nauci. Ali se lako nauci koristiti “krive” patterne. Preporucam da ovladas necim tipa Symfony2 (+Doctrine) prije nego se upustis u ovakve stvari. Razlog je jednostavan - ne samo da je znanje kvalitetnog i popularnog frameworka jako korisno, nego se nauci koristiti nekakve patterne koje su smislili ljudi s vise iskustva (da ne kazem pameti).
I btw, PSR standardi… bitno :slight_smile:

Hmm zanimljivo mi se čini ovo s repozitorij klasama. Znači model categ.class.php bi u sebi sadržavao i klasu categModel (koju već imam) i klasu categRepository u kojoj bi imao metode za dohaćanje podataka (loadPrev(), loadNext(), loadSubcategs()…) ? Kako bi onda pozivao categRepository ? Možda unutar baseModel napisati metodu getRepository() koja bi dohvaćala određenu repozitori klasu iz trenutnog model fajla ? Tipa:

$categ = new categModel();
$categ_prev = $categ->getRepository()->loadPrev();

Jel ima to šta smisla ili bi ipak morao drugačije implementirati repozitori tipa u poseban folder staviti repozitori klase ?

Da, stvarno je zanimljivo, više sam naučio u zadnjih mjesec-dva nego u godinu dana. Slažem se, ima puno pametnijih ljudi s puno više iskustva koji su stvarno svašta napraviti i može se od njih naučiti, ali sam baš htio nešto svoje napraviti i koristiti nekakvu najbolju praksu pisanja mvc-a i koda općenito. Pokušavam se čim više držati PSR-a :wink:

http://www.php-fig.org/psr/psr-1/ kaze:

  1. Namespace and Class Names
    Namespaces and classes MUST follow PSR-0.

This means each class is in a file by itself, and is in a namespace of at least one level: a top-level vendor name.

Class names MUST be declared in StudlyCaps.

Code written for PHP 5.3 and after MUST use formal namespaces.

For example:

<?php // PHP 5.3 and later: namespace Vendor\Model;

class Foo
{
}

Dakle, svaka klasa = svoj file, i ime filea = ime klase. (I veliko pocetno slovo kod imena klasa :slight_smile: ).

Repozitorij klasa moze bit u istom folderu kao i model, ili u zaseban, to je na tebi, kako ti je lakse organizirat kod. Ja volim napraviti zaseban folder, ali nije imperativ uopce. Poanta je da ces imati fileove Category.php (ili po tvom nazivlju CategoryModel.php - ne znam cemu bi kratio to na categModel), i CategoryRepository.php, i da odmah znas sta je u kojem fileu/klasi.
Sto se repozitorija tice, dobar primjer je Doctrine. Malo sam u prisi, a dalo bi se tu raspisat, pa mozda bolje da sam bacis oko na recimo ovaj jednostavan primjer:

Dakle, po meni bi dobar dizajn bio gdje ti zoves repozitorij, i onda iz tog objekta imas dostupne sve moguce metode koje zelis raditi na svom entitetu/modelu.

Hvala na opsežnom odgovoru :wink:

Ispravio sam autoloader funkciju da gledam Vendor\Namespace\ClassName, znači Application\Model\CategoryModel (rootpath/Application/Model/CategoryModel.php) i Application\Repository\CategoryRepository (rootpath/Application/Repository/CategoryRepository.php ? Tako ispravno ?

Koliko čitam PSR pravila, namespace mora biti definiran kao StudlyCaps, također isto vrijedi za klase što znači ako koristim namespaceove i include klasa, struktura foldera mora biti isto StudlyCaps, no nije li to malo kontra web logike - lowercase i korištenje crtice ili underscorea ? tipa application/model/category_repository.php ili tako nekako…

Kako bi onda trebao pozivati same fajlove tj modele ?

new Application\Model\CategoryModel; // (ako se ovako uopće može koristiti ?)

ili možda

use Application\Model\CategoryModel as CategoryModel;
new CategoryModel();

Na vrhu fajla stavim sve use namespaceove i onda unutar samo new ClassName(); koji sam zadao ?

Također, ako već koristim logiku namespacea, modela i repositorya, šta da radim s klasama kao što su ORM, autorizacijom korisnika ili session/cookie klasa i sl, to nije ni model ni repository jer ne mora biti vezana za bazu već obavlja neke funkcije vezane za rad same stranice. U poseban folder tipa Application\Lib\ pa ih također includati na isti način kao modele, repositorije ?

use Application\Lib\ORM as database;
user Application\Lib\Session as session;
new database();
new session();

Ima li išta od ovog smisla ? :smiley: Hvala!

Da, App\Model\Category je ok. I da, struktura foldera prati StudlyCaps, i to nije kontra web logike; lowercase i underscoreovi su praksa devedesetih. Nema nikakav razlog zasto po filesystemu ne bi pisao citka imena fajlova. Pogledaj bilo koji moderni framework; recimo Laravel ili Symfony - tocno su tako poslozeni, i njihovi autoloaderi ocekuju PSR, sta olaksava ukljucivanje third party paketa/bundleova. I ako ti slozis svoj autoloader da prati PSR, moc ces u svoj framework includeat third party pakete kojeg ce autoloader znat bootstrapat. Cak savjetujem da stvarno i isprobas to.

new App\Model\Category(); ce radit. Nije najzgodnije, jednostavnije je koristiti ‘use’. ‘as’ definira alias.

Klase koje nisu model ili repozitorij mogu biti puno toga. Controller, Helper, Service, Listener, nesto trece sto sam zelis definirat…
Kontroler je nesto sto komunicira s inputom/outputom; servis je nesto sto zelis da bude dostupno iz bilo kojeg paketa/bundlea/kontrolera/whatever, helper je klasa pomocnih metoda koje po definiciji ne spadaju nigdje, a trebas ih u recimo nekom kontroleru ili servisu (recimo blesav primjer, preformatiranje datuma), listener je nazovi middleware koji recimo radi neke provjere na requestu itd.

Instaliraj si symfony2, i utrosi tjedan dana, probaj napravit nekakav jednostavan task u njemu. Inicijalna instalacija cak ima nekakav demo bundle kojeg mozes pogledat. Isplatit ce ti se, jer ce ti odgovorit na prakticki sva ova pitanja, i bit ce ti puno bistrije kako poslozit stvari. Spominjem bas symfony2, jer je jako modularan i to je framework iz kojeg je izaslo dosta drugih frameworkova koji koriste njihove bundleove. Recimo Laravel koristi hrpu symfony bundleova. I sam ces moc u svoj framework povuc bilo koji od symfony bundleova. Ali vaznije, pokupit ces ideje i dobre prakse.

Hvala na odgovoru :wink:

Stavio sam autoloader, ali imam slijedeći problem, tj. nije problem ali mi se nikako ne sviđa.

Napravio sam slijedeće

use Application\Model\Category; // čak i ne moram koristiti 'as'
new Category();

ali onda u Category.php modelu moram postaviti namespace i staviti use Model:

namespace Application\Model;

use Application\Core\Model;

class Category extends Model
{
......
}

I tako u svakom fajlu, to mi baš nekako i nije najbolja praksa (previše ponavljanja istog koda), jesam nešto propustio ? Ista stvar za controllere:

use Application\Core\Controller;

class IndexController extends Controller
{
	public function index()
	{	
		$this->template->show('index');
	}

}

Što se tiče testiranja frameworka, to ću svakako morati napraviti, uzet si tjedan-dva ili koliko je potrebno i testirati, nema druge, čisto da pohvatam logiku stvari.

Da, nije to nista cudno. Ne upada to u “ponavljanje” koda, vise u pravilnu deklaraciju stvari prije nego uopce pocnes pisat kod.
Dakle:

  • otvoris php tag
  • deklariras namespace klase
  • useas base klasu i sta ti vec treba
  • deklariras klasu

To je nekakva standardna praksa za kreiranje novog filea u vise manje svim kvalitetno strukturiranim frameworksima (i jezicima). Postoje IDE-i koji to na ‘new file’ sve ispisu, ukljucujuci i anotacije parametara i deklaraciju autora filea, prije nego si ti uopce slovo koda utipkao :slight_smile:

Hehe tako znači :slight_smile:

A šta ako mi nagodinu nešto pukne i odlučim promjeniti Controller u Kontroler :smiley: onda ću morati svaki fajl ručno ispravljati, a ako ih ima 20-30 nije to mali posao :slight_smile:

Kad si rekao da je ok logika 1 model = 1 tablica, unutar controllera mogu pozvati više modela ili to baš nije praksa ? tipa na stranici imam prikaz jednog teksta te prikaz komentara i vezanih slika.

namespace Application\Controller;

use Application\Core\Controller;

class TextController extends Controller
{
	public function single()
	{	
                $text = new Text();
                $comments = new Comment();
                $images = new Image();

                $this->template->text = $text->load($id);
                $this->template->comments = $comments->loadAllFromText($id);
                $this->template->images = $images->loadAllFromText($id);

		$this->template->show('textSingle');
	}
}

I sad su me ovi repositoriji malo zbunili… Ako bi sav SQL, tj upite prema ORM-u stavio u Repository, što onda ide u Model ? OK, jasna mi je validacija podataka, error messaging, slanje maila i takvih stvari, ali onda sve ove load, loadAll, loadPrev, loadNext idu u Repository klasu, tako da mi na kraju ništa ne ostane za Model ? Osim ako Repository klasu pozivam iz Modela, a ne iz Controllera ? No onda duplam funkcije jer će mi i Model i Repository sadržavati iste metode… Znam, malo sam dosadan, ali što više čitam gledam imam više pitanja i nejasnoća heh

Ako odlucis mijenjat controller u kontroler, to je teski breaking change; u rangu kao da PHP odluci da se klasa vise ne deklarira sa class nego sa ‘klasa’. Konvencije i standardi postoje da olaksaju posao, i samim time nisu varijabilne (barem ne u nekakvom razumnom roku), i naziv Controller je konvencija tvog frameworka (i skoro svih drugih :smiley: ).

Unutar kontrolera zoves koliko god trebas modela, servisa, helpera, i bilo cega drugog, to je sasvim normalno.

Model opisuje bazu. Sad, ovo sto govorim nije uklesano u kamen; vise je stvar dizajna frameworka, ali ja preferiram ovakav dizajn:
Recimo model ‘post’ ce biti klasa sa protected varijablama id, author, category, content, createdAt, updatedAt itd - dakle cisto reflektira kolone u tablici koja sadrzi postove., i ima public metode koje getaju i setaju te varijable: getId(), getCategory(), getContent(), itd.
A repozitorij sadrzi one metode koje nisu cisto dohvacanje i spremanje kolona; odnosno direktno upravljanje onim protected varijablama koje opisuju bazu. Recimo nekakva metoda getRelatedPosts(), getWeeklyTopPosts() i stajaznan, nemaju sta radit u klasi koja opisuje model Post. To su metode koje sluze business logici.
Da imas nekakvu vezanu tablicu “related_posts” i tvoja posts tablica sadrzi foreign key na related_posts tablicu, onda bi getRelatedPosts() imao smisla u tom modelu jer opisuje ono sto je u bazi. Tako ti konvencija omogucava da gledajuci u kod tocno znas kako stvari izgledaju u bazi, ili obrnuto, gledajuci bazu tocno znas da sigurno mora postojati metoda u modelu, koju zbog konvencije bez gledanja u kod odmah znas kako se zove, i koja dohvaca ili seta neku kolonu.
A za sve ostalo postoji repozitorij, koji sadrzi sve one metode koje ne opisuju bazu nego sluze nekoj business svrsi. Takozvane “custom” metode, jer ih nisi napravio “zato sto moras”, odnosno radi konvencije, nego si ih napravio jer ti trebaju rijesiti neki problem kojeg osnovni set metoda iz modela ne rjesava.

Al opet kazem, to je dizajn kakav meni ima najvise smisla, i kakav dugorocno podrzava kompleksnije stvari. Takvim dizajnom tvoj ORM prema klasi zna tocno kako treba izgledat baza i kako mapirat podatke. Recimo meni fantastican feature kod Doctrine ORMa je to sto ce te nakon promjene modela (u kodu) upozorit da stanje u bazi ne odgovara onom sto mu je u modelu, i znat ce ti izbacit (i izvrsit) SQL kod potreban da se schema baze updatea da odgovara tvom novom modelu.
S druge strane, kod nekih frameworkova ces nac da se sve sta se tice baze trpa u file koji opisuje jedan model.