Jak w Google Analytics badać kliknięcia w linki.

Zagadnienie
Posiadamy serwis internetowy, na którym mamy linki wychodzące prowadzące do innych serwisów. W celach optymalizacyjnych, statystycznych lub ze zwykłej ciekawości chcielibyśmy zliczać kliknięcia w takie linki.

Rozwiązanie
Do rozwiązania tego zagadnienia wykorzystamy statystyki Google Analytics, które są nie dość, że są darmowe to również są zdecydowanie najlepsze na rynku.

Aby liczyć kliknięcia stworzymy wirtualną stronę, którą następnie w panelu ustawimy jako goal/cel. W ten sposób będziemy mieli w łatwy i przejrzysty sposób dostęp do szczegółowych statystyk kliknięć w ten link.

1. W kodzie serwisu musimy umieścić kod śledzący Google Analitycs

	<script type="text/javascript">
	var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
	document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
	</script>
	<script type="text/javascript">
	try {
	var pageTracker = _gat._getTracker("UA-TUTAj_TWOJ-ID");
	pageTracker._trackPageview();
	} catch(err) {}</script>

Należy pamiętać, że w kodzie HTML, powyższy kod musi być powyżej kodu trackera z punktu kolejnego.

2. W linku, który chcemy mierzyć, wstawiamy wywołanie poprzez zdarzenie javascript onclick skryptu GA pageTracker._trackPageview

onclick="javascript:pageTracker._trackPageview('/link/UNIKALNA-NAZWA')"

W miejsce '/link/UNIKALNA-NAZWA’ wpisz, tekst, który ma się wyświetlić jako nazwa wirtualnej strony w panelu Google Analitycs. Dobrym zwyczajem jest dodanie prefixu (w przykładzie 'link’). Jest to przydatne gdy w przyszłości chciałbyś wykluczyć statystyki wirtualnych stron w ogólnym raporcie GA.

3. Dodajemy nowy goal/cel

google analitycs goal

Należy pamiętać aby wartość pola 'Goal URL/Adres URL celu’ była taka sama jak wcześniej zdefiniowana wirtualna strona i zaczynała się od slasha '/’.

To wszystko. Można teraz zrobić kilka testowych kliknięć i należy poczekać 24h gdyż statystyki GA aktualizowane są raz dziennie.

4. Aby podglądnąć statystyki kliknięć należy udać się do panelu GA i przejść do zakładki 'Goals/Cele’, mamy tam zestaw gotowych raportów. Jeżeli mamy zdefiniowanych więcej niż jeden goal/cel, to po kliknięciu na 'Goal conversion/Konwersja na cel’ możemy ograniczyć raport do konkretnego celu. Statystyki naszej wirtualnej strony dostępne są również w zakładce 'Content/Zawartość’ gdyż, dla GA jest ona normalną stroną serwisową.

Dla jednego serwisu możemy zdefiniować wiele goal’i, w panelu możemy badać ich statystyki łącznie lub dla każdego osobno.

Podsumowanie
Metoda opisana powyżej działa zarówno dla linków wychodzących jak i dla linków wewnętrznych, wywołanie javascript:pageTracker._trackPageview(’/link/XXX’) może być zaszyte zarówno w javascript jak i we flashu. Prawdę mówiąc metodą tą można mierzyć kliknięcie w dowolny element serwisu (np. blok tekstowy), jednak do tego lepiej wykorzystać 'heat mapy’, ale o tym innym razem.

Jak w XSLT usunąć puste węzły XML’a?

Problem:
Mamy XML:

<root>
  <category>Biznes</category>
  <category>   </category>
  <category></category>
  <category>Hobby</category>
</root>

Poprzez transformacje XSLT, chcemy usunąć puste węzły kategorii.

Rozwiązanie:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:template match="/">
		<root>
		<xsl:for-each select="category">
		  <xsl:if test="normalize-space(.)=''">
	  	<category>
	  		<![CDATA[<xsl:value-of disable-output-escaping="yes" select="normalize-space(.)" />]]>		
	  	</category>	
		  </xsl:if>
		</xsl:for-each>
		</root>
	</xsl:template>
</xsl:stylesheet>

Wyjaśnienie:

  • Instrukcja match=”/” wybiera wszystkie węzły XML’a
  • Instrukcja for-each iteruje po wszystkich węzłach o nazwie 'category’ (selekcja wynika z warunku: select=”category”)
  • Funkcja normalize-space() usuwa białe znaki z początku i końca węzła. (w przypadku białych znaków w środku stringu, zamienia je na pojedynczą spację)
  • Instrukcja if sprawdza czy wartość węzła równa jest pustemu stringowi.
  • W przypadku powodzenia wartość węzła wstawioana jest w tag <category>
  • Instrukcja disable-output-escaping=”yes” powoduje, że znaki szczególne takie jak <,>,& nie zostaną wyescapowane (zamienione na &lt;,&gt;,&amp;)
  • Tagi <![CDATA[…]]> powodują, że niewyscapowane znaki <,>,& nie spowodują błędu walidacji XML’a

Sortowanie względem pola nie będącego w DISTINCT ON

Problem:
Chcemy wyświetlić jedynie unikalne rekordy względem konkretnego pola/pól i posortować innym polu. W naszym przykładzie chcemy otrzymać zestawienie id_user – ostatnie logowanie

Posiadane dane:
Tabela z datami logowań użytkowników aplikacji w tabeli postaci:

CREATE TABLE logs(
id SERIAL PRIMARY KEY, -- unikalny klucz główny
id_user INTEGER, -- klucz obcy tabeli użytkowników
date TIMESTAMP -- data logowania
);

Przykładowe rekordy:

INSERT INTO logs(id_user,date) VALUES(1,'2008-01-01');
INSERT INTO logs(id_user,date) VALUES(1,'2008-01-01');
INSERT INTO logs(id_user,date) VALUES(4,'2008-01-01');
INSERT INTO logs(id_user,date) VALUES(2,'2008-01-01');
INSERT INTO logs(id_user,date) VALUES(3,'2008-04-01');
INSERT INTO logs(id_user,date) VALUES(7,'2008-04-01');
INSERT INTO logs(id_user,date) VALUES(1,'2008-05-01');
INSERT INTO logs(id_user,date) VALUES(4,'2008-05-01');
INSERT INTO logs(id_user,date) VALUES(5,'2008-06-01');
INSERT INTO logs(id_user,date) VALUES(5,'2008-08-01');
INSERT INTO logs(id_user,date) VALUES(7,'2008-10-01');

Błędne zapytanie nr 1:
W pierwszym momencie wielu początkujących programistów pomyśli o zapytaniu:

SELECT
 DISTINCT ON (id_user)
 *
FROM
 logs
ORDER BY
 date DESC;

Niestety nie jest to poprawne zapytanie, postgres wyrzuci błąd:

ERROR:  SELECT DISTINCT ON expressions must match initial ORDER BY expressions

Oznacza to, że pola, które zawiera klauzula ORDER BY muszą się znaleźć na początku klauzuli DISTINCT ON.
W tym przypadku poprawne zapytanie musiało by zawierać DISTINCT ON (date, id_user).
Niestety wtedy nie dostaniemy pożądanego rezultatu (unikalność po kolumnie id_user) gdyż badana będzie
unikalność względem dwóch pól.

Błędne zapytanie nr 2:

SELECT
 *
FROM
 (SELECT
  DISTINCT ON (id_user)
  *
 FROM
  logs
) AS sub
ORDER BY
 sub.date DESC;

Idąc tropem unikalności można użyć podzapytania, które zwraca unikalne względem id_user rekordy a dopiero później, zwrócone przez nie rekordy posortować. Powyższe zapytanie wykona się i zwróci pewne rezultaty.

id id_user date
11 7 2008-10-01 00:00:00
9 5 2008-06-01 00:00:00
8 4 2008-05-01 00:00:00
5 3 2008-04-01 00:00:00
2 1 2008-01-01 00:00:00
4 2 2008-01-01 00:00:00

Niestety nie będą one do końca poprawne gdyż podzapytanie DISTINCT pozostawia pierwszy napotkany rekord zawierający unikalną wartość id_user. Zatem jeżeli dane nie są posortowane względem id_user i daty chronologicznie to uzyskamy wyniki nieprawidłowe. W naszym przypadku id_user=1 została zwrócona data=2008-01-01 gdyż jest ona bliżej początku niż rekord z datą=2008-05-01.

Rozwiązanie:

SELECT
 *
FROM
 (SELECT
  DISTINCT ON (sub2.id_user)
  sub2.*
 FROM
  (SELECT
  *
  FROM
  logs
  ORDER BY
  id_user ASC,
  date DESC
  ) AS sub2
) AS sub
ORDER BY
 sub.date DESC;

W podzapytaniu sub2 sortujemy dane po id_user i date malejąco tak aby każdy id_user na czele miał rekord z datą najpóźniejszą. Takie dane przekazujemy zapytaniu wcześniejszemu, które najpierw wyciąga rekordy unikalne względem kolumny id_user a następnie tak ograniczony zbiór sortuje względem kolumny date. Jako że dostarczyliśmy dane posortowane otrzymane rezultaty są zgodne z prawdą:

id id_user date
11 7 2008-10-01 00:00:00
10 5 2008-08-01 00:00:00
7 1 2008-05-01 00:00:00
8 4 2008-05-01 00:00:00
5 3 2008-04-01 00:00:00
4 2 2008-01-01 00:00:00

Zapytanie grupujące minimalnne wartości w grupie danych

Problem:
Chcemy wyświetlić miesięczne zestawienie liczby nowo zarejestrowanych
użytkowników mając jedynie daty ich logowań.

Posiadane dane:
Tabela z datami logowań użytkowników aplikacji w tabeli postaci:

	CREATE TABLE logs(
		id SERIAL PRIMARY KEY, -- unikalny klucz główny
		id_user INTEGER, -- klucz obcy tabeli użytkowników
		date TIMESTAMP -- data logowania
	);

Przykładowe rekordy:

INSERT INTO logs(id_user,date) VALUES(1,'2008-01-01');
INSERT INTO logs(id_user,date) VALUES(1,'2008-01-05');
INSERT INTO logs(id_user,date) VALUES(4,'2008-01-01');
INSERT INTO logs(id_user,date) VALUES(2,'2008-01-01');
INSERT INTO logs(id_user,date) VALUES(3,'2008-04-01');
INSERT INTO logs(id_user,date) VALUES(7,'2008-04-01');
INSERT INTO logs(id_user,date) VALUES(1,'2008-05-01');
INSERT INTO logs(id_user,date) VALUES(4,'2008-05-01');
INSERT INTO logs(id_user,date) VALUES(5,'2008-06-01');
INSERT INTO logs(id_user,date) VALUES(5,'2008-08-01');
INSERT INTO logs(id_user,date) VALUES(7,'2008-10-01');

Rozwiązanie:
Rejestracje można utożsamić z pierwszymi logowaniami, zatem:
Najpierw grupujemy dane według użytkownika i dla każdego id_user znajdujemy najwcześniejszą datę logowania. Następnie otrzymane dane grupujemy po roku i miesiącu.

Realizuje to podwójnie grupujące zapytanie:

SELECT 
	extract(year from date) || '-' || extract(month from date) AS month, 
	count(*) AS amount 
FROM 
	logs 
WHERE date IN (
	SELECT 
		min(date) 
	FROM 
		logs 
	GROUP BY 
		id_user 
) 
GROUP BY 
	extract(year from date) || '-' || extract(month from date)
ORDER BY
	amount DESC;

Można też ciut lepiej zoptymalizować zapytanie i operować bezpośrednio na wynikach zwróconych
przez podzapytanie:

	
SELECT 
	extract(year from sub.date) || '-' || extract(month from sub.date) AS month, 
	count(*) AS amount 
FROM 
 (
	SELECT 
		min(date) AS date
	FROM 
		logs 
	GROUP BY 
		id_user 
) AS sub
GROUP BY 
	extract(year from sub.date) || '-' || extract(month from sub.date)
ORDER BY
	amount DESC;	

W przypadku gdy zapytanie to będzie wykonywane często warto założyć index na kolumne z datą:

CREATE INDEX date_idx ON logs(date);

FirePHP – przyjazne debug’owanie aplikacji internetowych

Debugowanie to poważny problem programistów

Bez względu na to jakimi dobrymi programistami jesteśmy nigdy nie ustrzeżemy się błędów. To normalne, błędy popełnia każdy, różnica pomiędzy dobrym programistą a złym polega m. in. na szybkości wykrycia błędnego kodu i naprawienia go.

Mówiąc o błędnym kodzie nie mam na myśli jedynie błędów syntaktycznych, ale również błędów logicznych, które nie powodują widocznych error’ów a jedynie błędne działanie aplikacji. Takie błędy są dużo trudniejsze do wykrycia.

Aby wykryć błędny kod niezbędne jest zdebugowanie zmiennych, obiektów, zasobów, które wykorzystywane są w tworzonej przez nas aplikacji. Brzmi banalnie, jednak w rzeczywistości nie jest to takie łatwe gdyż:

  • po pierwsze trzeba wstawić w odpowiednie miejsca funkcje var_dump() lub print_r()
  • po drugie funkcje te nie wyświetlają przejrzyste i łatwe do zinterpretowania dane
  • po trzecie debug’owanie na ekran może spowodować błędy aplikacji, a debugowanie do pliku jest niewygodne w odczycie (szczególnie w przypadku aplikacji flash’owych lub AJAX’owych)
  • po czwarte zdarza się, że format zwracanych danych uniemożliwia swobodne dodawanie własnych danych do debugowania (np. XML, JSon).
  • po piąge po skończeniu debugowania trzeba usunąć wszystkie instrukcje debugujące

Rozwiązaniem problemu debugowania jest FirePHP

Dużo tych wad… całe szczęście jest na to rozwiązanie. FirePHP to darmowa, open source’owa biblioteka, która wraz z pluginem do Firebug’a idealnie sprawdza się w roli debugera.

FirePHP debuguje dane w konsoli Firebug’a, informacje do debugowania są umieszczone w specjalnych nagłówkach HTTP przez co 'output’ aplikacji pozostaje w nienaruszonym stanie. Rewelacja prawda?

Instalacja firePHP

Wymagania FirePHP to PHP 5.2+, Firefox z pluginem Firebug.

  • Instalujemy plugina do firefox’a Firebug
  • Instalujemy plugina do Firebug’a FirePHP
  • Ściągamy źródła FirePHP, warto zauważyć, że na stronie dostępne są informacje, jak zintegrować bibliotekę z wieloma znanymi frameworkami (CakePHP, CodeIgniter, Drupal, Kohana, ExpressionEngine, PRADO, Symfony, TYPO3, Zend Framework).
  • Integrujemy FirePHP z naszą aplikacją
  • Odpalamy stronę aplikacji w Firefox z uruchomionymi plugin’ami Firebug i FirePHP
  • Klikamy na ikonę Firebug’a a następnie przechodzimy do konsoli. W konsoli powinny być zaprezentowane zdebugowane zmienne naszej aplikacji.
  • W przypadku problemów przydać się mogą: mini tutorial how to, forum dyskusyjne o FirePHP.

Przykład działania

Pod adresem http://blog.adiasz.pl/examples/firephp/ stworzyłem prosty skrypt AJAX’owy łączący się z bazą i wykonujący prościutkie operacje. Jest zintegrowany z FirePHP, zachęcam do testów. Naprawdę warto! (Oczywiście musicie najpierw zainstalować plugin FireBug i FirePHP). Wszystkie pliki wykorzystane w tym przykładzie dostępne są tutaj.

Przykład powinien zaprezentować coś w stylu:

Na koniec, bardzo ważna informacja: po opublikowaniu projektu musicie wyłączyć debugowanie FirePHP poprzez dyrektywę:

$fp = FirePHP::getInstance( true );
$fp->setEnabled ( false );

W przeciwnym razie każdy internauta posiadający zainstalowany ten plugin będzie miał dostęp do niebezpiecznych z punktu bezpieczeństwa danych. Najlepiej w konfigu aplikacji sterować to poprzez zmienną typu boolean $development = true/false. Samych instrukcji debugujących nie trzeba kasować gdyż nie wiadomo kiedy się jeszcze przydadzą.

ps. FirePHP dostępne jest także dla innych niż PHP języków programowania aplikacji webowych: ASP, Python, Ruby.

Darmowe narzędzia dla webmasterów: statystyki serwisu

Ostatnio w ramach optymalizacji czasu i automatyzacji wykonywanych przez zemnie zadań stworzyłem mini aplikację sprawdzającą statystyki moich serwisów w wyszukiwarkach.

Aktualnie sprawdzam: Google PR, Google link, Google site, Yahoo link, Yahoo site, MSN site. Monitoringu pozycji słów kluczowych na razie nie robiłem, gdyż jest wiele tego typu dobrych i darmowych sprawdzaczy na necie – więc szkoda czasu.

Aplikacja działa tak:
* skrypty sprawdzające statystyki wywoływane są z automatycznie z cron’a, dane zapisują w bazie
* po zalogowaniu prezentowana jest lista najnowszych statystyk
* generowane są dzienne wykresy poszczególnych statystyk serwisów

Prościutkie, teraz zamiast sprawdzać ręcznie tysiące parametrów wystarczy się zalogować do panelu. Wyniki zostaną zaprezentowane szybki i w przejrzysty sposób.

Dla szerokiego internetu udostępniłem wersję uproszczoną – możliwość sprawdzenia wyżej wymienionych statystyk dla podanego adresu: zapraszam na darmowe narzędzia dla webmasterów. Nic odkrywczego, ale może komuś się przyda.

Aplikacja została napisana we framework’u: Kohana, o którym postaram się napisać niedługo, a już teraz polecam się z nim zapoznać.

Błąd SmartyPDT w Eclipse 3.3.2 i PDT 1.0.3 z JRE 1.6

Niedawno update’owałem swoje IDE, i natknąłem się na mały zgrzyt w Eclipse. Okazało się, że najnowsza wersja 0.5.4 plugin’a do Smarty’ego (SmartyPDT) nie chce działać w środowisku Eclipse PDT z zainstalowanym JRE 1.6.

Błąd

Otwarcie pliku *.tpl w domyślnym edytorze smarty’iego dawało błąd:

java.lang.IllegalAccessError?: tried to access field org.eclipse.php.internal.ui.editor.PHPStructuredEditor.isExternal from class org.eclipse.php.smarty.ui.editor.SmartyStructuredEditor?
    at org.eclipse.php.smarty.ui.editor.SmartyStructuredEditor?.doSetInput(SmartyStructuredEditor?.java:42) at org.eclipse.ui.texteditor.AbstractTextEditor?$19.run(AbstractTextEditor?.java:3003) at org.eclipse.jface.operation.ModalContext?.runInCurrentThread(ModalContext?.java:369) at 
etc...

Błąd w całości można obejrzeć tutaj. Po małym dochodzeniu okazało się, że wersja SmartyPDT 0.5.4 działa w co najwyżej 1.5 The J2SE Runtime Environment (JRE), natomiast na mojej maszynie zainstalowane jest najnowsze JRE 1.6.

Rozwiązanie

Aby SmartyPDT działało poprawnie trzeba ściągnąć nieoficjalną wersję 0.5.5, która dostępna jet tutaj. Następnie wgrać do katalogu eclipse, przejść Help->Software Updates->Manage Configuration i dać 'disable’ na wersji 0.5.4 i 'enable’ na wersji 0.5.5

Po restarcie Eclipse wszystko działa poprawnie.

Refleksja

Ciekawe dlaczego na stronie SmartyPDT nie ma słowa o tej niezgodności, oraz dlaczego w download’zie nie ma najnowszej wersji 0.5.5?

White SEO – podstawowe przykazania jak budować przyjazne strony www

Co to jest White SEO?
White SEO to zbiór technik pozwalających na optymalizacje witryn internetowych pod kontem wyszukiwarek internetowych. White SEO opiera się na czystych zasadach pozycjonowania, są to metody zgodne z zasadami tworzenia stron internetowych, są akceptowane, a nawet zalecane przez same wyszukiwarki.

Tytuł strony
Tytuł (<title>) każdej strony powinien być unikalny, krótki i zawierać esencję tego co się znajduje na stronie.

Opis strony
Pole <description> powinno być unikalne i zawierać streszczenie zawartości strony, treść z tego pola bardzo często pojawia się w wynikach wyszukiwania Google zatem tekst ten powinien przyciągać użytkownika na stronę.

Adresy URL stron
Adresy URL poszczególnych stron powinny zawierać słowa kluczowe pod którymi strona ma się pozycjonować. Adresy nie powinny być zbyt długie, powinny posiadać hierarchiczną strukturę (np. kolejne działy oddzielone slashem). Każdy adres URL powinien zawierać unikalną treść, strzeż się duplicate content.

Strony witryny
Strony witryny powinny być przejrzyste i łatwe do czytania zarówno przez człowieka jak i przez roboty, staraj się używać jak najwięcej CSS. Strony / działy powinny być zamknięte tematycznie.

Linki wewnętrzne
Linki w obrębie serwisu powinny być krótkie, tekstowe i opisujące stronę do której prowadzą. Zamiast nic nie mówiących: 'więcej’, 'następny’, podlinkuj np.: 'więcej o pozycjonowaniu’. Staraj się dobrze linkować serwis gdyż linki wewnętrzne posiadają moc.

Nagłówki
Używaj nagłówków <Hx> aby zaznaczyć ważność elementów na stronie.

Grafika
Zoptymalizuj zdjęcia i obrazki, każdy tak powinien mieć atrybut alt=”” opisujący co jest na tym obrazku, nazwy plików powinny być zwięzłe ale zawierać słowo kluczowe zdjęcia. Nawet nie masz pojęcia jak pozytywnie wpływa to na ruch organiczny z Google Images.

Promuj swoją witrynę w naturalny sposób
Staraj się prezentować na stronie aktualne, unikalne i ciekawe treści – dzięki temu użytkownicy sami, z własnej nieprzymuszonej woli będą linkować do Twojej strony. Taki naturalny (zwykle tematyczny) link jest wart więcej niż 100 linków ze śmieciowych katalogów.

W serwisach sprzedażowo-korporacyjnych dobrym kanałem promocyjnym jest ogólnoserwisowy blog z ciekawymi informacjami dotyczącymi bądź to samego serwisu lub branży.

Używaj ogólnodostępnych narzędzi monitoringu
Aby analizować statystyki odwiedzalności witryny zainstaluj Google Analytics, aby monitorować pozycje w Google dodaj witrynę do Google’s Webmaster, zbuduj Sitemap’e aby w łatwy sposób wyszukiwarki mogły indeksować Twoją witrynę.

Podsumowanie
Wyżej spisane przykazania same z siebie nie zagwarantują sukcesu Twojej witrynie, jednak z pewnością pomogą w jego osiągnięciu. Pomogą w ogólnej promocji serwisu w internecie. Wydawać się może, że opisują ogólnie znane prawdy – zgadzam się – prawdy są znane. Niestety rzadko stosowane – tym lepiej dla Ciebie gdyż możesz zdobyć przewagę nad konkurencją i zaoszczędzić pieniędzy wydawanych na 'firmę SEO’.

Interfejs a klasa abstrakcyjna

Interfejsy i klasy abstrakcyjne zostały wprowadzone do php od wersji 5.0. Konstrukcje te znacznie pomagają w uporządkowaniu struktury aplikacji, pozwalają na zachowanie kontroli typów, integralność oraz logiczną budowę aplikacji.

Dzięki wdrożeniu tych konstrukcji do aplikacji możemy w lepszy i logiczniejszy sposób odwzorować rzeczywistość a zarazem ustrzec się potencjalnych błędów, poza tym zyskujemy większą kontrolę nad modelowaną obiektową rzeczywistością.

Klasy abstrakcyjne
Klasy abstrakcyjne służą do modelowania rzeczywistości, którą chcemy przenieść do logiki swojej aplikacji. Klasa abstrakcyjna definiuje w dużej ogólności pewien podstawowy model i zachowanie obiektu.

Powiedzmy, że chcemy chcemy stworzyć wirtualne zoo. W zoo znajdować się będą różnorakie zwierzęta np: małpy, lisy, wiewiórki, motyle, pszczoły, żaby, jaszczurki.

Każde zwierzę opisywane jest przez podstawowe atrybuty jak: wiek, rozmiar oraz cechy taki jak: rodzenie, chodzenie, jedzenie.

Tworzymy zatem podstawową klasę abstrakcyjną 'Zwierze’ zawierającą abstrakcyjne metody: born, go, eat:

abstract class Animal {

	protected $age;
	protected $width;
	protected $height;

	abstract protected function born();
	abstract protected function go();
	abstract protected function eat();

	protected function setAge( $years ) {
		$this->age = $years;
	}

	protected function setSize( $width, $height ) {
		$this->width = $width;
		$this->height = $height;
	}
}

Dzięki użyciu klasy abstrakcyjnej mamy pewność, że nikt nie stworzy obiektu klasy 'Zwierze’. Z logicznego punktu jest to niemożliwe gdyż nie ma zwierzęcia 'Zwierze’ – taki byt w rzeczywistości nie istnieje gdyż jest to zbyt ogólne pojęcie.

Dodatkowo zadeklarowaliśmy metody born, go, eat jako abstrakcyjne co wymusi ich implementacje w klasach dziedziczących – to też jest jak najbardziej logiczne gdyż nie ma zwierzęcia, które nie może wykonywać tych czynności. Metody te deklarujemy jako abstrakcyjne gdyż nie znając zwierzęcia nie wiemy jeszcze jak je zaimplementować. Metody abstrakcyjne muszą być puste.

Zadeklarowaliśmy również podstawowe atrybuty ($age, $width, $height), które każde zwierze posiadać musi, oraz settery które je ustawiają. Przy setterach ustawiamy specyfikator dostępu protected, dzięki temu będą mogły być wywoływane jedynie z poziomu metod klas dziedziczących. Nie ustawiamy ich jako abstrakcyjnych gdyż ich implementacja w klasie 'Animal’ jest wystarczająca i klasy potomne będą z nich korzystać.

Tworzymy teraz klasę konkretnego zwierzęcia np. małpy:

require_once 'animal.php';

class Monkey extends Animal {

	public function born() {/*@TODO*/}
	public function go() {/*@TODO*/}
	public function eat() {/*@TODO*/}

	public function __construct( $age, $size ) {
		$this->setAge( $age );
		$this->setSize( $size[0], $size[1] );
	}
}

$monkey1 = new Monkey( 2, array( 100, 20 ) );
var_dump( $monkey1 );

Musi ona posiadać metody abstrakcyjne, w ciele klasy 'Monkey’, powinny one przybrać docelową formę. Jak widać nie ma setterów, jest za to konstruktor przyjmujący parametry obiektu i wywołuje odpowiednie settery z klasy bazowej 'Animal’.

Interfejs
Klasa abstrakcyjna jest dość mocno związana logicznie z obiektami z niej dziedziczącymi. Widać jasno, że małpa i lis są integralnie związane z definicją zwierzęcia. Interfejs natomiast jedynie narzuca klasie jakie metody powinna posiadać, interface definiuje pewne właściwości, które klasa musi spełniać. Sam w sobie nie definiuje logicznej zależności pomiędzy sobą a klasą go implementującą, definiuje natomiast powiązanie pomiędzy wszystkimi klasami go implementującymi. Agreguje klasy obiektów na których można wykonać pewne operacje.

Kontynuując przykład zoo… załóżmy, że zoo może sprzedawać i kupować zwierzęta zatem możemy stworzyć interface 'Trade’:

interface Trade {
	public function sell();
	public function buy();
}

Dodatkowo, zwierzęta podlegają ubezpieczeniom zatem tworzymy interface 'Insurance’:

interface Insurance {
	public function make();
	public function withdrawn();
}

Jedna klasa może implementować wiele interfejsów, w przykładzie zoo 'Animal’ będzie implementował zarówno 'Trade’, jak i 'Insurance’. Jak widać kolejne interfejsy nie są ze sobą związane jednak wpływają na zachowanie się obiektu zwierzęcia.

class Monkey extends Animal implements Trade, Insurance {
	public function born() {/*@TODO*/}
	public function go() {/*@TODO*/}
	public function eat() {/*@TODO*/}
	
	public function sell() {/*@TODO*/}
	public function buy() {/*@TODO*/}
	
	public function make() {/*@TODO*/}
	public function withdrawn() {/*@TODO*/}
	

	public function __construct( $age, $size ) {
		$this->setAge( $age );
		$this->setSize( $size[0], $size[1] );
	}
}

Ale przecież w zoo są nie tylko zwierzęta ale także budynki i ludzie. Budynki mogą być odprzedawane a ludzie ubezpieczani zatem klasy reprezentujące te obiekty także powinny implementować te interfejsy.

Klasa 'People’ powinna implementować 'Insurance’ a klasa 'Building’ interfejs 'Trade’.

class People implements Insurance {
	public function make() {/*@TODO*/}
	public function withdrawn() {/*@TODO*/}
}
class Building implements Trade {
	public function sell() {/*@TODO*/}
	public function buy() {/*@TODO*/}
}

Widzimy zatem, że klasy reprezentujące zupełnie różne obiekty (zwierzęta, budynki) w pewnym zakresie zachowują się tak podobnie (można je kupić i sprzedać) zatem implementują interface 'Trade’.

Dzięki zapisowi tych zależności nasza aplikacja nabiera logicznego kształtu a dodatkowo dzięki parserowi zyskujemy darmową walidację, gdyż w przypadku braku metody z interfejsu php zwróci błąd – zatem nie zapomnimy o jej implementacji.

WP-Cache – rozwiązania problemów z semget i działaniem

WP-Cache to bardzo przydatny plugin do Word Press’a. Plugin ten zapisuje każdą wygenerowaną stronę w postaci statycznego pliku HTML na serwerze przez co znacznie zmniejsza obciążenie serwera i bazy danych. Długość cache, jak i reguły opisujące pliki/strony, które mają być cache’owane można ustawić z poziomu panelu WP.

Ostatnio zetknąłem się z dwoma problemami w użytkowaniu tego plugina, poniżej rozwiązania:

Problem 1:
Za każdym razem, gdy strona generowana jest po raz pierwszy pojawia się komunikat w górze strony:

Warning: semget() failed for key 0x152b: Permission denied in /XXX/wp-content/plugins/wp-cache/wp-cache-phase2.php on line 98

Rozwiązanie:
Trzeba zedytować plik: wp-content/plugins/wp-cache/wp-cache-phase2.php w linii 98:
zamienić:

$mutex = sem_get($sem_id, 1, 0644 | IPC_CREAT, 1);

na

$mutex = @sem_get($sem_id, 1, 0644 | IPC_CREAT, 1);

Znak @ przed funkcją php blokuje komunikaty o błędach, spowodowanych daną funkcją.

Alternatywą tego rozwiązania jest dopisanie

ini_set( 'display_errors', 0 );

w pliku wp-config.php W tym wypadku zablokujemy wyświetlania się komunikatów o błędach globalnie w całym Word Press’ie.

Problem 2:
WP-Cache jest poprawnie zainstalowane, włączone, jednak strony się nie cache’ują.

Sprawdz czy w katalogu wp-content znajduje się plik

!advanced-cache.php

Jeżeli tak to oznacza, że WP-Cache ma źle podlinkowany plik wp-cache-phase1.php

Rozwiązanie:
Najpierw należy skasować plik !advanced-cache.php.

Następnie przypadku gdy mamy dostęp ssh trzeba z poziomu katalogu wp-content wykonać polecenie unix’owe, które stworzy link symboliczny do tego pliku wykorzystywany przez skrypty WP:

ln -s   plugins/wp-cache/wp-cache-phase1.php advanced-cache.php

Gdy nie mamy ssh musimy zainstalować wtyczkę jeszcze raz (zdeaktywować i aktywować).