Niezalogowany [ logowanie ]
Subskrybuj kanał ATOM Kanał ATOM

Autor wpisu: Śpiechu, dodany: 06.11.2011 14:10, tagi: mysql, php

Trochę nie pisałem. Mam nadzieję, że dzisiejszy wpis wszystkim wynagrodzi moją nieobecność. Ostatnio stanąłem przed wyzwaniem zrobienia galerii zdjęć, których kolejność dałoby się dowolnie modyfikować za pomocą przeciągania i upuszczania. Dzisiaj opiszę operacje bazodanowe, a na następny raz jQuery. Będę maksymalnie upraszczał aby nie zaciemniać meritum.

1. Przygotowania

Powiedzmy, że mamy 2 tabele relacyjne odpowiedzialne za przechowywanie galerii i obrazów. Np. takie:

CREATE TABLE `galleries` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
  `title` VARCHAR(250) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 ;
 
CREATE TABLE IF NOT EXISTS `images` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `gallery_id` INT(10) UNSIGNED NOT NULL,
  `filename` VARCHAR(50) NOT NULL,
  `ordr` INT(10) UNSIGNED NOT NULL DEFAULT '1',
  `created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `image_to_gallery` (`gallery_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 ;

Od razu widać dwa dziwactwa: dlaczego pole nazywa się ordr a nie order? Z czystego lenistwa. Order jest słowem zarezerwowanym w SQL (ORDER BY coś tam). Każdorazowo nazwa pola musiałaby być w nawiasach. Dlaczego pole updated ma domyślną wartość 0000–00-00 00:00:00? Ano dlatego, że CURRENT_TIMESTAMP można użyć tylko raz w tabeli. Wobec tego stworzymy od razu wyzwalacz (trigger), który przed każdym zapytaniem typu UPDATE poprawi wartość na taką jak trzeba.

DELIMITER $$
CREATE TRIGGER `updated_current_timestamp` BEFORE UPDATE ON `galleries`
   FOR EACH ROW BEGIN
      SET NEW.updated = NOW();
END$$

Na koniec trzeba stworzyć relację 1 galeria do wielu zdjęć.

ALTER TABLE `images`
   ADD CONSTRAINT `image_to_gallery` FOREIGN KEY (`gallery_id`) REFERENCES `galleries` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;

Oznacza to, że kasując galerię od razu pozbędziemy się również wszystkich powiązanych z nią zdjęć. Pliki z dysku oczywiście nie znikną. Można napisać funkcję, która przed usunięciem galerii z bazy najpierw wyrzuca wszystkie powiązane z nią pliki, a dopiero potem wykonuje polecenie DELETE.

2. Decyzje

Teraz nadszedł czas na poważne decyzje. Chodzi o sposób manipulacji wierszami dotyczącymi zdjęć. Można to zrobić za pomocą PHP. Jest to rozwiązanie prostsze. Powoduje jednak spory narzut komunikacji PHP<—>SQL. W przypadku zwalenia wszystkiego na bazę danych, pchamy logikę wyżej i bliżej modyfikowanych danych. Minusem jest cholerna składnia SQLowa i późniejsze problematyczne utrzymanie kodu. Problem oczywiście nie istnieje gdy robimy galeryjkę na 10 obrazków i przestawimy sobie kolejność ostatniego na przedostatni. Ja raczej podchodzę do rzeczy poważnie i wolę od początku zrobić to tak jak powinno być. Poza tym wyzwalacze i procedury składowane to jest to, co bazodanowe tygryski lubią najbardziej :-)

3. Wykonanie

Każdy nowy rekord tabeli images musi mieć nadany odpowiedni identyfikator pozycji ordr o 1 większy od ostatniego w danej galerii. Mamy trzy rozwiązania: czysty PHP, wyzwalacz wywoływany przed INSERTem lub procedura składowana. Zapytanie w PHP może wyglądać tak:

$q = $pdo->prepare('INSERT INTO images (filename,gallery_id,ordr) (SELECT ?,?,MAX(ordr)+1 FROM images WHERE gallery_id=? LIMIT 1)');
$q->bindValue(1, 'obrazek.jpg', PDO::PARAM_STR);
$q->bindParam(2, $galleryId, PDO::PARAM_INT);
$q->bindParam(3, $galleryId, PDO::PARAM_INT);
$q->execute();

Wspominam o tym rozwiązaniu dlatego, że ma ciekawą konstrukcję INSERT SELECT. Zapewne większość z was po kilkunastokrotnej próbie wywołania polecenia INSERT INTO VALUES i gdzieś tam SELECT dostanie cholery i rozbije zapytanie na 2: pierwsze sprawdza ostatni ordr, a następne doda 1 i umieści INSERTem pozostałe dane. Bazodanowe tygryski wybiorą jednak co innego. Procedury składowane!

DELIMITER $$
CREATE PROCEDURE `insert_image`(IN image_filename VARCHAR(50), IN image_gallery_id INT, OUT last_inserted_id INT)
   MODIFIES SQL DATA
   COMMENT 'Inserts new image at the end of given gallery.'
   BEGIN
      DECLARE max_order INT;
 
      # Zamiast SET zmienna= uzywam SELECT INTO just FOR fun
      SELECT MAX(ordr) INTO max_order FROM images WHERE gallery_id=image_gallery_id LIMIT 1;
 
      # Gdy obrazek jest pierwszy w galerii
      IF max_order IS NULL THEN 
         SET max_order = 1;
      ELSE
         SET max_order = max_order + 1;
      END IF;
      INSERT INTO images (filename, gallery_id, ordr) VALUES (image_filename, image_gallery_id, max_order);
      SELECT LAST_INSERT_ID() INTO last_inserted_id;
END$$

Próba wywołania $pdo->lastInsertId() zakończy się niepowodzeniem (a raczej zerem :-) ). Dlatego potrzebujemy parametru wyjściowego. Poniżej pokazuję jak całość wywołać w PDO:

$q = $pdo->prepare('CALL insert_image(?,?,@lastInsertId)');
$q->bindValue(1, 'obrazek.jpg', PDO::PARAM_STR);
$q->bindParam(2, $galleryId, PDO::PARAM_INT);
$q->execute();
$outputArray = $pdo->query('select @lastInsertId')->fetch(PDO::FETCH_ASSOC);
$lastInsertId = $outputArray['@lastInsertId'];

Ktoś może się zapytać po co te numery ze zmienną wyjściową. PDO i sterownik MySQL w PHP ma szpetny błąd dotyczący obsługi parametrów wyjściowych z procedur składowanych. Podobno w nowszych wersjach jest OK. Trik podany wyżej u mnie działa i oszczędza trochę nerwów.

Czytaj dalej tutaj (rozwija treść wpisu)
Czytaj dalej na blogu autora...

Autor wpisu: Łukasz Socha, dodany: 06.11.2011 10:48, tagi: php

pobierz w .pdf(przeznaczone do wydruku)

PHP jest obecnie bezdyskusyjnie najpopularniejszym językiem programowania używanym w aplikacjach internetowych. Jego główną zaletą jest prostota i łatwość nauki (chociażby brak jawnych typów). Jednak, ma on także znaczne ograniczenia. PHP nie nadaje się do wykonywania bardziej zaawansowanych algorytmów – język nie posiada odpowiednich narzędzi do tworzenia abstrakcyjnych struktur danych. No to jak sam PHP nie ma takich możliwości, to połączmy go z C++…

Jak to zrobić?

Do uruchamiania programów napisanych w innych językach możemy użyć funkcji popen().

Za manualem:

resource popen ( string $command , string $mode )

Funkcja popen() wykonuje polecenie zawarte w $command – może to być np. program w C++ lub polecenie systemowe systemów operacyjnych UNIX. Parametr $mode określa zaś prawa dostępu do procesu. Funkcja zwraca (w przypadku sukcesu) uchwyt do uruchomionego procesu, w przeciwnym wypadku zwróci false.

Przykładowy program

Żeby zobaczyć jak to wygląda w praktyce stwórzmy prosty program do sortowania liczb. Wykorzystamy sortowanie bąbelkowe.

Najpierw stwórzmy kod w C++:

#include <iostream>
#include <string>
#include <sstream>
using namespace std;
void boubleSort(int table[], int size) {
	int i, j, temp;
     for (i = 0; i<size; i++) {
		for (j=0; j<size-1; j++) {
			if (table[j] > table[j+1]) {
			 temp = table[j+1];
                table[j+1] = table[j];
                table[j] = temp;
            }
        }
	}
}

int main(int argc, char** argv) {
	int n=argc-1;
	int t[n+1];
	for(int i=0;i<n;i++) {
		stringstream konwersja(argv[i+1]);
		konwersja>>t[i];
	}
	boubleSort(t, n);
	for(int i=0;i<n;i++) {
		cout << t[i]<< " ";
	}
	return 0;
}

W liniach 5-16 zawarty jest algorytm sortujący. W wierszach 21-24 odbieramy i konwertujemy argumenty przekazywane przy uruchamianiu programu do typu int. Właśnie za pomocą tablicy argv będziemy przekazywać dane ze skryptu PHP. Linie 26-28 wyświetlają wynik na standardowym wyjściu (zostanie on przekazany do skryptu PHP).

Teraz stwórzmy skrypt PHP:

Czytaj dalej tutaj (rozwija treść wpisu)
Czytaj dalej na blogu autora...

Autor wpisu: Tomasz Kowalczyk, dodany: 04.11.2011 21:57, tagi: php

Witajcie! Już dawno nie pisałem na blogu, ale o tym później (nie dzisiaj :)). Ostatnio jestem strasznie zarzucony pracą i staram się robić wszystko tak szybko, jak to tylko możliwe, ale na pierwszym miejscu stawiam raczej "porządne" wykonanie poszczególnych elementów, bo "kto szybko robi, ten dwa razy robi". Pisząc testy jednostkowe do jednej z bibliotek [...]

Autor wpisu: Łukasz Socha, dodany: 03.11.2011 19:00, tagi: oop, php, mvc

pobierz w .pdf(przeznaczone do wydruku)

W ostatniej części artykułu o wzorcu MVC stworzymy pozostałe elementy prostego systemu artykułów.

Dobrą praktyką przy budowaniu aplikacji z użyciem wzorca MVC jest „rozbicie” całego kodu na poszczególne, mniejsze moduły. W poprzedniej części stworzyliśmy fragmenty kodu do obsługi kategorii, teraz zajmiemy się artykułami.

Tworzymy kontroler artykułów

Na początek tworzymy kontroler controllers/articles.php:

<?php
/**
 * @author Łukasz Socha <kontakt@lukasz-socha.pl>
 * @version: 1.0
 * @license http://www.gnu.org/copyleft/lesser.html
 */

include 'controller/controller.php';

class ArticlesController extends Controller{

    public function index() {
        $view=$this->loadView('articles');
        $view->index();
    }
    public function one() {
        $view=$this->loadView('articles');
        $view->one();
    }
    public function add() {
        $view=$this->loadView('articles');
        $view->add();
    }
    public function insert() {
        $model=$this->loadModel('articles');
        $model->insert($_POST);
        $this->redirect('?task=articles&action=index');
    }
    public function delete() {
        $model=$this->loadModel('articles');
        $model->delete($_GET['id']);
        $this->redirect('?task=articles&action=index');
    }
}

I tu także przeanalizujmy reakcje dla następujących adresów URL:

  • ?task=articles&action=index – zostanie wywołana metoda index(), która inicjuje obiekt widoku articles, następnie zostaje wywołana metoda index()
  • ?task=articles&action=one – zostanie wywołana metoda one(), która inicjuje obiekt widoku articles, następnie zostaje wywołana metoda one()
  • ?task=articles&action=add – zostanie wywołana metoda add(), która inicjuje obiekt widoku articles, następnie zostaje wywołana metoda add()
  • ?task=articles&action=insert – zostanie wywołana metoda insert(), która inicjuje obiekt modelu articles, następnie zostaje wywołana metoda insert()
  • ?task=articles&action=delete – zostanie wywołana metoda delete(), która inicjuje obiekt modelu articles, następnie zostaje wywołana metoda delete()

Musimy jeszcze zmodyfikować plik index.php, by skrypt wywoływał kontroler Articles:

<?php

if($_GET['task']=='categories') {
include 'controller/categories.php';
    $ob = new CategoriesController();
    $ob->$_GET['action']();
} else if($_GET['task']=='articles') {
include 'controller/articles.php';
    $ob = new ArticlesController();
    $ob->$_GET['action']();
} else {
    $ob = new ArticlesController();
    $ob->index();
}

Mamy już kontroler. Teraz przejdźmy do modelu.

Tworzymy model artykułów

model/articles.php:

<?php
/**
 * @author Łukasz Socha <kontakt@lukasz-socha.pl>
 * @version: 1.0
 * @license http://www.gnu.org/copyleft/lesser.html
 */

include 'model/model.php';

class ArticlesModel extends Model{

    public function getAll() {
        $query="SELECT a.id, a.title, a.date_add, a.autor, c.name FROM articles AS a LEFT JOIN categories AS c ON a.id_categories=c.id";
        $select=$this->pdo->query($query);
        foreach ($select as $row) {
            $data[]=$row;
        }
        $select->closeCursor();

        return $data;
    }
    public function getOne($id) {
        $query="SELECT a.id, a.title, a.date_add, a.autor, c.name, a.content FROM articles AS a LEFT JOIN categories AS c ON a.id_categories=c.id where a.id=".$id;
        $select=$this->pdo->query($query);
        foreach ($select as $row) {
            $data[]=$row;
        }
        $select->closeCursor();

        return $data;
    }
    public function insert($data) {
        $ins=$this->pdo->prepare('INSERT INTO articles (title, content, date_add, autor, id_categories) VALUES (
            :title, :content, :date_add, :autor, :id_categories)');
        $ins->bindValue(':title', $data['title'], PDO::PARAM_STR);
        $ins->bindValue(':content', $data['content'], PDO::PARAM_STR);
        $ins->bindValue(':date_add', $data['date_add'], PDO::PARAM_STR);
        $ins->bindValue(':autor', $data['author'], PDO::PARAM_STR);
        $ins->bindValue(':id_categories', $data['cat'], PDO::PARAM_INT);
        $ins->execute();
    }
    public function delete($id) {
        $del=$this->pdo->prepare('DELETE FROM articles where id=:id');
        $del->bindValue(':id', $id, PDO::PARAM_INT);
        $del->execute();
    }
}

Tak jak w przypadku kategorii, metody w ArticlesModel dodają, usuwają oraz poobierają dane z bazy danych.

Czytaj dalej tutaj (rozwija treść wpisu)
Czytaj dalej na blogu autora...

Autor wpisu: Wojciech Sznapka, dodany: 01.11.2011 21:33, tagi: php, symfony, symfony2

Recently I did a lot of Test Driven Development on my Symfony2 bundle. I used PHPUnit’s built-in mocks and stubs for many projects, so I took it again. But while I was working on mocking Symfony2 core objects I found those mocks very uncomfortable in use. I tried Mockery and it saved my day. Let’s [...]
Wszystkie wpisy należą do ich twórców. PHP.pl nie ponosi odpowiedzialności za treść wpisów.