devblog, portfolio

movie producer, zend framework, php, jquery pluginy



Programowanie obiektowe w PHP #1

Każdy kto rozpoczął przygodę z PHP, prędzej, czy później zainteresuje się programowaniem obiektowym. Przyznaję, że ja, kiedy opanowałem umiejętność pisania proceduralnego, nie miałem zbyt wielkiej ochoty na uczenie się czegokolwiek o programowaniu obiektowym. Dopiero kiedy zwykłe funkcje przestały mi wystarczać postanowiłem zobaczyć co to jest to OOP. Wystarczył mi jeden przykład, abym przekonał się, że jest to naprawdę przydatne, choć na początku wydawało mi się bardzo trudne. W rzeczywistości programowanie obiektowe nie jest takie trudne.

Zapraszam do lektury pierwszej części kursu!

Każdy kto rozpoczął przygodę z PHP, prędzej, czy później zainteresuje się programowaniem obiektowym. Przyznaję, że ja, kiedy opanowałem umiejętność pisania proceduralnego, nie miałem zbyt wielkiej ochoty na uczenie się czegokolwiek o programowaniu obiektowym. Dopiero kiedy zwykłe funkcje przestały mi wystarczać postanowiłem zobaczyć co to jest to OOP. Wystarczył mi jeden przykład, abym przekonał się, że jest to naprawdę przydatne, choć na początku wydawało mi się bardzo trudne. W rzeczywistości programowanie obiektowe nie jest takie trudne.

Co to jest OOP?

OOP, czyli dosłownie Obiektowo Orientowane Programowanie (ang. Obejct-Oriented Programming), to sposób tworzenia oprogramowania polegający na tworzeniu klas, czyli pewnego rodzaju pojemników na funkcje (choć z czasem dowiemy się, że możliwości klas wykraczają daleko poza pełnienie roli „pojemników”). Dzięki takiemu podejściu skracamy czas bezpośredniego tworzenia aplikacji. Najpierw musimy poświęcić trochę czasu na stworzenie odpowiednich klas, które obsłużą naszą aplikację, ale jeśli zrobimy to dobrze, to będziemy je mogli wykorzystywać w wielu innych projektach.

Teraz więc nauczę Was na prostym przykładzie co to są klasy, a później zajmiemy się zasadami ich jak najlepszego tworzenia.

Hello world!

Aby pokazać jak przydatne może być stworzenie klasy, najpierw napiszemy prostą stronę, która będzie wyświetlała odpowiednio przetworzony tekst, korzystając z programowania proceduralnego, a następnie to samo osiągniemy korzystając z OOP.

listing 1

Teraz załóżmy, że mamy taki kod powtórzony w pliku 1000 razy. Wszystko działa bardzo sprawnie i jesteśmy zadowoleni. Uśmiech znika z twarzy, gdy okazuje się, że tekst miał być wyświetlony małymi literami – zamiast funkcji strtoupper, powinniśmy użyć strtolower. Tragedia! Edycja kodu zajmie nam mnóstwo czasu, a na dodatek w innych plikach także popełniliśmy ten błąd. Przy krótkim kodzie możemy skorzystać z funkcji „zastąp”, która jest dostępna we wszystkich edytorach kodu, a nawet notatnikach systemowych. Kiedy jednak chodzi o kod, który zajmuje więcej miejsca, może pojawić się problem. Pomijamy już nawet kwestię rozmiaru pliku, który przy wielokrotnym powtarzaniu tego samego, długiego kodu będzie dość duży.

Teraz pokażę jak uzyskać ten sam efekt korzystając z OOP:

listing 2

Teraz wystarczy zmodyfikować wnętrze funkcji modyfikuj należącej do klasy Tekst. Widzimy jednak, że nie jest to zbyt elastyczna, jeśli tak to można nazwać, aplikacja.

Jak stworzyć dobrą klasę?

Aby tworzyć dobre klasy, musimy poznać zasady ich tworzenia, możliwości OOP i nabyć trochę wprawy. Zacznę od tego, że elementy klasy, czyli wszystkie zmienne i funkcje mogą mieć jeden z trzech modyfikatorów widzialności: public, private, protected. Są to pewnego rodzaju definicje tego, gdzie z danej zmiennej czy metody można korzystać i, w przypadku zmiennych, czy można je modyfikować i gdzie.

Public

Pierwszy modyfikator – public – pozwala na nieograniczone wykorzystanie, zarówno we wnętrzu klasy jak i poza nią. Jeśli pod nim kryje się zmienna, to możemy modyfikować ją z dowolnego miejsca wewnątrz i poza klasą.

Private

Drugi modyfikator – private – pozwala na modyfikowanie i korzystanie z kryjących się pod nim elementów tylko wewnątrz klasy. Często stosuje się praktykę dodawania przed nazwami zmiennych i funkcji, które są prywatne znaku _, co pozwala nam na szybsze zorientowanie się, że coś jest nie tak, jeśli użyjemy danej metody poza ciałem klasy. Elementy z dostępem private nie mogą być dziedziczone. (O dziedziczeniu powiem w późniejszej części artykułu.)

Protected

Ostatni modyfikator dostępu – protected – pozwala działa dokładnie tak samo jak private, jednak kryjąca się pod nią zmienna czy metoda może być dziedziczona.

OOP w praktyce

Aby lepiej pokazać jak korzystać z OOP w PHP, stworzymy zestaw małych klas do zarządzania grupami uczniów. Nie będzie to oczywiście profesjonalna aplikacja, którą będzie można wykorzystać, jednak bardzo dobrze pokaże istotę programowania obiektowego.

Tworzymy pierwszą klasę

listing 3

Stworzyliśmy teraz klasę o nazwie Grupa. Posiada ona dwie zmienne chronione (protected) i dwie funkcje publiczne (public). Działanie klasy jest opisane w komentarzach kodu. Zagadką może jeszcze być zmienna $this. Jest to zmienna pre-definiowana – znaczy to, że próba przypisania do niej jakiejś wartości może zakończyć się błędem. $this jest zmienną służącą do wskazywania na to, że chcemy skorzystać z zasobów danej klasy – zmiennej lub funkcji - wewnątrz tej klasy.

Stworzona przez nas funkcja dodajUcznia zwraca właśnie tę pre-definiowaną zmienną $this. Dzięki temu możemy ograniczyć później ilość tworzonego kodu, ponieważ, aby wywołać kolejną funkcję, wystarczy dodać operator odwołania do elementów klasy („->”) - nie musimy po raz kolejny zaczynać od uchwytu obiektu.

Ale wróćmy do naszej klasy Grupa. Teraz jest to zwykły „pojemnik” na dane. W dodatku bardzo prosty. Powinniśmy więc stworzyć bardziej szczegółowy zbiór danych o grupie i studentach (uczniach). Zaczniemy od rozbudowania zbioru danych o grupie.

listing 4

Na powyższym listingu zastąpiliśmy zmienną $_nazwa tablicą $_daneGrupy, a także dodaliśmy nowe metody: ustawDane i pobierzDane. Zmodyfikowaliśmy także lekko konstruktor. Klasa jest już bardziej elastyczna, ponieważ możemy sami decydować o tym, jakie dane o grupie wprowadzimy.

Teraz należałoby rozszerzyć dane o uczniach. Od razu przychodzi do głowy pomysł stworzenia wielowymiarowej tablicy z uczniami. Ale nie zrobimy tak. Uczeń to zupełnie co innego niż grupa. Należy mu się więc osobna klasa, która będzie magazynowała i obsługiwała dane o nim. Stwórzmy ją:

listing 5

Mamy już prosty magazyn danych. Nie jest to jednak dobry mechanizm. Co by się stało, gdybyśmy użyli metody ustaw dla istniejącego klucza? Dane zostałyby nadpisane. Jednak nie zawsze jest to pożądane. Dlatego dodamy kolejny parametr do tej metody. Jeśli nie będziemy chcieli, aby dane były nadpisywane, zmienimy wartość dla tego parametru na false.

Co do metody pobierz, to wydawałoby się, że nie ma tutaj nic do poprawienia. A jednak znajdzie się :) Może się zdarzyć, że ktoś, kto korzysta z tej klasy, poda nieistniejący w tablicy klucz. Wtedy powinien otrzymać jakieś dane alternatywne. Kolejny parametr tej funkcji będzie zawierał właśnie dane, które zostaną zwrócone, w razie gdyby w tablicy nie istniały dane oznaczone takim kluczem.

Poprawiona klasa wygląda tak:

listing 6

Mamy już więc za sobą stworzenie prostej klasy do obsługi uczniów i grupy. Teraz pozostało nam dostosować klasę Grupa, tak żeby współpracowała ona z klasą Uczen.

listing 7

Jak widzimy zmieniła się tylko metoda dodajUcznia (i trochę typy danych przechowywanych w zmiennych i zwracanych przez funkcje). Wszystko jest opisane w komentarzach kodu.

Teraz wykonamy skrypt, który zaprezentuje możliwości naszych klas (oczywiście na jego podstawie możecie przetestować ich wcześniejsze).

listing 8

Zadanie

1. Stwórz skrypt, który będzie dodawał do grupy wielu uczniów. Niech klucze zamiast tekstowych (nazwa, rok_urodzenia) będą liczbami (0,1,...). Aby nie pomylić się, pod jaką liczbą kryje się imię i nazwisko, a pod jaką rok urodzenia itp. stwórz odpowiednie stałe (o stałych przeczytasz w artykule Stałe w PHP).

2. Rozszerz klasę Grupa o możliwość dodania nauczycieli i innych przedmiotów. Może się już domyślasz jak masz wykonać to zadanie. Jeśli nie, to podpowiem, że oprócz poszerzania klasy Grupa, musisz także stworzyć dwie kolejne klasy: Nauczyciel i Przedmiot.

Teraz wyobraźmy sobie, że tworząc obiekty Grupa dla kilku klas o profilu z rozszerzoną fizykę, musimy dodawać do każdego obiektu te same przedmioty (mam nadzieję, że wykonałeś/aś zadanie drugie ;). Oczywiście możemy stworzyć obiekty typu Przedmiot w tablicy i dodawać je w pętli do każdej z klas. Jest to podejście skuteczne, ale niezbyt profesjonalne. Tutaj właśnie powiemy o dziedziczeniu.

Dziedziczenie, to w skrócie otrzymanie od klasy, po której się dziedziczy wszystkich elementów. Do takiej klasy dziedziczącej możemy dodać swoje elementy (metody, zmienne), a także modyfikować działanie i parametry tych dziedziczonych.

Stwórzmy więc klasę, którą nazwiemy Grupa_MatFiz. Będzie ona dziedziczyła po klasie Grupa (w mojej wersji klasy Grupa jest jedynie obsługa przedmiotów – nie zrobiłem pracy domowej do końca ;).

listing 9

listing 10 - klasa Przedmiot

Jak widzisz, dziedziczenie bardzo upraszcza programowanie obiektowe. Teraz tworząc w PHP magazyn danych o klasach matematyczno-fizycznych, wystarczy, że będziemy tworzyć obiekty typu Grupa_MatFiz, a odpowiednie przedmioty i nazwy grup będą ustawiały się automatycznie.

Zadanie

1. Rozbuduj wszystkie klasy stworzone w tym odcinku kursu o odpowiednie metody kasujące dane. Podpowiedź: skorzystaj z pre-definiowanej funkcji php unset.

2. Napisz prostą aplikację wykorzystującą stworzone klasy. Swoje klasy możesz wysłać na adres kontakt@kacperkolodziej.com. Pamiętaj o podaniu swojego imienia i nazwiska lub nicku. Wszystkie prace, które wiążą się z zaprezentowanymi w tym artykule zagadnieniami, zostaną tutaj zamieszczone.

lisek Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1

2 sprawy:
-czemu właściwie używasz print zamiast echo?
-funkcje nie powinny korzystać wewnątrz siebie z takich funkcji jak print, echo itp, zamiast tego return. a jeśli funkcja ma wyświetlić jedno a zwrócić co innego, to robi się przekazywanie argumentów przez referencję, czyli w deklaracji funkcji poprzedzasz zmienne znakiem &, wtedy funkcja będzie mogła zmieniać tą zmienną a nie jej lokalną kopię zrobioną na potrzeby funkcji. A czemu return, a nie echo? Wyobraź sobie, że nagle to co do tej pory wyświetlało się na ekranie, teraz musisz wrzucić także do mailingu. I mnóstwo kodu do poprawki ;)

Kacper Kołodziej www Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30

1. print użyłem z przyzwyczajenia.
2. Tu się nie zgodzę. Funkcja (czy też metoda) powinna robić to, co chce jej twórca. Tutaj można było po prostu zwrócić wynik, ale jest to klasa pokazowa, która miała pokazywać jak wykonać dokładnie to samo zadanie! Użycie tej metody miało więc wyświetlić przetworzony tekst.
Referencja w tym przypadku nie jest dobrym pomysłem. Jeśli przetwarzamy tekst wielokrotnie (tak jak tutaj), to zazwyczaj oryginał jest nam potrzebny do kolejnych przekształceń. Co innego było w klasie Arr (w tym artykule ;)). Tam dodawaliśmy elementy do konkretnej tablicy. Nie było więc sensu robić czegoś takiego:
$array = Arr::push($array,$element);
A co do tego używania return i print w funkcjach, to znowu odsyłam do artykułu o szablonach. W napisanym przeze mnie przykładzie masz dwie metody __toString() i display(). Jedna z nich zwraca kod szablonu, a druga go wyświetla. I to właśnie ta wyświetlająca kod jest częściej używana.
Każdy w swoim pierwszym podręczniku do PHP był przestrzegany przed używaniem echo czy też print wewnątrz funkcji. Jednak praktyka taka jest stosowana. Jak mi nie wierzysz, to przejrzyj sobie kilka większych frameworków.

lisek Mozilla/5.0 (Windows NT 6.1; rv:5.0) Gecko/20100101 Firefox/5.0

frameworki to co innego, w nich niektóre funkcje istnieją tylko po to by coś wyświetlić więc zabawa w return i echo nie miałaby tam sensu. Ale jeśli funkcja ma za zadanie zwrócić np. 10 najnowszych artykułów (no nie całych, ale linków do nich ;) ) to fajnie jest jeśli jednak zwróci je przez return. Wtedy nie trzeba będzie pisać prawie identycznej funkcji tylko po to żeby identyczny tekst wrzucić do maila albo rss.
Mój podręcznik był jakiś wybrakowany bo nic nie wspominał że echo w funkcji to zuoo, ale sam to wykombinowałem przy kolejnej rozbudowie jednej stronki. Na szczęście udało się wszystko zrobić metodą znajdź i zamień.
Co do rekurencji to nie miałem może na myśli tego konkretnego przykładu, ale spotkałem się już z funkcją która cośtam drukowała na stronie, potem drukowała separator na żywca przekazany w argumencie, potem znowu jakaś tabela, i dopiero return. W takim wypadku lepiej byłoby to zrobić właśnie przez rekurencję.

Kacper Kołodziej www Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30

Rekurencja? Chyba pomyliłeś z referencją. (Rekurencja to odwoływanie się funkcji do samej siebie ;)
Poza tym to chyba znasz moje metody na tyle, żeby nie podejrzewać mnie o robienie czegoś takiego w praktyce. Tutaj to przykład. Funkcja miała zrobić dokładnie to samo co wykonywał tamten kod.

Bartosz Milczarek www Opera/9.80 (X11; Linux i686; U; pl) Presto/2.9.168 Version/11.50

Nie chce mi się czytać całego... dotrwałem do drugiego listingu

To trochę może mylić:
$klasa = new Tekst;

Tworzysz obiekt klasy, więc bardziej odpowiednie byłoby
$obiekt = new Tekst;

Przecież modyfikujesz obiekt a nie klasę
$obiekt->modyfikuj($tekst);


Nie wiem czy nie kumasz oop czy nie piszesz czystego kodu...

Kacper Kołodziej www Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30

Chwila nieuwagi...
Poza tym, to kilka razy wspomniałem w tekście, że tworzymy obiekt, więc każdemu uważnemu czytelnikowi daje to do myślenia.
Nie zmuszam do czytania, więc nie musisz dawać mi komunikatu, że Ci się nie chce.

Dodaj komentarz »