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: 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
Autor wpisu: Athlan, dodany: 09.03.2009 16:21, tagi: php, mvc
Powstało masę artykułów na temat MVC, temat staje się naprawdę oklepany. Postanowiłem zebrać wszystkie informacje w jedno miejsce i streścić je w jednym artykule uzupełniając go o informacje, które nabyłem z własnego doświadczenia oraz zwracając uwagę na najistotniejsze informacje.
Czym jest model
Model to jedna z warstw wzorca projektowego MVC, który odpowiada logikę biznesową, czyli pozyskiwanie oraz modelowanie danych pozyskanych ze źródła danych. Na samym wstępie brzmi to bardzo abstrakcyjnie. W myśl architektury MVC, dostęp do modelu powinien mieć tylko kontroler, a w żadnym wypadku widok. Dodatkowo model musi pobrać i modelować dane w taki sposób, aby można było go ewentualnie wymienić bez jakiejkolwiek ingerencji w kontroler, a co za tym idzie – widok. Niezależnie od tego, z jakiego źródła informacji korzysta (pliki tekstowe, bazy danych, pliki XML) kontroler powinien otrzymać maksymalnie zbliżone dane podczas wymiany źródła informacji.
Model != baza danych
Często spotykam się z definicją modelu jako źródłem połączenia i wykonywania zapytań do serwera bazy danych. Otóż nie jest to prawdą. Według ideologii MVC model powinien być jedynie pośrednikiem między warstwą aplikacji przeznaczoną do połączenia do bazy danych, wykonywania zapytań itp., a kontrolerem. Dodatkowo powinien pomóc kontrolerowi w zbudowaniu zapytania do źródła informacji (pobranie danych na podstawie kryteriów), zmodelować je i zwrócić. Dlaczego model nie jest połączeniem do bazy danych? Jeżeli model potraktujemy jako pośrednika między kontrolerem a źródłem danych, ma on prawo wybrać dowolny sposób uzyskania żądanych informacji. Wcale nie oznacza to, że model musi używać baz danych, ale może użyć plików XML lub API udostępniane przez konkretny serwis (np. YouTube)
Wymienialność modeli i modelowanie danych
Modelowanie informacji jest to dostosowanie ich do użytku przez kontroler. Zazwyczaj jest to przekazywanie informacji w postaci tablic, wartości logicznych, liczb i ciągów znaków. Przykładem może być pobieranie informacji z bazy danych. Kontroler de facto nie wie skąd są pobierane dane, wie to tylko model, otrzymuje suche informacje. Jak rozumieć modelowanie danych przy projektowaniu aplikacji? Wyobraźmy sobie sytuację, że zmieniamy źródło informacji z bazy danych na pliki XML. W tym przypadku kontroler powinien otrzymać rekordy danych jako tablica o tych samych kluczach i tych samych typach danych, jak miało to miejsce przy używaniu bazy danych. Wymiana modelu odbywa się bez ingerowania w kontroler.
Przykłady modeli
Najpopularniejszym sposobem pozyskania informacji jest połączenie do bazy danych i pobieranie (reprezentowanie) ich na różnoraki sposób. Doskonale wyjaśnia to tekst znajdujący się w wikipedii:
Autor wpisu: Zyx, dodany: 07.03.2009 15:25, tagi: php
Autor wpisu: SongoQ, dodany: 07.03.2009 10:14, tagi: php, symfony
Szybkie serwisy, zoptymalizowane pod względem zużycia pamięci i ilości odwołań do bazy – czasami mogą być kluczowym elementem w powodzeniu naszego projektu. Chciałbym przedstawić jedną z takich możliwości, czyli użycie w Symfony widoków baz danych. Przykład można zastosować w bazach: PostgreSQL, MySQL, Oracle, itd. Najważniejszym elementem jest to, czy baza danych obsługuje widoki.
Dla przykładu użyję tabeli użytkownik i grupa.
schema.yml
propel: groups: id: { type: integer, required: true, primaryKey: true, autoincrement: true } owner_id: { type: integer, foreignTable: users, foreignReference: id, index: true } last_user_id: { type: integer, foreignTable: users, foreignReference: id, index: true } name: { type: varchar, size: 80, index: true } routing_name: { type: varchar, size: 80, index: true, uniq: true } description: { type: varchar, size: 400 } created_at: { type: timestamp } user_count: { type: integer, index: true } position_rank: { type: integer } users: id: { type: integer, required: true, primaryKey: true, autoincrement: true } login: { type: varchar, size: 15, index: true, uniq: true } password: { type: varchar, size: 255 } name: { type: varchar, size: 255 } created_at: { type: timestamp } updated_at: { type: timestamp } last_login_at: { type: timestamp } last_request_at: { type: timestamp } group_id: { type: integer, foreignTable: groups, foreignReference: id, index: true }
Załóżmy, że mamy template w którym pokazujemy 20 grup według ilości użytkowników, w której wyświetlamy: nazwę grupy wraz z linkiem do profilu grupy, właściciela grupy, ostatniego dodanego użytkownika do tej grupy i ilość użytkowników w grupie. Stosując klasyczne wykorzystanie modelów Propela, dostaniemy modele grup z zależnymi 2 modelami użytkowników – właściciel i ostatnio dodany użytkownik. Od razu widać że większość danych jest zbędna, a co z tym idzie wykorzystanie pamięci będzie większe i czas zwracania danych z bazy będzie dłuższy.
Definiowanie widoku (views) w schema
Aby rozdzielić pliki w którym są definicje tabel i widoki, warto utworzyć osobny plik w którym zapiszemy naszą definicje widoku.
config/views_schema.yml
propel: groups_view: _attributes: { skipSQL: true, readOnly: true } id: { type: integer } name: { type: varchar } routing_name: { type: varchar } user_count: { type: integer } owner_name: { type: varchar } last_user: { type: varchar }
Kolejnym krokiem jest przygotowanie SQL z definicją naszego vidoku. Dobrym rozwiązaniem jest by każdy widok umieszczać w osobnym pliku i dodawać do sqldb.map, w celu zbudowania widoku z automatu (nie musimy już ręcznie wywoływać SQL).
Tworzymy plik sql
data/sql/views/groups_view.sql
CREATE OR REPLACE VIEW groups_view AS SELECT groups.id, groups.name , groups.routing_name, groups.user_count, owner.login AS owner_name, last.login AS last_user FROM groups JOIN users owner ON owner.id = groups.owner_id JOIN users last ON last.id = groups.ast_user_id;
Dodajemy widok do sqldb.map
Edytujemy plik sqldb.map i dodajemy nową pozycję
data/sql/sdldb.map