Choď na navigáciu

3. Tvoríme CMS s CakePHP - Active Record

Treťou časťou pokračuje seriál o CakePHP. Táto časť patrí k najdôležitejším, pretože si v nej budeme podrobne vysvetľovať srdce celého frameworku a to je Active Record.

V minulej časti sme si povedali o základnej inštalácii celého frameworku a pre začiatok sme si vysvetlili, ako funguje základné rozloženie stránky. V dnešnej časti si predstavíme srdce celého CakePHP a ukážeme si, ako správne dodržiavať konvencie Cake v súvislosti s návrhom databáze. Samotný návrh databázy príde na radu v nasledujúcej čas­ti.

Active Record

Active Record je srdce CakePHP a dokonale demonštruje princípy, na akých je Cake postavený.

Active Record je návrhový vzor, ktorý mapuje databázové tabuľky na triedy, riadky na objekty a stĺpce na ich atribúty (Toto však v Cake nie je úplne tak, rozdiel je v tom, že aktuálne sa riadky a stĺpce mapujú na dvojrozmerné polia. Mapovanie na objekty je naplánované na verziu 2.0). Z názvu triedy modelu teda jednoducho odvodíme názov tabuľky, napr. máme model Article, a k nemu patrí tabuľka articles (vždy množné číslo od názvu modelu). Zároveň active record umožňuje definovať vzťahy medzi tabuľkami (hasMany, belongsTo, hasManyAndBelon­gsTo…), okrem toho obsahuje výkonné nástroje pre prácu s dátami, ako je zápis, čítanie, validácia.

Čo teda Active Record prináša do našej práce? Najdôležitejšia a podstatná vec je, že nám značne uľahčuje prácu s databázou, kde v konečnom dôsledku nemusíme písať SQL. Koľko práce trávime písaním, ladením sql dotazov, následné mapovanie výsledkov atď. Active Record nám umožňuje pracovať na oveľa vyššej intelektuálnej úrovni. Poďme sa pozrieť na nasledujúci príklad:

Máme tabuľku Articles, v ktorej máme uložené jednotlivé články. Bežným spôsobom chceme vybrať konkrétny článok SELECT * FROM articles WHERE id = 1

+----+-------------+--------------------+--------+---------------------+
| id | title       | text               | user   | created             |
+----+-------------+--------------------+--------+---------------------+
|  1 | Titulok     | Text               | Linus  | 0000-00-00 00:00:00 |
+----+-------------+--------------------+--------+---------------------+

V Cake si vytvoríme triedu Article (uložíme ju do súboru /app/models/ar­ticle.php), ktorá nám bude reprezentovať tabuľku articles (názov tabuľky je množné číslo názvu modelu)

class Article extends AppModel{
  var $name = 'Article';
}

A pomocou príkazu $this->Article->find(1); dostaneme požadovaný výsledok

Article =>
id: 1
title: Titulok
text: Text
user: Linus
created: 0000-00-00 00:00:00

Pôsobivé však? Ako sme si uviedli v prvej časti, konvencia má prednosť pred konfiguráciou, preto Cake automaticky predpokladá, že keď máme model nazvaný Article, v našej databáze existuje tabuľka nazvaná articles. Ak by sme však chceli zmeniť názov tabuľky a nepoužívať tak automatické konvecie, môžeme tak urobiť následovne

class Article extends AppModel{
  var $name = 'Article';
  var $useTable = 'clanky';
}

Vyššie sme si povedali, že Active Record obsahuje efektívne metódy nielen pre čítanie dát, ale aj pre ich manipuláciu. Poďme teda zmeniť titulok nášho článku

$article = $this->Article->find(1);
$article['Article']['title'] = 'nový titulok';
$this->Article->save($article);

po uložení môžeme vidieť obsah tabuľky

+----+-------------+--------------------+--------+---------------------+
| id | title       | text               | user   | created             |
+----+-------------+--------------------+--------+---------------------+
|  1 | nový titulok| Text               | Linus  | 0000-00-00 00:00:00 |
+----+-------------+--------------------+--------+---------------------+

A máme hotovo. Jednoduchšie to už ani nemôže byť. Absolútny rozdiel od spôsobu, kde by sme museli písať sql ako select…, potom update… je značný. Možností využitia Active Record je mnoho

  • $this->Article->findById(1) ⇒ vráti článok s id 1
  • $this->Article->findAll(null,a­rray(‚title,‘tex­t'),‚Article.cre­ated desc‘) ⇒ vráti články zotriedené podľa dátumu vytvorenia, a vyberú sa len stĺpce title a text
  • $this->User->findByName(‚Li­nus‘) ⇒ vráti autora s menom Linus

Bolo by asi zbytočné vypisovať tu ďalšie ukážky a demonštrovať tak silu Active Record. Pre jednotlivé metódy a ich parametre odporúčam pozrieť oficiálnu dokumentáciu. Okrem iného sme si povedali, že Active Record nám nedáva len nástroje pre manipuláciu s dátami, ale poskytuje nám taktiež možnosť validácie vstupných dát a určenie vzťahov medzi tabuľkami.

Validácia vstupných dát je jednou zo základných podmienok bezpečnosti a správnej funkcie webovej aplikácie. Active Record preto obsahuje vstavanú a veľmi prepracovanú validáciu, ktorá nám uľahčí množstvo práce. Chceme určiť, aby titulok článku nebol pri uložení prázdny

class Article extends AppModel{
   var $name = 'Article';
   var $validate = array(
           'title' => VALID_NOT_EMPTY,
           );
}

Týmto sme určili, že pri uložení článku, musí byť vždy atribút title neprázdny. Preto, keď sa pokúsime uložiť článok následovne, dostaneme chybovú hlášku, ktorá nás upozorní, že titulok článku nemôže byť prázdny.

$article = $this->Article->find(1);
$article['Article']['title'] = '';
$this->Article->save($save);

This field cannot be left blank

Používať môžeme aj niekoľko validačných kritérie pre jeden atribút. Chceme napríklad určiť, aby náš titulok nebol prázdny a zároveň, aby mal minimálne 5 znakov a maximálne 100 znakov, nie je nič jednoduchšie

class Article extends AppModel{
   var $name = 'Article';
   var $validate = array(
        'title' => array(
            'required' => VALID_NOT_EMPTY,
            'length' => array( 'rule' => 'validateLength', 'min' => 5, 'max' => 100 )
        ),
   );
}

Sami vidíte, že jednoduchšie to už ani nemôže byť. A k tomu ešte platí, že Cake automaticky vypĺňa hodnotu prvku formulára, pri neúspešnom uložení generuje chybovú hlášku pre každý prvok formulára (podľa určenia validácie v modely) a umožňuje nám určiť taktiež aj iné validačné pravidlá ako VALID_NUMBER, VALID_YEAR, resp. ľubovoľný vlastný regulárny výraz. Z tohto nám musí byť jasné, koľko práce, kódu a starostí máme ušetrených.

Posledná vec, ktorú spomeniem pri Active Record sú vzťahy medzi tabuľkami. Active Record umožňuje mapovať vzťahy medzi tabuľkami ako napríklad 1:N, M:N atď. Vezmime si náš prechádzajúci príklad. Určite budeme chcieť mať autorov v osobitnej tabuľke. Vytvoríme si preto novú tabuľku s názvom users. Každý článok bude mať práve jedného autora a autor môže mať niekoľko článkov, čiže tu máme väzbu 1:N. Kód bude nasledovný

class Article extends AppModel{
  var $name = 'Article';
  var $belongsTo = 'User';
}

class User extends AppModel{
  var $name = 'User';
  var $hasMany = 'Article';
}

Poďme si vytvoriť nového autora a následne nový článok s daným autorom

$user['user']['name'] = 'Linus';
$user['user']['email'] = 'linus@aaa.com';
$this->user->save($user);

+----+-------------+--------------------+
| id | name        | email              |
+----+-------------+--------------------+
|  1 | Linus       | linus@aaa.com      |
+----+-------------+--------------------+

$article['Article']['title'] = 'titulok';
$article['Article']['text'] = 'text';
$article['Article']['user_id] = 1;
$this->Article->save($article);

+----+-------------+--------------------+-----------+---------------------+
| id | title       | text               | user_id | created             |
+----+-------------+--------------------+-----------+---------------------+
|  1 | titulok     | text               | 1         | 0000-00-00 00:00:00 |
+----+-------------+--------------------+-----------+---------------------+

To bolo jednoduché, keď sa teraz pozrieme na článok $this->Article->find(1), vidíme detaily, že nám všetko funguje správne

Article =>
id: 1
title: Titulok
text: Text
user: Linus
created: 0000-00-00 00:00:00
user =>
id: 1
name: Linus
email: linus@aaa.com

Aké jednoduché však? Nemuseli sme písať žiadne dodatočné sql dotazy, stále nám stačí príkaz $this->Article->find(1), jediné, čo je potrebné, je dodržiavať konvencie Cake a správne určiť vzťahy medzi tabuľkami (určiť správny vzťah medzi danými tabuľkami nám značne pomáha tzv. console, venovať sa jej budeme v niektorých z nasledujúcich častí) a v daných tabuľkách správne dodržiavať cudzie kľúče. Poďme sa preto pozrieť, ako vyzerajú dané tabuľky.

cakephp hasmany active record

Z obrázku je jasné, že cudzí kľúč v tabuľke articles musí byť pomenovaný user_id čo sú dané konvencie Cake, aby framework mohol určiť, že sa jedná o cudzí kľúč z tabuľky users. Čiže vždy platí, že cudzí kľúč v tabuľke je zložený z jednotného číslo názvu tabuľky cudzieho kľúča a príslušného id, teda pre cudzí kľúč autora v tabuľke articles bude user_id.

Ako to bude s väzbou N:M? Opäť to bude veľmi jednoduché. Predstavme si situáciu, že článok môže mať niekoľko autorov a autor može mať niekoľko článkov. Tabuľky sú na sebe nezávislé a ich prepojenie bude zaisťovať špeciálna tabuľka, ktorú môžeme vidieť na obrázku

cakephp hasandbelongstomany active record

Opäť musíme dodržiavať konvencie Cake, aby všetko fungovalo správne. Názov tabuľky articles_users sa skladá z názvov tabuliek, ktoré sú vo vzťahu N:M, čiže articles a users a navyše tieto tabuľky musie byť zapísané v abecednom poradí a oddelené podtržítkom. Kód bude potom nasledovný

class Article extends AppModel{
  var $name = 'Article';
  var $hasAndBelongsToMany = array(
          'user' => array('className' => 'user',
                        'joinTable' => 'articles_users',
                        'foreignKey' => 'article_id',
                        'associationForeignKey' => 'user_id',
                           )
       );
}

class user extends AppModel{
  var $name = 'user';
  var $hasAndBelongsToMany = array(
          'Article' => array('className' => 'Article',
                        'joinTable' => 'articles_users',
                        'foreignKey' => 'user_id',
                        'associationForeignKey' => 'article_id',
                           )
       );
}

Ešte treba poznamenať, že model sa pre spojovaciu tabuľku articles_users nevytvára, vďaka dodržiavaniu konvencií Cake to nie je potrebné.

Týmto sme u konca. Dúfam, že sa mi podarilo zachytiť základné rysy a možnosti Active Record v CakePHP. Samotný návrh databáze a jej testovanie príde na radu v nasledujúcom článku.

Rád uvítam vaše námety a pripomienky v diskusii k článku.

Povedz o článku aj ostatným - www.pridej.cz

Hodnotenie článku: 50%
Počet hodnotení: 84

zlýdobrý

Komentáre k článku

Nový komentár

  1. Titulok: Active Record
    Autor: janko
    Vytvorený: 14. 02. 2008 20:07

    „Koľko práce trávime písaním, ladením sql dotazov“, no zrovna na ukazkach tak par sekund (SELECT nieco FROM nieco WHERE nieco). Ja mam stale problemy s agregacnymi funkciami (group by), to stravim tak-ci-tak vela prace pisanim nie len dotazov … S cake-om sa ucim, ale uz ma to api studovat nebavi, ci to tam chyba, ci je to chyba, ako to pouzit …

  2. Titulok: mala chybicka
    Autor: greppi
    Vytvorený: 25. 02. 2008 09:25

    na zaciatok, pekny clanok :) ale tusim ze syntax:

    $this->Article->findAll(null,a­rray(‚title,‘tex­t'),‚Article.cre­ated desc‘)

    je trocha popletena, nema to byt:

    $this->Article->findAll(null,a­rray('title','tex­t')‚ "Article.cre­ated desc")

    ?

  3. Titulok: RE: mala chybicka
    Autor: Tibor
    Vytvorený: 25. 02. 2008 11:46

    No toto sposobuje texy. Ak si to pozornejsie vsimnes, tak sa jedna o ciarku a nasledne apostrof, ale bohuzial texy to sformatovalo tak, ze to vyzera ako dve ciarky po sebe. Dakujem za upozornenie, musim sa nato pozriet a nastavit texy, aby apostrofy neformatoval.

  4. Titulok: off topic - cakePHP 1.1 -> 1.2
    Autor: greppi
    Vytvorený: 25. 02. 2008 21:39

    ucim sa teraz cakePHP 1.1ku, hlavne z toho dovodu ze k 1.2ke nie je zatial poriadna dokumentacia a tiez vacsina komponentov na 1.2ke hapruje, myslis ze nebude problem potom prejst na 1.2ku ?

    Dik

  5. Titulok: RE: off topic - cakePHP 1.1 -> 1.2
    Autor: Tibor
    Vytvorený: 26. 02. 2008 00:04

    No ja si myslim, ze medzi tymito verziami je zasadny rozdiel.

    Jasny rozdiel je hned vidiet z vypisu tried vo verzii 1.1 a vo verzii 1.2

    Takze rozhodne by som odporucal zacat sa ucit verziu 1.2, pretoze je ovela vyspelejsia ako 1.1 a disponuje oproti 1.1 obrovske mnozstvo novych funkcii, ktore proste zato stoja (i ked je v sucasnosti 1.2 v stadiu beta).

    A k tej kompatibilite: kedze sa v 1.2 menila znacna cast kodu a znacna cast bola pridana, tak je jasne, ze verzie programov postavene na cake 1.1 nebudu s verziou 1.2 kompatibilne.

  6. Titulok: RE: RE: off topic - cakePHP 1.1 -> 1.2
    Autor: greppi
    Vytvorený: 26. 02. 2008 09:20

    aha, no dam si poradit od skusenejsich a prejdem teda na 1.2ku :), … pogooglil som a nasiel som celkom slusny navod na 1.2ku

  7. Titulok: Nefunguje
    Autor: w3q
    Vytvorený: 28. 02. 2008 22:01

    Asi som debil, ale vôbec mi nefunguje ten výber z DB. Najprv mi to písalo chybu z triedov model a napísalo kde ju mám vložiť a čo do nej napísať. Spravil som to a písalo zase niečo, aj to som spravil, teraz nepíše žiadnu chybu, ale vôbec mi ani jeden ten kód nefunguje.

  8. Titulok: RE: Nefunguje
    Autor: Tibor
    Vytvorený: 28. 02. 2008 22:48

    No skutocne neviem co ti mam poradit, ked nic konkretne neuvadzas. Podla mna si nespravne pochopil model view controller architekturu (ta bude vysvetlana v 5. clanku). Ak si ju spravne pochopil a mas kode spravne rozdelene, potom nikde nemoze byt chyba.

    Takze vezmem ukazky z tohto clanku. Musis mat vytvorene nasledovne subory a v nich nasledovny kod

    Model (subor /app/models/ar­ticle.php)

    class Article extends AppModel {
      var $name = 'Article';
    }

    K modelu musis mat v databaze vytvorenu tabulku pomenovanu articles a v nej polia pomenovane id,name,text

    Controller (subor /app/controller­s/articles_con­troller.php)

    class ArticlesController extends AppController {
      var $name = 'Articles';
    
      function index() {
        $this->set('articles',$this->Article->findAll());
      }
    }

    View (subor /app/views/ar­ticles/index.ctp)

    foreach($articles as $article):
      e($article['Article']['name']);
      e($article['Article']['text']);
    endforeach;

    Aby si videl, ci ti to nieco vypisuje, zadaj adresu napr. adresu tvoja-adresa/articles/in­dex

    Toto je kompletny kod, ktory potrebujes, aby ti vsetko spravne fungovalo. Pisal som to z hlavy, takze je mozne, ze sa tam nejaka kozmeticka chyba vyskytuje. Dufam, ze som pomohol, ak nie, tak tu hod presny vypis chyby, a hod aj svoje kody z Model, View, Controller.

  9. Titulok: RE
    Autor: Tibor
    Vytvorený: 29. 02. 2008 22:46

    Takze poprve. Nepouzivaj mi tu ziadne vulgarne vyrazy ani nic podobne, nikto tu nie je nato zvedavy. Taktiez tu nehadz kazdu blbu chybu, ktoru ti to pise a radsej si poriadne pozri manual php. Toto co si tu napisal je uplne kravina a holy nezmysel

    <?php foreach($articles as $article):
       $article = $this->Article->find(1);
    endforeach; ?>

    Ziadne sql nepatria do view a uz ani nehovorim o tom nezmysly, co dany kod znamena. Pozri si laskovo moj priklad vyssie, v ktorom som ti jasne napisal, ako a kde mas ktore casti kodu umiestnit. Ak ti to nie je stale jasne, pockaj si na dalsie casti serialu, ale nespamuj mi tu takymito prispevkami.

  10. Titulok: oprava
    Autor: w3q
    Vytvorený: 05. 03. 2008 08:02

    Controller:

    <?php
    class ArticlesController extends AppController
    {
      var $name = 'Articles';
    
      function index()
      {
        $this->set('articles',$this->Article->find(1));
      }
    }
    ?>

    Model:

    <?php
    class Article extends AppModel{
      var $name = 'Article';
      }
    ?>

    View:

    <?php
    foreach($articles as $article):
      e($articles['Article']['title']);
      echo "<br>";
      e($articles['Article']['text']);
    endforeach;
    ?>
  11. Titulok: RE: Oprava
    Autor: Tibor
    Vytvorený: 06. 03. 2008 11:46

    To bude zrejme sposobene tym, ze tabulky articles nema ziadnu vazbe a preto ti model nevracia pole v tvare $articles[‚Ar­ticle‘] ale vracia ti to v tvare $articles[0]. Vyskusaj si vypisat vo view, co sa nachadza v premennej $articles a hned budes vediet

    <?php
    var_dump($articles);
    foreach($articles as $article):
      e($articles['Article']['title']);
      echo "<br>";
      e($articles['Article']['text']);
    endforeach;
    ?>
  12. Titulok: otázka selectu
    Autor: Fabyen
    Vytvorený: 28. 08. 2008 10:26

    ahoj mám otázku cakephp sa učím , mozete mi prosím objasniť vzťahy $hasMany $hasBelong …moc som to nepochopil , mám trebars články a vkaždom článku je user_id ako ident uživatela z tabulky users ( banálny príklad ) ako vyselektujem meno uživatela k jednotlivému článku ? v modeli $hasMany ? a v controllery $uses= array(‚Clanky‘,‚U­ser‘); ? ( no viem že to nieje podla cake konvencií ale píšem to ako príklad ) Opps nieak som sa zamotal , jedná sa o to , že sa mi tento select podaril a funguje to ako má , ale ak sa pozriem v controllery n apremennú ‚User‘ zisím že mi natiahol všetkých uživatelov z databáze … ako určiť tento vzťah ? ( v cake? )aby sa mi selectovali len uživatelia k jednotlivým článkom ?

  13. Titulok: RE:otázka selectu
    Autor: Tibor
    Vytvorený: 01. 09. 2008 23:03

    No presne tento priklad na ktory sa pytas, je uvedeny aj v clanku a funguje bez problemov. V clanku su uvedene aj zdrojove kody k modelom article a user kde presne vidis, ako su urcene vztahy a funguje to spravne.

    class Article extends AppModel{
      var $name = 'Article';
      var $belongsTo = 'User';
    }
    
    class User extends AppModel{
      var $name = 'User';
      var $hasMany = 'Article';
    }
  14. Titulok: vymazanie polozky
    Autor: jardos
    Vytvorený: 26. 10. 2008 19:08

    class User extends AppModel
    {
      var $name = "User";
      var $belongsTo = array(
                    'Group' => array('className' => 'Group',
                                     'foreignKey' => 'group_id'));
    }
    
    class Group extends AppModel
    {
      var $name = "Group";
      var $hasMany = array(
                     'User' => array('className' => 'User',
                                     'foreignKey' => 'group_id'));
    }

    ako zabezpecim aby bolo mozne vymazat skupinu len pokial vnej niesu ziadny uzivatelia. momentalne to mam osetrene tak krkolomne ze v kontrollery si najskor zistim informacie o danej skupine a nasledne ak Users je prazdne ju vymazem, urcite to ale ide aj jednoduchsie neviete postrcit niekto .. „ktorym smerom sa ubrat“ ?

    dakujem ..

  15. Titulok: RE: vymazanie polozky
    Autor: Tibor
    Vytvorený: 29. 10. 2008 22:10

    Odpoved najdes v diskusii k tomutoclanku, je to tam priamo vyriesene http://www.ims.rockretail.com/…ame-pracovat

    Inak taka mala poznamka k tomu co si napisal… Vidim, ze mas ocividne zly pristup a pracu s datami vykonavas v controllery. Mal by si si vytvorit metodu v modely a tam by si si mal zistovat ci ma nejakych uzivatelov a pripadne ju vymazat a z controlleru by si mal len volat tuto metodu modelu.