Lekcja 22 (OOP 2): Klasy i Obiekty - Definiowanie, Właściwości, Metody
Witaj w drugiej lekcji poświęconej Programowaniu Obiektowemu w PHP! W poprzedniej lekcji wprowadziliśmy teoretyczne podstawy OOP, omawiając kluczowe koncepcje takie jak klasy, obiekty, hermetyzacja, dziedziczenie i polimorfizm. Teraz nadszedł czas, aby przejść od teorii do praktyki i nauczyć się, jak definiować własne klasy, tworzyć z nich obiekty oraz pracować z ich właściwościami i metodami w PHP.
W tej lekcji skupimy się na fundamentalnych elementach tworzenia klas: jak wygląda ich składnia, jak deklarować właściwości (czyli zmienne przechowujące stan obiektu) oraz metody (czyli funkcje definiujące zachowanie obiektu). Zobaczymy również, jak tworzyć instancje klas (obiekty) i jak odwoływać się do ich składowych. To kluczowe umiejętności, które pozwolą nam budować pierwsze, proste struktury obiektowe.
Definiowanie Klasy w PHP
Klasa w PHP jest definiowana za pomocą słowa kluczowego class
, po którym następuje nazwa klasy, a następnie para nawiasów klamrowych { }
, wewnątrz których umieszcza się definicje właściwości i metod klasy. Nazwy klas w PHP, zgodnie z popularnymi konwencjami (np. PSR-1), powinny być pisane w stylu PascalCase (znanym też jako UpperCamelCase), co oznacza, że każdy wyraz w nazwie zaczyna się wielką literą, bez spacji, np. MojaPierwszaKlasa
, Uzytkownik
, ProduktSamochodowy
.
Podstawowa składnia definicji klasy:
<?php
class NazwaKlasy
{
// Tutaj definicje właściwości (zmiennych członkowskich)
// Tutaj definicje metod (funkcji członkowskich)
}
?>
Przykład prostej, pustej klasy:
<?php
class Produkt
{
// Na razie klasa jest pusta
}
?>
Taka klasa, mimo że pusta, jest już poprawną definicją i możemy tworzyć jej obiekty. Jednak aby klasa była użyteczna, powinna zawierać właściwości i metody.
Właściwości (Properties)
Właściwości (nazywane też atrybutami, polami lub zmiennymi członkowskimi) to zmienne zadeklarowane wewnątrz klasy. Przechowują one dane, które opisują stan obiektu. Każdą właściwość deklaruje się za pomocą jednego z modyfikatorów dostępu (public
, protected
lub private
), po którym następuje nazwa właściwości (zaczynająca się od znaku dolara $
, tak jak zwykłe zmienne w PHP).
Modyfikatory dostępu dla właściwości:
public
: Właściwość jest dostępna z każdego miejsca – z wnętrza klasy, z klas dziedziczących oraz spoza klasy (bezpośrednio przez obiekt).protected
: Właściwość jest dostępna tylko z wnętrza samej klasy oraz z klas, które po niej dziedziczą. Nie jest dostępna bezpośrednio spoza klasy.private
: Właściwość jest dostępna tylko z wnętrza samej klasy, w której została zdefiniowana. Nie jest dostępna dla klas dziedziczących ani spoza klasy.
Na razie będziemy używać głównie modyfikatora public
, aby ułatwić dostęp do właściwości. O hermetyzacji i znaczeniu pozostałych modyfikatorów będziemy mówić szczegółowo w kolejnych lekcjach.
Przykład deklaracji właściwości w klasie Produkt
:
<?php
class Produkt
{
// Właściwości publiczne
public $nazwa;
public $cena;
public $opis;
public $dostepnaIlosc = 0; // Możemy nadać wartość domyślną
// Właściwość chroniona (dostępna tylko w tej klasie i klasach potomnych)
protected $kosztProdukcji;
// Właściwość prywatna (dostępna tylko w tej klasie)
private $numerSeryjnyUnikalny;
}
?>
Właściwościom można nadawać wartości domyślne bezpośrednio przy ich deklaracji, tak jak dla $dostepnaIlosc
. Jeśli wartość domyślna nie zostanie podana, właściwość będzie miała wartość null
do momentu jej pierwszego przypisania.
Typowanie Właściwości (Property Typing)
Od PHP 7.4 wprowadzono możliwość typowania właściwości klas. Oznacza to, że możemy określić, jakiego typu dane dana właściwość może przechowywać. Zwiększa to bezpieczeństwo typu i czytelność kodu, pomagając wykrywać błędy na wcześniejszym etapie.
<?php
class ProduktZaawansowany
{
public string $nazwa; // Właściwość musi być typu string
public float $cena; // Właściwość musi być typu float
public ?string $opis; // Może być stringiem lub null (nullable type)
public int $dostepnaIlosc = 0;
// W PHP 8 wprowadzono również typy unijne (union types)
public int|string $identyfikator;
}
// Próba przypisania nieprawidłowego typu spowoduje błąd TypeError
// $produkt = new ProduktZaawansowany();
// $produkt->cena = "bardzo drogo"; // Błąd! "bardzo drogo" to nie float
?>
Typowanie właściwości jest dobrą praktyką i zaleca się jego stosowanie w nowoczesnym kodzie PHP.
Metody (Methods)
Metody to funkcje zdefiniowane wewnątrz klasy. Określają one zachowania obiektu lub operacje, które można na nim wykonać. Składnia definicji metody jest bardzo podobna do zwykłej funkcji PHP, ale poprzedzona jest modyfikatorem dostępu (public
, protected
lub private
).
Modyfikatory dostępu dla metod działają analogicznie jak dla właściwości:
public
: Metoda jest dostępna z każdego miejsca.protected
: Metoda jest dostępna tylko z wnętrza samej klasy oraz z klas, które po niej dziedziczą.private
: Metoda jest dostępna tylko z wnętrza samej klasy, w której została zdefiniowana.
Przykład definicji metod w klasie Produkt
:
<?php
class Produkt
{
public string $nazwa = "Nieznany produkt";
public float $cena = 0.0;
public int $dostepnaIlosc = 0;
// Metoda publiczna do wyświetlania informacji o produkcie
public function wyswietlInformacje(): string
{
// $this odnosi się do bieżącego obiektu (instancji klasy)
return "Produkt: " . $this->nazwa . ", Cena: " . $this->cena . " PLN, Dostępna ilość: " . $this->dostepnaIlosc;
}
// Metoda publiczna do zmiany ceny
public function zmienCene(float $nowaCena): void // void oznacza, że metoda nic nie zwraca
{
if ($nowaCena >= 0) {
$this->cena = $nowaCena;
} else {
echo "Cena nie może być ujemna!<br>";
}
}
// Metoda publiczna do sprzedaży produktu
public function sprzedaj(int $ilosc = 1): bool
{
if ($ilosc > 0 && $this->dostepnaIlosc >= $ilosc) {
$this->dostepnaIlosc -= $ilosc;
echo $ilosc . " szt. produktu '" . $this->nazwa . "' zostało sprzedanych.<br>";
return true;
} else {
echo "Nie można sprzedać produktu '" . $this->nazwa . "'. Niewystarczająca ilość lub błędna wartość.<br>";
return false;
}
}
// Prywatna metoda pomocnicza (nie będzie dostępna z zewnątrz)
private function logujOperacje(string $operacja): void
{
// Tutaj mógłby być kod logujący operację np. do pliku
echo "LOG: Wykonano operację '" . $operacja . "' na produkcie '" . $this->nazwa . "'.<br>";
}
}
?>
Pseudo-zmienna $this
Wewnątrz metod obiektu (niestatycznych, o statycznych powiemy później) dostępna jest specjalna pseudo-zmienna $this
. $this
odnosi się do bieżącej instancji obiektu, czyli do obiektu, na rzecz którego dana metoda została wywołana. Używamy $this->nazwaWlasciwosci
do odwołania się do właściwości obiektu i $this->nazwaMetody()
do wywołania innej metody tego samego obiektu.
Typowanie Argumentów i Wartości Zwracanej przez Metody
Podobnie jak w przypadku zwykłych funkcji (co omawialiśmy w Lekcji 10), metody również mogą (i powinny) mieć typowane argumenty oraz określoną typ wartości zwracanej. W przykładzie powyżej, metoda wyswietlInformacje()
ma zadeklarowany typ zwracany string
, metoda zmienCene()
przyjmuje argument $nowaCena
typu float
i zwraca void
(czyli nic nie zwraca), a metoda sprzedaj()
przyjmuje int
i zwraca bool
.
Tworzenie Obiektów (Instancji Klasy)
Sama definicja klasy jest tylko szablonem. Aby móc z niej korzystać, musimy stworzyć obiekt (nazywany też instancją lub egzemplarzem) tej klasy. Obiekty tworzy się w PHP za pomocą słowa kluczowego new
, po którym następuje nazwa klasy i para nawiasów okrągłych ()
(nawet jeśli klasa nie ma zdefiniowanego konstruktora – o konstruktorach powiemy w następnej lekcji).
<?php
// Zakładając, że klasa Produkt jest zdefiniowana jak powyżej
// Tworzenie obiektu (instancji) klasy Produkt
$ksiazka = new Produkt();
$dlugopis = new Produkt();
// $ksiazka i $dlugopis to teraz dwa niezależne obiekty klasy Produkt.
// Każdy z nich ma własny zestaw właściwości ($nazwa, $cena, $dostepnaIlosc).
?>
Dostęp do Właściwości i Metod Obiektu
Po utworzeniu obiektu, możemy odwoływać się do jego publicznych właściwości i wywoływać jego publiczne metody za pomocą operatora obiektu ->
(strzałka składająca się z myślnika i znaku większości).
Dostęp do właściwości: $nazwaObiektu->nazwaWlasciwosci
Wywołanie metody: $nazwaObiektu->nazwaMetody(argumenty)
Przykład użycia obiektu klasy Produkt
:
<?php
// Zakładając, że klasa Produkt jest zdefiniowana
$ksiazka = new Produkt();
// Ustawianie wartości publicznych właściwości
$ksiazka->nazwa = "PHP dla Początkujących";
$ksiazka->cena = 49.99;
$ksiazka->dostepnaIlosc = 100;
// Wywoływanie publicznych metod
echo $ksiazka->wyswietlInformacje() . "<br>";
// Wynik: Produkt: PHP dla Początkujących, Cena: 49.99 PLN, Dostępna ilość: 100
$ksiazka->zmienCene(45.50);
echo "Po zmianie ceny: " . $ksiazka->wyswietlInformacje() . "<br>";
// Wynik: Po zmianie ceny: Produkt: PHP dla Początkujących, Cena: 45.5 PLN, Dostępna ilość: 100
$ksiazka->sprzedaj(5);
// Wynik: 5 szt. produktu 'PHP dla Początkujących' zostało sprzedanych.
echo "Po sprzedaży: " . $ksiazka->wyswietlInformacje() . "<br>";
// Wynik: Po sprzedaży: Produkt: PHP dla Początkujących, Cena: 45.5 PLN, Dostępna ilość: 95
// Próba odwołania się do właściwości chronionej lub prywatnej spoza klasy spowoduje błąd:
// echo $ksiazka->kosztProdukcji; // Błąd: Cannot access protected property Produkt::$kosztProdukcji
// echo $ksiazka->numerSeryjnyUnikalny; // Błąd: Cannot access private property Produkt::$numerSeryjnyUnikalny
// Próba wywołania metody prywatnej spoza klasy również spowoduje błąd:
// $ksiazka->logujOperacje("test logowania z zewnątrz"); // Błąd: Call to private method Produkt::logujOperacje()
$dlugopis = new Produkt();
$dlugopis->nazwa = "Elegancki Długopis";
$dlugopis->cena = 19.90;
$dlugopis->dostepnaIlosc = 250;
echo $dlugopis->wyswietlInformacje() . "<br>";
// Wynik: Produkt: Elegancki Długopis, Cena: 19.9 PLN, Dostępna ilość: 250
?>
Jak widać, każdy obiekt ($ksiazka
, $dlugopis
) utrzymuje swój własny, niezależny stan (wartości właściwości). Wywołanie metody na jednym obiekcie nie wpływa na stan innego obiektu tej samej klasy (chyba że metoda celowo operuje na danych współdzielonych, np. statycznych, ale o tym później).
Przykład Kompletnej Klasy i Jej Użycia
Połączmy wszystko w jeden większy przykład – klasa reprezentująca prostokąt.
<?php
class Prostokat
{
// Właściwości z typowaniem
public float $dlugoscBokuA;
public float $dlugoscBokuB;
// Metoda do ustawiania wymiarów
public function ustawWymiary(float $a, float $b): void
{
if ($a > 0 && $b > 0) {
$this->dlugoscBokuA = $a;
$this->dlugoscBokuB = $b;
} else {
echo "Długości boków muszą być dodatnie!<br>";
// Można by tu zainicjować domyślnymi wartościami lub rzucić wyjątek
$this->dlugoscBokuA = 0;
$this->dlugoscBokuB = 0;
}
}
// Metoda obliczająca pole
public function obliczPole(): float
{
return $this->dlugoscBokuA * $this->dlugoscBokuB;
}
// Metoda obliczająca obwód
public function obliczObwod(): float
{
return 2 * ($this->dlugoscBokuA + $this->dlugoscBokuB);
}
// Metoda wyświetlająca informacje o prostokącie
public function wyswietlDane(): string
{
if ($this->dlugoscBokuA > 0 && $this->dlugoscBokuB > 0) {
return sprintf(
"Prostokąt o bokach %.2f i %.2f: Pole = %.2f, Obwód = %.2f",
$this->dlugoscBokuA,
$this->dlugoscBokuB,
$this->obliczPole(),
$this->obliczObwod()
);
} else {
return "Prostokąt ma niepoprawne wymiary.";
}
}
}
// Tworzenie i używanie obiektów klasy Prostokat
$prostokat1 = new Prostokat();
$prostokat1->ustawWymiary(5.0, 10.0);
echo $prostokat1->wyswietlDane() . "<br>";
// Wynik: Prostokąt o bokach 5.00 i 10.00: Pole = 50.00, Obwód = 30.00
$prostokat2 = new Prostokat();
$prostokat2->ustawWymiary(3.5, 7.2);
echo "Pole drugiego prostokąta: " . $prostokat2->obliczPole() . "<br>";
// Wynik: Pole drugiego prostokąta: 25.2
$prostokat3 = new Prostokat();
$prostokat3->ustawWymiary(-2.0, 5.0); // Niepoprawne wymiary
echo $prostokat3->wyswietlDane() . "<br>";
// Wynik: Długości boków muszą być dodatnie!
// Prostokąt ma niepoprawne wymiary.
?>
Ten przykład pokazuje, jak klasa Prostokat
enkapsuluje (łączy) dane (długości boków) z operacjami na tych danych (ustawianie wymiarów, obliczanie pola i obwodu, wyświetlanie informacji). Każdy obiekt klasy Prostokat
jest niezależną jednostką.
Podsumowanie Lekcji
W tej lekcji nauczyliśmy się praktycznych aspektów definiowania klas i tworzenia obiektów w PHP. Dowiedzieliśmy się, jak deklarować właściwości (ze zwróceniem uwagi na modyfikatory dostępu i typowanie) oraz metody (również z modyfikatorami dostępu, typowaniem argumentów i wartości zwracanej). Kluczowym elementem jest zrozumienie roli pseudo-zmiennej $this
, która pozwala metodom na interakcję ze stanem konkretnego obiektu.
Opanowaliśmy również proces tworzenia instancji klasy za pomocą operatora new
oraz sposób odwoływania się do publicznych właściwości i metod obiektu za pomocą operatora ->
.
Te podstawy są absolutnie fundamentalne dla dalszej nauki Programowania Obiektowego. W następnej lekcji zajmiemy się bardzo ważnym tematem konstruktorów i destruktorów, które pozwalają na inicjalizację obiektów przy ich tworzeniu oraz wykonywanie operacji porządkujących przy ich niszczeniu. Poznamy również inne metody magiczne w PHP.
Zadanie praktyczne
Stwórz klasę o nazwie Kolo
, która będzie reprezentować koło geometryczne.
- Klasa powinna mieć jedną publiczną, typowaną właściwość
$promien
(typufloat
). - Dodaj publiczną metodę
ustawPromien(float $r): void
, która ustawia wartość promienia. Metoda powinna sprawdzać, czy podany promień jest większy od zera. Jeśli nie, powinna wyświetlić komunikat błędu i ustawić promień na 0. - Dodaj publiczną metodę
obliczPole(): float
, która oblicza i zwraca pole koła (wzór: π * r2). Użyj funkcjiM_PI
dla wartości π. - Dodaj publiczną metodę
obliczObwod(): float
, która oblicza i zwraca obwód koła (wzór: 2 * π * r). - Dodaj publiczną metodę
wyswietlInformacje(): string
, która zwraca sformatowany ciąg znaków z informacjami o kole (promień, pole, obwód). - Utwórz dwa obiekty klasy
Kolo
, ustaw im różne promienie i wyświetl informacje o nich, korzystając z metodywyswietlInformacje()
. Przetestuj również przypadek podania ujemnego promienia.
Kliknij, aby zobaczyć przykładowe rozwiązanie
<?php
class Kolo
{
public float $promien = 0.0;
public function ustawPromien(float $r): void
{
if ($r > 0) {
$this->promien = $r;
} else {
echo "Promień musi być wartością dodatnią! Ustawiono promień na 0.<br>";
$this->promien = 0.0;
}
}
public function obliczPole(): float
{
return M_PI * pow($this->promien, 2);
}
public function obliczObwod(): float
{
return 2 * M_PI * $this->promien;
}
public function wyswietlInformacje(): string
{
if ($this->promien > 0) {
return sprintf(
"Koło o promieniu %.2f: Pole = %.2f, Obwód = %.2f",
$this->promien,
$this->obliczPole(),
$this->obliczObwod()
);
} else {
return "Koło ma niepoprawny promień (musi być większy od 0).";
}
}
}
// Testowanie klasy Kolo
$kolo1 = new Kolo();
$kolo1->ustawPromien(5.0);
echo $kolo1->wyswietlInformacje() . "<br>";
$kolo2 = new Kolo();
$kolo2->ustawPromien(2.75);
echo $kolo2->wyswietlInformacje() . "<br>";
$kolo3 = new Kolo();
$kolo3->ustawPromien(-3.0);
echo $kolo3->wyswietlInformacje() . "<br>";
/* Przykładowy wynik:
Koło o promieniu 5.00: Pole = 78.54, Obwód = 31.42
Koło o promieniu 2.75: Pole = 23.76, Obwód = 17.28
Promień musi być wartością dodatnią! Ustawiono promień na 0.
Koło ma niepoprawny promień (musi być większy od 0).
*/
?>
Zadanie do samodzielnego wykonania
Stwórz klasę Samochod
z następującymi elementami:
- Publiczne, typowane właściwości:
$marka
(string),$model
(string),$rokProdukcji
(int),$predkoscAktualna
(int, domyślnie 0). - Publiczną metodę
przyspiesz(int $wartosc): void
, która zwiększa$predkoscAktualna
o podaną wartość, ale nie więcej niż do pewnej prędkości maksymalnej (np. 200). Jeśli prędkość maksymalna zostanie osiągnięta, ustaw$predkoscAktualna
na tę wartość. - Publiczną metodę
hamuj(int $wartosc): void
, która zmniejsza$predkoscAktualna
o podaną wartość, ale nie mniej niż do 0. - Publiczną metodę
wyswietlStatus(): string
, która zwraca informacje o marce, modelu, roku produkcji i aktualnej prędkości. - Utwórz dwa różne obiekty klasy
Samochod
, ustaw im właściwości (marka, model, rok), a następnie przetestuj metodyprzyspiesz()
ihamuj()
, wyświetlając status po każdej operacji.
FAQ - Klasy i Obiekty w PHP
Czy nazwy właściwości i metod muszą zaczynać się od znaku dolara $
?
Nazwy właściwości (zmiennych członkowskich) muszą zaczynać się od znaku dolara $
, np. public $nazwa;
. Natomiast nazwy metod (funkcji członkowskich) piszemy bez znaku dolara, np. public function mojaMetoda() {}
. Odwołując się do właściwości wewnątrz metody za pomocą $this
, również używamy znaku dolara: $this->$nazwaWlasciwosci
.
Jaka jest różnica między public $zmienna;
a var $zmienna;
w deklaracji właściwości?
Słowo kluczowe var
jest starszym sposobem deklarowania właściwości, pochodzącym z PHP 4. W PHP 5 i nowszych jest ono traktowane jako alias dla public
. Zaleca się jednak używanie jawnych modyfikatorów dostępu (public
, protected
, private
) dla lepszej czytelności i zgodności z nowoczesnymi praktykami.
Czy mogę zadeklarować właściwość bez modyfikatora dostępu?
Jeśli pominiesz modyfikator dostępu przy deklaracji właściwości (co nie jest zalecane), PHP domyślnie potraktuje ją jako public
. Jednak dla jasności kodu zawsze powinno się jawnie określać modyfikator dostępu.
Co się stanie, jeśli spróbuję odwołać się do nieistniejącej właściwości lub metody obiektu?
Jeśli spróbujesz odczytać nieistniejącą właściwość, PHP wygeneruje ostrzeżenie (Notice) i zwróci null
. Jeśli spróbujesz przypisać wartość do nieistniejącej właściwości publicznej, PHP ją utworzy dynamicznie (co jest często uważane za złą praktykę). Próba wywołania nieistniejącej metody spowoduje błąd krytyczny (Fatal error).
Czy typowanie właściwości i metod jest obowiązkowe?
Nie, typowanie w PHP jest opcjonalne (z wyjątkiem niektórych kontekstów w PHP 8+). Jednak jego stosowanie jest silnie zalecane, ponieważ poprawia czytelność kodu, pomaga wykrywać błędy na wczesnym etapie i ułatwia pracę narzędziom do analizy statycznej kodu. Jest to cecha nowoczesnego PHP.
Czy mogę mieć w klasie tylko właściwości lub tylko metody?
Tak, klasa może zawierać tylko właściwości, tylko metody, lub obie te rzeczy. Klasa może być nawet całkowicie pusta (choć rzadko jest to użyteczne samo w sobie, czasem puste klasy służą jako znaczniki lub są rozszerzane przez inne klasy).
Jakie są konwencje nazewnictwa dla metod?
Podobnie jak dla funkcji, dla metod najczęściej stosuje się konwencję camelCase (pierwszy wyraz małą literą, kolejne wyrazy zaczynają się wielką literą, np. obliczPoleKola
, wyswietlDaneUzytkownika
). Jest to zgodne z rekomendacjami PSR-1.