Niezalogowany [ logowanie ]
Subskrybuj kanał ATOM Kanał ATOM

Autor wpisu: Zyx, dodany: 25.03.2009 11:07, tagi: php

Siedzę sobie ostatnio i dłubię przy oficjalnym porcie bibliotek OPL dla Zend Frameworka, a ponieważ OPL na dzień dzisiejszy składa się właściwie wyłącznie z Open Power Template'a, moje zadanie sprowadza się do przeprojektowywania obsługi literki V w zendowej implementacji MVC. Muszę powiedzieć, że inwencja programistów jest naprawdę powalająca, a to, co wyczyniają, by wyświetlić trochę kodu HTML, byłoby całkiem silną nominacją do konkursu jak maksymalnie skomplikować prostą czynność.

Autor wpisu: nospor, dodany: 24.03.2009 13:37, tagi: php

Wzorzec obserwator to moim zdaniem jeden z ciekawszych wzorców jakie wymyślono. Korzystam z niego od dłuższego czasu i muszę powiedzieć jest bardzo przydatny. Bardzo pomaga w wyodrębieniu funkcjonalności oraz zapobiega wrzucania wszystkiego do jednego wora.

Autor wpisu: Diabl0, dodany: 20.03.2009 12:08, tagi: sql

Ostatnio zaprezentowałem MySQLową wersję implementacji drzewa tzw. metodą Depesza (patrz tutaj). Nie napisałem natomiast nic na temat praktycznego wykorzystania takiego drzewa. Dlatego dzisiaj ciąg dalszy, tym razem już bardziej praktyczny.

Aby wygodnie operować na naszej bazie, a zwłaszcza znacznie ułatwić sobie pobieranie list kategorii gotowych do wyświetlenia, potrzebujemy jeszcze 2 funkcji:

1 - ścieżka kategorii - Potrzebna nam do przygotowania posortowanej listy kategorii:

DROP FUNCTION IF EXISTS treepath;
CREATE FUNCTION treepath( in_leaf_id INTEGER ) RETURNS TEXT
BEGIN
	--
	-- Funckcja zwracająca ścieżkę kategorii dla zadanego ID kategorii
	--
	DECLARE reply TEXT;
	DECLARE t_name VARCHAR(127);

	-- deklarujemy kursor (zakręcona implementacja pgsqlowego FOR row IN query LOOP)
	DECLARE done INT DEFAULT 0;
	DECLARE cur1 CURSOR FOR
		SELECT tree.name FROM tree INNER JOIN tree_pos ON tree.id = tree_pos.parent_id WHERE tree_pos.tree_id =  in_leaf_id ORDER BY tree_pos.depth DESC;

	DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

	OPEN cur1;
		REPEAT
			FETCH cur1 INTO t_name;
			IF NOT done THEN
					SET reply = CONCAT_WS( '/', reply, t_name );
			END IF;
		UNTIL done END REPEAT;
	CLOSE cur1;

	SET reply = CONCAT( '/', reply );

    RETURN reply;
END;

Przykład działania:

mysql> select treepath(8);
+-------------------------------------------+
| treepath(8)                               |
+-------------------------------------------+
| /Kategoria główna/Kat 1/Kat 1-2/Kat 1-2-1|
+-------------------------------------------+
1 row in set

2 - poziom zagnieżdżenia wybranej kategorii

DROP FUNCTION IF EXISTS item_depth;
CREATE FUNCTION item_depth( in_item_id INTEGER) RETURNS INTEGER
BEGIN
	--
	-- Funkcja zwraca poziom zagnieżdżenia dla zadanego ID kategorii
	--
	DECLARE reply INTEGER;

	SELECT depth INTO reply FROM tree_pos WHERE tree_id = in_item_id ORDER BY depth DESC LIMIT 1;

    RETURN reply;
END;

Ta natomiast banalna i pozornie niepotrzebna funkcja znacznie ułatwi nam zbudowanie podstawowego zapytania:

mysql> SELECT *, item_depth( id ) AS depth FROM tree ORDER BY treepath(id) ASC;
+----+-----------+------------------+-------+
| id | parent_id | name             | depth |
+----+-----------+------------------+-------+
|  1 | NULL      | Kategoria główna |     0 |
|  2 |         1 | Kat 1            |     1 |
|  4 |         2 | Kat 1-1          |     2 |
|  7 |         4 | Kat 1-1-1        |     3 |
|  9 |         4 | Kat 1-1-2        |     3 |
|  5 |         2 | Kat 1-2          |     2 |
|  8 |         5 | Kat 1-2-1        |     3 |
|  3 |         1 | Kat 2            |     1 |
|  6 |         3 | Kat 2-1          |     2 |
+----+-----------+------------------+-------+
9 rows in set

lub idąc dalej:

mysql> SELECT id, CONCAT( REPEAT('  ', item_depth( id ) ), '+- ', name) FROM tree ORDER BY treepath(id) ASC;
+----+-------------------------------------------------------+
| id | CONCAT( REPEAT('  ', item_depth( id ) ), '+- ', name) |
+----+-------------------------------------------------------+
|  1 | +- Kategoria główna                                 |
|  2 |   +- Kat 1                                            |
|  4 |     +- Kat 1-1                                        |
|  7 |       +- Kat 1-1-1                                    |
|  9 |       +- Kat 1-1-2                                    |
|  5 |     +- Kat 1-2                                        |
|  8 |       +- Kat 1-2-1                                    |
|  3 |   +- Kat 2                                            |
|  6 |     +- Kat 2-1                                        |
+----+-------------------------------------------------------+
9 rows in set

Z tak przygotowanymi danymi już chyba każdy sobie poradzi z ich wyświetlaniem.

Garść innych ciekawych informacji (jak np. modyfikacja kolejności) znajdziecie na stronie http://www.depesz.com/various/various-sqltrees-implementation.php z której czerpałem inspiracje.

Uzupełniony zrzut z testowej bazy łącznie z przykładowymi wpisami można znaleźć tutaj: tree_v2.sql. Natomiast sam opis struktury i triggerów znajdziecie tutaj.

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

Autor wpisu: nospor, dodany: 17.03.2009 17:52, tagi: php

Dosyć często spotykam się z pytaniem, jak napisać grupowanie wyników, czyli np. mamy produkty które należą do kategorii i jak wyświetlić wszystkie kategorie wraz z należącymi do nich produktami. Początkujący programiści albo nie wiedzą jak to zrobić, albo robią to przy użyciu wielu zagnieżdżonych zapytań, co niezbyt dobrze wpływa na wydajność aplikacji.

Autor wpisu: Tomasz Sh4dow Budzyński, dodany: 17.03.2009 13:33, tagi: css, php

Z nudów człowiek robi dziwne rzeczy. Jedną z nich jest parser dla plików CSS. Początkowo miał być to tokenizer który pozwoliłby na tworzenie różnego rodzaju narzędzi operujących na stylach CSS. Niestety po zapoznaniu się dokładnie ze strukturą CSS w wersji 2.1 odkładam ten plan na bliżej nie określoną przyszłość. Ale na pewno napisze, bo może być to ciekawe doświadczenie.

Po co mi ten skrypt ? Bo czasami jak otrzymam od kogoś kawałek szablonu ze skryptami. To wygląda to jak pole po bitwie lub niekończące się tasiemce. Często w pracy grafik przysyła nam szablony pocięte do tego CSS gdzie każda klasa szablonu jest napisana w jednej linii, a do tego bardzo długiej. Ja preferuje zupełnie odmienny styl. Na początek musimy przeanalizować plik CSS. Wykorzystamy do tego proste wyrażenia regularne, które rozpoznają nam poszczególne części. Musimy rozpoznać 3 podstawowe rzeczy: Nazwę klasy wraz z jej ‘ciałem’, następnie z ów ciała musimy wyciągnąć atrybuty i ich wartości. Trzecią i ostatnią rzeczą są komentarze, które z mojego punktu widzenia są w ogóle niepotrzebne więc w tej wersji są po prostu usuwane.

/* wyrazenie regularne do wyszukiwania klas */
$patern_main = '~([\.|#]?[^{]*)[\s]*\{([^}]*)\}~i';
/* wyrazenie regularne do wyszukiwania atrybutow w klasach */
$patern_css = '~([^\:\;\s]+)\s*:\s*([^\;\s]+)~i';
/* wyrazenie regularne do wyszukania i usuniecia komentarzy */
$patern_comments = '~\/\*[^*]*\*+([^/*][^*]*\*+)*\/~i';

Cała reszta jest już prosta. W pierwszej kolejności usuwamy wszystkie znaki nowej linii oraz tabulatory, a następnie pozbywamy się wszystkich komentarzy. czyli:

$css = file_get_contents('/sciezka/do/pliku.css');
$css = str_replace(array("\n", "\r", "\t"), '', $css );
/* usuwanie komentarzy */
$css = preg_replace($patern_comments, '', $css);

Tak obrobiony styl będziemy teraz przeszukiwać, korzystając z wyrażeń regularnych.

/* wyszukiwanie klas */
preg_match_all($patern_main, $css, $match);
 
$css_array = array();
foreach (array_keys($match[0]) as $key) {
	$css_match = array(); //czyscimy tablice w przypadku pustej klasy
	$css_body = trim( $match[2][ $key ] );
	if( $css_body != '' ) { //jesli definicja klasy jest pusta to ja opuszczamy
		preg_match_all($patern_css, $css_body, $css_match);
		$css_array[ trim( $match[1][ $key ] ) ] = array_combine($css_match[1], $css_match[2]);
	} else {
		$css_array[ trim( $match[1][ $key ] ) ] = array();
	}
}

W ten sposób mamy już „rozłożony” styl CSS na czynniki pierwsze. Można by się pokusić oczywiście o sprawdzanie poprawności poszczególnych elementów i ich wartości. Ale sądzę że jest to raczej czysta formalność stworzyć słownik atrybutów oraz ich dopuszczalnych wartości. Drugim pomysłem może być powiedzmy analiza i wyszukanie powtarzających się klas lub też wielokrotne powtarzanie tych samych atrybutów wraz z wartościami w różnych klasach. Ale podejrzewam, że będzie to materiał na inny wpis.

Teraz może poukładamy wszystkie klasy na dwa sposoby, „tasiemiec” oraz „drzewko”. Żeby wyjaśnić różnice pokaże przykładzie.

body { background-color: white; margin: 0px; padding: 0px; }
/*lub*/
body { 
	background-color: white; 
	margin: 0px; 
	padding: 0px; 
}

Poniżej układamy CSS’a jeśli chcemy tasiemce zmienne $nl i $tab pozostawiamy puste lub też pozostawiamy tak jak teraz i mamy drzewka.

$nl = "\n";
$tab = "\t";
$string = '';
foreach( $css_array as $klasa => $body ) {
	$string .= $klasa.' {'.$nl;
	foreach( $body as $att => $value ) {
		$string .= $tab."$att: $value;".$nl;
	}
	$string .= "}\n";
}

Oczywiście kod ma parę niedociągnięć. Nie obsługuje takich tagów jak import, charset ale może to kiedy indziej. Czy komuś się to przyda nie wiem, jak w tytule był robiony w chwili wolnego czasu. Będzie chociaż troche ładnie poukładane.

Kod poskładany w całość:

<?php
$file = '/sciezka/do/pliku.css';
$css = file_get_contents($file);
$css = str_replace(array("\n", "\r", "\t"), '', $css );
 
/* wyrazenie regularne do wyszukiwania klas */
$patern_main = '~([\.|#]?[^{]*)[\s]*\{([^}]*)\}~i';
/* wyrazenie regularne do wyszukiwania atrybutow w klasach */
$patern_css = '~([^\:\;\s]+)\s*:\s*([^\;\s]+)~i';
/* wyrazenie regularne do wyszukania i usuniecia komentarzy */
$patern_comments = '~\/\*[^*]*\*+([^/*][^*]*\*+)*\/~i';
 
/* usuwanie komentarzy */
$css = preg_replace($patern_comments, '', $css);
 
/* wyszukiwanie klas */
preg_match_all($patern_main, $css, $match);
 
$css_array = array();
foreach (array_keys($match[0]) as $key) {
	$css_match = array(); //czyscimy tablice w przypadku pustej klasy
	$css_body = trim( $match[2][ $key ] );
	if( $css_body != '' ) { //jesli definicja klasy jest pusta to ja opuszczamy
		preg_match_all($patern_css, $css_body, $css_match);
		$css_array[ trim( $match[1][ $key ] ) ] = array_combine($css_match[1], $css_match[2]);
	} else {
		$css_array[ trim( $match[1][ $key ] ) ] = array();
	}
}
$nl = "\n";
$tab = "\t";
$string = '';
foreach( $css_array as $klasa => $body ) {
	$string .= $klasa.' {'.$nl;
	foreach( $body as $att => $value ) {
		$string .= $tab."$att: $value;".$nl;
	}
	$string .= "}\n";
}
?>

Autor wpisu: Diabl0, dodany: 11.03.2009 15:11, tagi: sql

drzewo_duzeW czasie pracy nad jednym z projektów po raz kolejny byłem zmuszony podejść do kwestii drzewa. Jednakże tym razem postanowiłem podejść do tego troszkę inaczej i zaimplementować drzewo bardziej rozbudowane niż typowe id | nazwa | parent_id. Wybór padł na tzw. metodę Depesza oraz próbę jej implementacji pod MySQL.

Nie będę się tutaj rozwodził nad zasadą działania czy listą wad/zalet gdyż zostało to już dość dobrze opisane:

Poszukiwania “gotowca” dla MySQL nie przyniosły skutku. Jedyne co znajdywałem to rozwiązania dla PostgreSQL i ewentualnie prośby o gotowca dla MySQL. Tak więc ambicjonalnie (bez żadnego wcześniejszego doświadczenia w programowaniu baz danych) postanowiłem napisać (tudzież przepisać) coś takiego.

Diagram tabel i relacji

Diagram tabel i relacji

W czasie pracy/nauki bazowałem na wersji pod PostgreSQL napisanej przez Michała Szych - Implementacja metody 4. Dla uzupełnienia tutaj znajdziecie działający i sprawdzony zrzut z bazy danych (w kilku miejscach jest inny niż ten zaprezentowany na stronie).

Całość rozwiązania bazuje na kluczach obcych oraz triggerach przy dodawaniu i modyfikacji kategorii (diagram po prawej).

Klucze obce służą do kaskadowego usuwania kategorii - usuwając pojedyńczy rekord automatycznie usuwane są wpisy powiązań jak też ewentualni potomkowie (relacja wewnętrzna parent_id -> id).

Najważniejsze jednak zawarte jest w wyzwalaczach.

-- ----------------------------
-- Trigger structure for tree_insert
-- ----------------------------
DELIMITER ;;
CREATE TRIGGER `tree_insert` AFTER INSERT ON `tree` FOR EACH ROW BEGIN

INSERT INTO tree_pos (tree_id, parent_id, depth) VALUES (NEW.id, NEW.id, 0);

IF NEW.parent_id IS NOT NULL THEN
INSERT INTO tree_pos (tree_id, parent_id, depth)
SELECT NEW.id, parent_id, depth + 1 FROM tree_pos WHERE tree_id = NEW.parent_id;
END IF;

END;;
DELIMITER ;

Odpowiada on za stworzenie wszystkich połączeń przy dodawaniu nowej kategorii.  Jest on na tyle prosty że chyba nie trzeba tego tłumaczyć.

Drugi wyzwalacz - ON UPDATE jest już natomiast znacznie bardziej skomplikowany. Odpowiada on bowiem za przebudowywanie listy połączeń przy przenoszeniu kategorii. Sam trigger poniżej, w komentarzach znajduje się także wyjaśnienie co i dlaczego.

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

Autor wpisu: SongoQ, dodany: 09.03.2009 23:14, tagi: php

Moja dzisiejsza notatka będzie pewnym przykładem rozwiązania problemu, z którym często się spotykam, ale z pewnych ograniczeń, postanowiłem to rozwiązać trochę w inny sposób. Metoda ta oczywiście znajduje się poniżej.

Często z baz danych usuwamy rekordy, które są odzwierciedleniem obrazków na dysku, ale jak usunąć rekord a wraz z nim plik zależny, który nie zawsze znajduje się fizycznie na tej samej maszynie? Dlaczego akurat uzależniać, usuwanie pliku od bazy danych? Powodów może być kilka, np wiele powtarzających się mechanizmów obsługujących ten sam mechanizm. Wydawać by się mogło że refaktoryzacja kody to załatwi ale niestety różnie to bywa i często do ideału daleka droga.

Przykład z życia – obliczamy punkty fotek w funkcji plpgsql a następnie niepotrzebne rekordy są usuwane wraz z fotkami zapisanymi fizycznie na dysku, oczywiście PHP w tym momencie nie ma dostępu do tego rekordu.

Konkrety: PostgreSQL i język proceduralny

Baza danych taka jak PostgreSQL, umożliwia obsługę wielu proceduralnych języków np. :

  • pl/pgsql
  • pl/tcl
  • pl/python
  • pl/perl
  • pl/sh

Dla przykładu wykorzystam pl/python

Załóżmy, że mamy tabele images z polami, id, created_at, name, path, gdzie path bedzie scieżka na naszym serwerze

CREATE TABLE images
(
   id serial,
   created_at timestamp without time zone DEFAULT NOW(),
   image_name character varying(100) NOT NULL,
   path character varying(255) NOT NULL,
   CONSTRAINT pkey_id PRIMARY KEY (id)
);

Usuwając rekord z tabeli images chcemy usunąć plik fizycznie z dysku. Pierwszą rzeczą jest dodanie obsługi języka plpython, oczywiście z poziomu superuser’a.

CREATE LANGUAGE plpythonu;

Pozostaje nam tylko stworzenie triggera, który wywoła funkcję usuwania pliku z dysku po usunięciu rekordu.

CREATE OR REPLACE FUNCTION delete_images()
RETURNS trigger AS
'
  import os
  os.unlink( TD["old"]["path"] )

  return "OK"
'
LANGUAGE 'plpythonu' VOLATILE;

CREATE TRIGGER delete_images_trigger
BEFORE DELETE
ON images
FOR EACH ROW
EXECUTE PROCEDURE delete_images();

Pozostaje nam jeszcze dodać przykładowy rekord a następnie go usunąć

INSERT INTO images (name, path) values ('test', '/home/songoq/pg_test/a')

Dla testu tworzymy plik na dysku

touch /home/songoq/pg_test/a

Usuwamy rekord

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.