Połączenie z bazą – ORM

Na prostym przykładzie pokażę jak używać modułu ORM do pobierania danych z bazy. Będzie to prosta baza zawierająca w jednej tabeli użytkowników, a w drugiej ich artykuły.
Na początku o wymaganiach:
1. Nazwy tabel należy podawać w języku angielskim w liczbie mnogiej, np. users, articles.
2. Nazwy pól powinny być w liczbie pojedynczej, np. name, password, id.
3. Do połączenia tabel używamy relacji, np. jeden do wielu (jeden użytkownik może mieć wiele artykułów) w tabeli articles należy podać nazwę tabeli users w liczbie pojedynczej i jej klucz: user_id. Pole to znajduje się w tabeli articles i zawiera nr id użytkownika, który napisał artykuł.

Schemat bazy:
tabela users
| id | nick | password |

tabela articles
| id | user_id | title | content | date |

Zawiera ona dwóch użytkowników i 2 artykuły. Bazę można pobrać: Testowa baza (554)

Pierwsze co należy zrobić to włączyć w bootstrap.php moduły database i ORM, trzeba usunąć znak komentarza z lini:

/application/bootstrap.php

Kohana::modules(array(
	 'database'   => MODPATH.'database',   // Database access
	 'orm'        => MODPATH.'orm',        // Object Relationship Mapping
	));

Następnie trzeba ustawić dane do bazy w konfigu. Plik database.php należy skopiować z folderu /modules/database/config/ do folderu /application/config, a następnie ustawić dane do bazy:

Konfig database: /application/config/database.php (zmieniamy w default array)

'hostname'   => 'localhost',
'database'   => 'kohana',
'username'   => 'kohana',
'password'   => 'kohana',

W database.php podajemy tylko dane do bazy, host, nazwę bazy, nazwę użytkownika i hasło. Reszta pozostaje bez zmian.

Należy teraz utworzyć 2 modele (dla tabeli użytkowników i dla artykułów)

Model user: /application/classes/model/user.php

<?php defined('SYSPATH') OR die('No direct access allowed.');

class Model_User extends ORM {

    protected $_has_many = array(
            'article' => array(),
        );

    public function  __construct($id = NULL) {
        parent::__construct($id);
    }
}
?>

Do połączenia tabel należy użyć relacji. Wpis w $_has_many oznacza, że jeden użytkownik może mieć wiele artykułów, natomiast wpis w modelu article $_belongs_to oznacza, że jeden artykuł jest przypisany do jednego użytkownika.

Model article: /application/classes/model/article.php

<?php defined('SYSPATH') OR die('No direct access allowed.');

class Model_Article extends ORM {

    protected $_belongs_to = array(
            'user' => array(),
        );
    
    public function  __construct($id = NULL) {
        parent::__construct($id);
    }
}
?>

Załóżmy, że mamy już kontroler article, np. z Pierwsze starcie. Chcemy pobrać wszystkie artykuły z tabeli article i dane użytkownika z tabeli user. W kontrolerze article pobieramy dane i przesyłamy je do widoku. Aby pobrać i wyświetlić wszystkie artykuły wystarczy przesłać do widoku zmienną o wartości ORM::factory(‚article’)->find_all();

Kontroler article: /application/classes/controller/article.php

<?php defined('SYSPATH') or die('No direct script access.');

class Controller_Article extends Controller_Default {
     public function action_index() {
        $this->template->title = __('Article');
        $this->template->content='article';
        $articles=ORM::factory('article')->find_all();
        $this->template->articles=$articles;
    }

    public function action_add() {
        $this->template->title = __('Add article');
        $this->template->content='article_add';
    }
}
?>

W pętli dla każdego elementu wyświetlamy artykuły.

Widok article: /application/views/article.php

<h1><?php echo $title?></h1>
<p><a href="/article/add">Add article</a></p>
<?php foreach ($articles as $article){
    echo '<h2><a href="/article/add/'.$article->id.'">'.$article->title.'</a></h2>'.
            '<p><strong>Dodał: </strong>'.$article->user->nick.' <strong>dnia</strong> '.$article->date.'</p>'.
            '<p>'.$article->content.'</p>';
}?>

Jak widać powyżej, aby pobrać dane z innej tabeli wystarczy dać $article->user->nazwa_pola_z_tabeli_user

Na koniec jeszcze krótko o dodawaniu i aktualizacji pól w bazie. Aby dodać wpis do bazy należy w kontrolerze w akcji add do zmiennej, np. $article przypisać ORM::factory(‚article’); a następnie do atrybutów (nazwy jak w tabeli article) przypisać wartości np. ze zmiennej $_POST: $article->title=$_POST['title']; A następnie wykonać $article->save();
Aby edytować jakiś wpis w bazie jako drugi atrybut funkcji factory należy podać nr id: $article=ORM::factory(‚article’, $id); gdzie $id to np. 1, przypisać nowe wartości, a następnie wykonać save().
Kontroler article teraz wygląda tak:

Kontroler article: /application/classes/controller/article.php

<?php defined('SYSPATH') or die('No direct script access.');

class Controller_Article extends Controller_Default {
     public function action_index() {
        $this->template->title = __('Article');
        $this->template->content='article';
        $articles=ORM::factory('article')->find_all();
        $this->template->articles=$articles;
    }

    public function action_add($id=NULL) {
        $this->template->title = __('Add article');
        $this->template->content='article_add';
        $this->template->msg='';
        $this->template->users=ORM::factory('user')->find_all()->as_array('id','nick');
        $this->template->id=isset($id) ? '/'.$id : '';
                
        if(isset($_POST['submit'])){
            $data=$_POST;
            $article=ORM::factory('article', isset($id) ? $id : NULL);
            $article->title=$data['title'];
            $article->user_id=$data['user_id'];
            $article->content=$data['content'];
            $article->date=date('Y-m-d H:i:s');
            $article->save();
            $this->template->msg='<p class="green">'.(!isset($id) ? __('Dodawanie zakończone powodzeniem',NULL,'pl-pl') : __('Edycja zakończona powodzeniem',NULL,'pl-pl')).'</p>' ;
            unset($_POST);
        }
        
        $input=array('title', 'user_id', 'content');
        if(!isset($data)&&(isset($id))){
            $this->template->title=__('Edit article');
             $device=ORM::factory('article',$id);
             foreach($input as $in){
                $data[$in]=$device->$in;
                }
             $this->template->data=$data;
         }elseif(!isset($_POST['submit'])){
            foreach($input as $in){
                $data[$in]='';
                }
            $this->template->data=$data;
        }
    }
}
?>

Na początku przekazujemy do widoku pustą zmienną z komunikatem (pusta, gdyż musi być przesłana, a w momencie gdy chcemy wyświetlić komunikat, nadpisujemy jej wartość). Przekazujemy wszystkich użytkowników do widoku, aby można było z listy wybrać kto dodaje artykuł (nie ma tu logowania, więc tylko wybieramy z listy kto dodał). Sprawdzamy czy w linku jest nr id artykułu, jeśli jest to formularz prowadzi do edycji, jeśli nie ma to dodajemy nowy artykuł. Następnie sprawdzamy czy wysłano formularz, jeśli tak to zapisujemy do bazy dane. Na końcu akcji sprawdzane jest czy mamy do czynienia z edycją (przesyłamy dane do formularza) jeśli dodajemy nowy wpis przesyłamy puste wartości do widoku.

Widok z formularzem do dodawania i edycji(jeśli wystąpi id w linku) wygląda tak:
Widok article_add: /application/views/article-add.php

<h1><?php echo $title?></h1>
<?php
echo  Form::open('article/add'.$id).
'<fieldset>'.
    '<legend>'.__('Uzupełnij pola',NULL,'pl-pl').'</legend>'.
            $msg.
            Form::label('title', __('Title').':').
            Form::input('title', $data['title']).'<br />'.

            Form::label('user_id', __('User').':').
            Form::select('user_id', $users, $data['user_id']).'<br />'.

            Form::label('content', __('Content').':',array('style'=>'vertical-align: top')).
            Form::textarea('content', $data['content'], array('cols'=>'60','rows'=>'5')).'<br />'.

        '</fieldset>'.
        Form::submit('submit', __('Submit')).
        Form::close();
?>

Formularze i zarządzanie nimi to osobny temat, dlatego szczegółowe wyjaśnienia zostawię na osobny artykuł. Pokrótce: do widoku z formularzem musimy przesyłać puste wartości na początku, gdyż w przypadku edycji przesyłane są wartości z bazy. Można to rozwiązać na dwóch akcjach (do dodawania i osobno do edycji) i 2 widokach ale w tym przykładzie jest to w jednym. Gdy dodajemy nowy wpis (/article/add) przesyłane są puste wartości do inputów, po wysłaniu formularza zostaje dodany nowy wpis. Gdy edytujemy istniejący wpis (/article/add/id) pobierane są wartości z bazy i przesyłane do inputów, po wysłaniu formularza dane w bazie zostają zaktualizowane.

Gotowe pliki można pobrać: Baza połączenia ORM (452)

28 Odpowiedzi :“Połączenie z bazą – ORM”

  1. Trochę brzydka sprawa z umieszczaniem wszystkiego w kontrolerze.
    Po to mamy model, żeby tam właśnie trzymać kwestie związane z bazą danych.
    Proponuję lekką modyfikację:

    $article=ORM::factory('article', isset($id) ? $id : NULL)->save_data($_POST);
    

    a w modelu:

    public function save_data($data)
    {
        $this->title=$data['title'];
        $this->user_id=$data['user_id'];
        $this->content=$data['content'];
        $this->date=date('Y-m-d H:i:s');
        $this->save();
    }
    

    Takie podejście jest dużo bardziej czytelne.
    Poza tym bardzo fajny blog. Popieram inicjatywę :)
    Pozdrawiam!

  2. Rafał napisał:

    Witam,

    Czy w najbliższym czasie można spodziewać się jakiegoś artykułu na temat logowania?

    Pozdrawiam,
    Rafał

  3. Mariusz napisał:

    Przepraszam za brak nowych wpisów, ale wiąże się to z brakiem czasu, ale niebawem będę miał go więcej… Obecnie kończę pisać pracę mgr – system do zarządzania siecią (pisany w Kohanie :) zawiera m.in. logowanie i panel admina, tak że jak skończę wrzucę artykuły na ich temat i sam system – podział internetu w sieciach LAN i zarządzenie klientami)

    • Adam napisał:

      Witam,
      mam pytanie co do następującego kawałku kodu:

      ‚hostname’ => ‚localhost’,
      ‚database’ => ‚kohana’,
      ‚username’ => ‚kohana’,
      ‚password’ => ‚kohana’,
      ‚persistent’ => FALSE,

      czy jeżeli utworze bazę w mysql o nazwie kohana to użytkownika mam root, czy w takim przypadku mam utworzyć użytkownika kohana oraz nadać mu hasło kohana?

      Dziękuję z góry za odpowiedź.

      • Mariusz napisał:

        To są przykładowe dane do logowania, więc albo utworzyć nowego użytkownika, np. kohana, albo podać dane istniejącego w tym configu.

        • Adam napisał:

          Postanowiłem wypróbować Twój plik „zebrana wiedza” gdzie korzystasz z modułu Auth oraz bazy ORM, zainstalowałem całość na wampserwer 2.5 i niestety nie działa w pełni, nie łączy mnie z bazą danych.
          Zgodnie z Twoimi wskazówkami utworzyłem bazę o nazwie kohana oraz utworzyłem użytkownika o nazwie kohana z hasłem kohana – niestety otrzymuję komunikat „wewnętrzny błąd serwera jeżeli chce się zalogowac albo wyświetlić artykuł” czy możesz mi pomóc i napisać co może być problemem? dziękuję z góry

          • Mariusz napisał:

            Na jakiej wersji próbujesz? Zapewne artykuł był na przykładzie wersji 3.1.. Możesz sprawdzić też logi serwera, czy coś w nich nie ma.

            Możesz ewentualnie spróbować z wersją 3.2 wpis Drugie starcie – zebrana wiedza [KO3.2], a następnie samodzielnie dostosować: Migracja z 3.2 do 3.3.

          • Adam napisał:

            Korzystam z Twojego przykładu w wersji 3.2 o nazwie drugie starcie i niestety nie działa,

            poniżej podaję plik który umieściłem w modules->database -> config -> database
            oraz w application -> config -> database i niestety nie działa. Już naprawdę nie wiem w czym może tkwić problem.

            array
            (
            ‚type’ => ‚mysql’,
            ‚connection’ => array(
            /**
            * The following options are available for MySQL:
            *
            * string hostname server hostname, or socket
            * string database database name
            * string username database username
            * string password database password
            * boolean persistent use persistent connections?
            * array variables system variables as „key => value” pairs
            *
            * Ports and sockets may be appended to the hostname.
            */
            ‚hostname’ => ‚localhost’,
            ‚database’ => ‚kohana’,
            ‚username’ => ‚kohana’,
            ‚password’ => ‚kohana’,
            ‚persistent’ => FALSE,
            ),
            ‚table_prefix’ => ”,
            ‚charset’ => ‚utf8′,
            ‚caching’ => FALSE,
            ‚profiling’ => TRUE,
            ),
            ‚alternate’ => array(
            ‚type’ => ‚pdo’,
            ‚connection’ => array(
            /**
            * The following options are available for PDO:
            *
            * string dsn Data Source Name
            * string username database username
            * string password database password
            * boolean persistent use persistent connections?
            */
            ‚dsn’ => ‚mysql:host=localhost;dbname=kohana’,
            ‚username’ => ‚root’,
            ‚password’ => ‚r00tdb’,
            ‚persistent’ => FALSE,
            ),
            /**
            * The following extra options are available for PDO:
            *
            * string identifier set the escaping identifier
            */
            ‚table_prefix’ => ”,
            ‚charset’ => ‚utf8′,
            ‚caching’ => FALSE,
            ‚profiling’ => TRUE,
            ),
            );

          • Mariusz napisał:

            Wyświetla jakiś komunikat? Sprawdziłeś te logi?

          • Adam napisał:

            Tak wyświetla mi „Wewnętrzny błąd serweraHTTP 500″ co do logów to możesz podpowiedzieć jak należy je sprawdzić?

          • Mariusz napisał:

            Szukaj pliku error.log w katalogu apache.

          • Adam napisał:

            Hej, długo milczałem z powodu braku czasu jednak plik już mam i widzę w nim wiele błędów jak np. AH00354: Child: Starting 64 worker threads.

            naprawdę nie wiem co dalej.

  4. stmn napisał:

    1. Jeśli jest relacja has_many, to w listingu w tablicy nie powinno być articles (liczba mnoga)? Chyba, że coś się zmieniło w tej wersji.

    2. Zamieszczanie w widoku foreach, a w nim html’a wyświetlanego przez echo wygląda okropnie, i dalej podobnie.

    3. ORM::factory(‘article’, isset($id) ? $id : NULL);
    Po co tak, przecież wystarczy tak:
    ORM::factory(‘article’, $id);

    4. Czy coś się zmieniło i od 3.1 trzeba nadpisywać konstruktor w modelu?

    5. A poniższe to dla mnie zupełna zagadka, czemu sobie tak utrudniasz, masz przy tym jakieś korzyści? Poza tym akcja nazywa się add, a Ty tu dodajesz i edytujesz. Jeszcze usuwania brakuje. ;)

    $input=array('title', 'user_id', 'content');
            if(!isset($data)&amp;&amp;(isset($id))){
                $this-&gt;template-&gt;title=__('Edit article');
                 $device=ORM::factory('article',$id);
                 foreach($input as $in){
                    $data[$in]=$device-&gt;$in;
                    }
                 $this-&gt;template-&gt;data=$data;
             } (...)
    
  5. Mariusz napisał:

    Ad. 1. W liczbie poj. działa, we wcześniejszych też chyba tak było.
    Ad. 2. Od czasu do czasu mi się to zdarza :) jestem trochę leniwy i czasami umieszczam html w echo..
    Ad. 3. Masz chyba rację, niepotrzebnie sprawdzałem tu czy jest taka zmienna, chociaż jak jej nie ma, a odwołuję się do niej to nie pamiętam czy nie wyświetli błędu, chociaż w tym miejscu może to być zbędne.
    Ad. 4. Jest to tylko po to aby pokazać jak wygląda konstruktor modelu (różni się od konstruktora kontrolera), nic w nim nie ma, można go usunąć.
    Ad. 5. Tak, jest to zarówno do dodawania, jak i edycji, kod byłby podobny stąd połączyłem w jednym miejscu. Aby było czytelniejsze lepiej to rozdzielić i przenieść do modelu, tak jak wspomniał @Hekima. Postaram się dodać nowy artykuł z bardziej zrozumiałymi rozwiązaniami. Tak aby nie propagować złych nawyków.

  6. white napisał:

    co opłaca się przełożyć z kontrollera do modelu i w jaki sposób ?

  7. white napisał:

    jak odseparować kod z kontrolera do modelu, we właściwy sposób ?

    • Mariusz napisał:

      Dobry sposób przedstawił Hekima kilka komentarzy wyżej. W kontrolerze tworzymy obiekt ORM, a w modelu dzięki np. funkcji save_data przesyłamy dane z $_POST i zapisujemy.

      $article=ORM::factory('article');
      if(isset($_POST['submit']))
      $results=$article->save_data($_POST);
      

      W modelu może być także walidacja. Jeśli będzie pozytywna funkcja save_data zapisze dane i zwróci false do $results. Jeśli się nie powiedzie zwróci błędy. Przykład wykorzystania zawarty jest w Drugie starcie w modelu article. Proponowałbym przeanalizowanie jak tam to jest wykonane.
      PS. Ostatnio znów dopadł mnie brak czasu :(

  8. morawcik napisał:

    Na początku chcę powiedzieć, że Twoje artykuły są świetne. Oby tak dalej!

    Co do kodu to miałem jeden problem. Otóż wszystko było ok do momentu edycji, wtedy wykonywało się dodawanie. Po kilku próbach doszedłem do wniosku, że nie identyfikuje mi parametru id. Rozwiązaniem było dodanie na początku akcji add:

    $id = $this->request->param('id');
    

    Teraz śmiga aż miło.

    • Mariusz napisał:

      Wpis był pisany przed wersją 3.2, gdzie można było odwoływać się do parametrów również tak:

      public function action_add($id=NULL) {
      

      W najnowszej Kohanie należy tak, jak zauważyłeś:

      $id = $this->request->param('id');
      

      Tu jest taki wstęp do ORM, validację i zapisywanie polecam przenieść do modelu, tak jak jest to w komentarzach wyżej.

  9. white napisał:

    Witam,

    postanowiłem użyć ORM, po raz pierwszy, mam relacje wielu do wielu, ale troche nie wiem jak najpoprawniej porozdzielać wszystko do modeli/kontrolerów, mógłbyś dać jakiś przykład, jak to powiązać ?
    bo nie chciałbym wszystkiego ładować w kontrolera, albo nie potrzebnie tworzyć modele,

    i czy ORM ma jakąś magiczną sztuczkę na dodanie nowego rekordu który bedzie powiązany z tabelami, czy trzeba do każdej tabeli osobno dodawać ?

    • Mariusz napisał:

      Opisz co to za połączenie, to co czasami wydaje się relacją wiele do wielu jest czasami relacją jeden do wielu.

      Aby była relacja wiele do wielu musi być tabela w której przechowywane są wpisy z 2 tabel. Na przykład jeśli mamy 2 tabele: categories i parameters, to tabela zwać się powinna categories_parameters

      W modelu category:

      class Model_Category extends ORM{
          protected $_has_many = array(
              'parameters' => array(
                  'through' => 'categories_parameters'
              ),
          );
      

      A w modelu parameter:

      class Model_Parameter extends ORM{
          protected $_has_many = array(
      	'categories' => array(
      	    'through' => 'categories_parameters'
      	),
          );
      

      Do dodania powiązania użyj funkcji add();

      $category = ORM::factory('category', $id);
      $param = ORM::factory('parameter', $id);
      $category->add('parameters', $param);
      
  10. white napisał:

    Witam,

    jeszcze tylko takie pytanko bo nie wiem gdzie trzymać takie metody/funkcje
    jak dodaje coś do bazy przez ORM, to wszystko robic w kontrolerze ?

    jak mam takie coś :

            public function action_addPerson(){
                $this->persones->name = 'John';
                $this->persones->surname = 'John';
                $this->persones->save();
                $this->persones->add('categories', 1);
                $this->persones->add('categories', 2);
            }
            
    

    to taki kod (to tylko przykład, moze meic błedy itp) trzymać w osobnym modelu dla db czy w modelu ORM w ktorym jest deklarowana relacja ?

    • Mariusz napisał:

      Ogólnie to zapisywanie do bazy, validację powinno się przechowywać w modelu, w kontrolerach operacje ‚zlecenie’ zapisu do bazy, sprawdzanie praw, obliczenia, natomiast w widokach to co jest wyglądem, szablon, kod html. W pierwszych komentarzach do tego wpisu jest przykładowe rozwiązanie jak to powinno wyglądać.

  11. Janek napisał:

    Jak zrobic ten formularz żeby dodawac bez przeładowania w jQuery + ajax? Mógłby ktoś to pokazac? Pozdrawiam

    • Mariusz napisał:

      Mniej więcej tak:

      <span id="msg"></span>
      <form>
      <input type="text" name="name1">
      <input type="button" value="Submit" onclick="articleadd();">
      </formm>
      
      <script type="text/javascript">
      function articleadd(){
          $jQ.post("<?php echo URL::base()?>ajax/articleadd/", 
          { 
              content: $jQ('input[name=name1]').val()
          },
          function(response){
              if (response != 'ok')
              {
                  $jQ("#msg").text(response);          //błąd
              }
              else
              {
                  $jQ('input[name="name1"]').val('');   //wyczyszczenie
                  $jQ("#msg").text('OK');               //wyświetlenie OK
                  //window.location ='/article/details/'; //lub przekierowanie
              }
          });
      }
      </script>
      

      No i stworzyć kontroler do obsługi ajaxu:

      <?php defined('SYSPATH') or die('No direct script access.');
      
      class Controller_Ajax extends Controller{
          public function action_articleadd(){
              if(Auth::instance()->logged_in())
              {
                  if (isset($_POST))
                  {
                      //validacja i dodanie do bazy
                      echo 'ok';
                  }
                  else
                  {
                      echo 'nie ma posta';
                  }
              }
              else
              {
                  echo 'zaloguj się';
              }
      }
      

      Możesz też zamiast wyświetlać echo w odpowiedzi zwracać tablicę, np. json w której będziesz miał wartości msg (komunikat), errory dla poszczególnych pól, i np. wartość gdzie ma przekierować jeśli nie ma błędów.
      Pisane z głowy może gdzieś być literówka.

Dodaj komentarz

Dodając kod PHP używaj tagów: [php][/php]

*