Niezalogowany [ logowanie ]
Subskrybuj kanał ATOM Kanał ATOM

Autor wpisu: Tomasz Kowalczyk, dodany: 06.11.2011 15:07, tagi: css, php

Co tu dużo pisać - przed Wami kolejny Linkdump związany z PHP, zapraszam do lektury!       Fotografia: Alexandr Mitriuc @ Fotolia. Linkdump: PHP. I like PHP. I like it too. :) Warto przeczytać, dlaczego można lubić ten język. CSS Crush: PHP-Based CSS Pre-Processor. "Przetwarzacz" CSS w PHP. Open Source PHP Search Engine – [...]

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 [...]

Autor wpisu: singles, dodany: 29.10.2011 18:39, tagi: php

Niedawno natrafiłem na problem polegający na konieczności wybrania z istniejącego pliku XML kilku losowych elementów. Tzn, dla przykładu – mamy plik z 50 pytaniami (nie wnikam w powody, dla których są one właśnie w XML, tylko uznaję to za pewnik), a przy każdym odświeżeniu strony do klienta ma trafiać zestaw kilku innych, losowo wylosowanych pytań. Nie jest to problem spotykany często, mam jednak nadzieję, że przedstawione tutaj rozwiązanie komuś się przyda.

Problem

Przypuśćmy, że mamy nastepujacy plik (mocno uproszczony przykład):

<?xml version="1.0" encoding="UTF-8"?>
<questions>
    <question>
        <subject>Question 1</subject>
        <answer1>Answer 1</answer1>
        <answer2>Answer 2</answer2>
    <question>
 <!-- etc -->
</questions>

Jak wspomniałem wcześniej, zależy nam każdorazowym wybraniu innego zestawu pytań i zwróceniu ich w postaci prawidłowo sformatowanego dokumentu XML.

Możliwe rozwiązania

Po przejrzeniu w sieci informacji na ten temat, najcześciej natrafiałem na podejście pt. „Zamień XMLa na arraya, wylosuj klucze, i z powrotem stwórz z tego XMLa.”

Niestety, w PHP (bez zastosowania dodatkowych bibliotek czy funkcji) nie jest to rzecz, którą da się załatwić w kilku linijkach. Dodatkowo, dochodzi tutaj kwestia dwukrotnej konwersji.

Podejście drugie polega na wykorzystaniu wyrażeń regularnych. Istnieje jednak dość powszczechna zasada, aby do parsowania HTMLa czy też XMLa nie używać wyrażeń regularnych, ponieważ nie mamy pewności czy dokument został sformatowany prawidłowo.

Możemy także wykorzystać XPath, wybierając po jednym elemencie o indeksie z zakresu (1 – liczba potenacjalnych elementów), pamiętając o tym, że elementy nie mogą się powtórzyć.

Jednakże, podejście drugie i trzecie ma to do siebie, że do wylosowanych elementów musimy dodać otaczającą ją resztę dokumentu, która nie zawsze może być tak uproszczona jak w naszym przykładze.

DOM na ratunek

A gdyby zamiast wybierania losowych elementów, które później zwracamy w postaci nowo utworzonego dokumentu XML, usuwać węzły z oryginalnego dokumentu? Wynikowo otrzymamy zestaw danych w tym samym formacie, bez konieczności dbania o resztę struktury dokumentu czy też parsowania.

W celu wykonania zadania, posłużyłem się klasą DOMDocument (domyślnie dostępna w PHP), która udostępnia zestaw metod znanych z JavaScriptu jako części DOM API. Poniżej przedstawiam kawałek kodu wykorzystujący wspomniane przeze mnie podejście.

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

Wszystkie wpisy należą do ich twórców. PHP.pl nie ponosi odpowiedzialności za treść wpisów.