Agudo

web2

t: 504 426 710 e: pawel@agudo.pl

Blog w oparciu o MongoDB i Zend Framework

Data: 03.03.2010

W tym wpisie będziemy tworzyć prosty blog w oparciu o bazę MongoDb którą opisywałem ostatnio MongoDB - wstęp do słownikowej bazy danych w PHP. Ponieważ blog będzie oparty o Zend Framework więc niezbędna jest minimalna wiedza o tym stworze.

1. Model

Wszystkie funkcjonalności zawrzemy w jednym kontrolerze - IndexController. Na początek jednak zajmiemy się modelem, który będzie nam służył do obsługi bazy danych.

class Model_News {
    protected $db = null;
    protected $collection = null;
    public function  __construct() {
        $connection = new Mongo();
        $this->db = $connection->selectDB('blog');
        $this->collection = $this->db->selectCollection('news');
    }

    public function  __call($name, $args = array()) {
	        return call_user_func_array(array($this->collection, $name), $args);
	}
}

Z założenia ma być prosto. W konstruktorze wybieramy bazę danych a z niej kolekcję (nie jest to najlepszym rozwiązaniem na przyszłość). Natomiast magiczna metoda __call() umożliwia nam odpytywanie kolekcji w przyjemny sposób z wykorzystaniem metod z MongoCollection. Powyższy kod zapewni nam wszystkie niezbędne funkcje do pobierania wiadomości na blogu. Plik zapisujemy pod nazwą News.php w folderze default/models.
Przejdźmy teraz do pierwszej akcji w której będziemy wyświetlać listę wiadomości.

2. Wyświetlanie treści wpisów bez komentarzy

public function indexAction()
{
    $newsModel = new Model_News();
    $this->view->news = $newsModel->find()->sort(array('date' => -1));
    $this->view->newsCount = $newsModel->count();        
}

Powyższy kod wykonuje mniej więcej takie czynności:
- Łączy się z bazą, wybiera kolekcję - 1 linijka
- Pobieramy wszystkie dokumenty sortując je po etykiecie 'date' - 2 linijka
- Pobieramy liczbę dokumentów z kolekcji - 3 linijka

3. Dodawanie

Tutaj stworzymy formularz który będzie nam służył do dodawania i edycji wiadomości, a przy okazji zajmie się walidacją i filtrowaniem danych. Skorzystałem z Zend_Form, plik zapisałem w folderze forms/News.php.

class Form_News extends Zend_Form {
    public function  __construct($options = array()) {
        parent::__construct($options);

        $title = new Zend_Form_Element_Text('title');
        $title->setRequired(true)
                ->setLabel('Tytuł')
                ->addValidator('NotEmpty', true)
                ->addValidator('StringLength', false, array(5, 180));

        $text = new Zend_Form_Element_Textarea('text');
        $text->setLabel('Opis')
            ->setRequired(true)
            ->setAttrib('class', 'text')
            ->addValidator('NotEmpty', true)
            ->addValidator('StringLength', false, array(5, 2000));

        $submit = new Zend_Form_Element_Submit('submit');
        $submit->setValue('Zapisz');

        $this->addElements(array($title, $text, $submit));
    }
}

Akcja odpowiadająca za dodanie wpisu.

public function addAction() {
        $form = new Form_News();
        if($this->_request->isPost()) {
            $data = $this->_request->getPost();
            if($form->isValid($data)) {
                $news = array(
                            'title' => $data['title'],
                            'text'  => $data['text'],
                            'date'  => time()
                    );

                $newsModel = new Model_News();
                $newsModel->insert($news, true);
                $this->_redirect('index');
            }
        }

        $this->view->form = $form;
    }

Sama akcja także nie jest skomplikowana. Tworzymy obiekt formularza sprawdzamy czy wywołanie nie jest POST, jeśli tak to walidujemy dane i jeśli wszystko jest dobrze tworzymy tablicę i zapisujemy ją do bazy poprzez metodę insert(). Jeśli któryś z powyższych warunków nie jest spełniony generowany jest formularz.

4. Edycja wpisu


$id = $this->_getParam('id', null);
        $newsModel = new Model_News();
        $news = $newsModel->findOne(array('_id' => new MongoId("$id")));
        if(null === $news) {
            throw new Zend_Controller_Action_Exception('Page not found',404);
        }
        $form = new Form_News();
         if($this->_request->isPost()) {
            $data = $this->_request->getPost();
            if($form->isValid($data)) {
                $n = array(
                            'title' => $data['title'],
                            'text'  => $data['text'],
                            'update'  => time(),
                            'date'  => $news['date']
                    );
                 $data = $newsModel->update(array('_id' => new MongoId("$id")), $n);
                 if($data) {
                     $this->_redirect('index');
                 }
                 else {
                     $this->view->error = 'Update error';
                 }
            }
        }
        else {
            $form->populate($news);
        }
        $this->view->form = $form;

Jest to akcja analogiczna do tej z punktu wyżej, jedynie na początku poprzez parametr przekazany w adresie tworzymy obiekt MongoId i szukamy konkretnego wpisu. Każdy dokument w kolekcji ma tworzoną automatycznie indywidualna wartość _id składającą się z 24 znaków.

5. Widok wiadomości plus komentarze

Kod formularza służącego do obsługo dodawania komentarzy jest bardzo prosty składa się z 3 elementów text input, textarea i submit - plik forms/Comment.php.


class Form_Comment extends Zend_Form {
    public function  __construct($options = array()) {
        parent::__construct($options);

        $title = new Zend_Form_Element_Text('author');
        $title->setRequired(true)
                ->setLabel('Autor')
                ->addValidator('NotEmpty', true)
                ->addValidator('StringLength', false, array(5, 180));

        $text = new Zend_Form_Element_Textarea('text');
        $text->setLabel('Treść')
            ->setRequired(true)
            ->setAttrib('class', 'text')
            ->addValidator('NotEmpty', true)
            ->addValidator('StringLength', false, array(5, 2000));

        $submit = new Zend_Form_Element_Submit('submit');
        $submit->setValue('Zapisz');

        $this->addElements(array($title, $text, $submit));
    }
}

Natomiast akcja odpowiadająca za wyświetlanie i dodawanie komentarza wygląda następująco:


public function newsAction() {
        $id = $this->_getParam('id', null);
        $newsModel = new Model_News();
        
        $news = $newsModel->findOne(array('_id' => new MongoId("$id")));
        if(null === $news) {
            throw new Zend_Controller_Action_Exception('Page not found',404);
        }
       
        $form = new Form_Comment();
        if($this->_request->isPost()) {
            $data = $this->_request->getPost();
            if($form->isValid($data)) {
                $comment = array(
                            'author' => $data['author'],
                            'text'  => $data['text'],
                            'date'  => time()
                    );
                               
                $newsModel->update(array('_id' => new MongoId("$id")), array('$push' => array('comments' => $comment)));
                $this->_redirect('/index/news/id/'.$id);
            }
        }
   
        $this->view->form = $form;
        $this->view->news = $news;
    }

Kolejność kroków jakie wykonujemy jest następująca:
- szukamy wiadomości po parametrze id
- ładujemy formularz komentarzy
- jeśli wywołanie to post sprawdzamy przesłane dane i ewentualnie dodajemy komentarz do kolekcji newsa
- dodajemy formularz do widoku
- dodajemy wiadomość do widoku

Najważniejsza linijka to:

$newsModel->update(array('_id' => new MongoId("$id")), array('$push' => array('comments' => $comment)));

W mongoDb mamy klika "zmiennych" pomocnych przy zapytaniach. Cała ich lista to:
$gt, $gte, $lt, $lte, $eq, $neq, $exists, $set, $mod, $where, $in, $nin, $inc, $push, $pull, $pop, $pushAll, $popAll.
W naszym przypadku $push dodaje do tablicy comments kolejną tablicę z danymi komentarza dzięki czemu w dokumencie mamy tablicę komentarzy, nie musimy ich pobierać przez jakieś joiny i inne tego typu wynalazki.

6. Zakończenie

Przekazałem tu podstawowe informacje na temat mongoDb które myślę, że jednostkom zainteresowanym tematem pomogą trochę rozwinąć skrzydła. Jakby ktoś potrzebował więcej praktyki to zawsze można dopisać stronicowanie wyników(limit, skip), usuwanie komentarzy i kilka innych funkcjonalności. Polecam także śledzić grupę dyskusyjną projektu. Zwolenników irca może skusi irc://irc.freenode.net/#mongodb. Zrzut całej aplikacji opisanej powyżej do ściągnięcia tutaj. Powodzenia.