Klasa, interfejs, dziedziczenie… jest wiele pojęć, które są niezbędne dla chcących wykorzystać potencjał obiektowy języka PHP. Od wersji 5, PHP oferuje bogaty zestaw konstrukcji językowych i struktur, które pozwalają pracować z obiektami głównie z perspektywy obsługi bazy danych i plików.

Klasa i Obiekt

Klasa (Class) w większości języków programowania oznacza szablon, szkic, dzięki któremu jesteśmy w stanie powoływać do życia obiekty. Klasa to wytyczne, które posłużą nam do definiowania czym jest obiekt, jakie ma właściwości i co można z nim zrobić. Klasa może być nadrzędna i podrzędna, może być pochodną innej klasy, a także może być jedynie wskazówką do tworzenia innych klas.

Sztandarowym przykładem może być abstrakcja samochodu. Samochód posiada takie cechy (właściwości) jak silnik, przebieg, kolor karoserii czy ilość drzwi. Każda z tych cech może być inna i w zależności od tego jaki samochód akurat produkujemy w fabryce może być definiowana na nowo. Przykładem może być rozróżnienie aut ze względu na rodzaj nadwozia: kombi, sedan, hatchback czy autobus. Każdy z tych typów aut ma cechy wspólne (są pojazdami), ale mają inną konstrukcję i zastosowanie.

Podobnie jest w przypadku wielu abstrakcji znanych z życia codziennego. Klasyfikacja różnych dziedzin życia znajduje odzwierciedlenie w świecie programowania, a klasy są jedynie pisemną definicją tego, czym obiekty różnią się od siebie.

Klasyfikacja (Klasa)Przykłady
OwocePomarańcze, Banany, Jabłka, Śliwki
BudynkiDom, Blok, Szkoła, Kościół
KomputeryStacjonarny, Laptop, Tablet, Palmtop

Jeżeli klasa to szkic, obiekt (Object) jest reprezentacją tego szkicu w działaniu. Programista z pomocą klasy może tworzyć obiekty, które mają właściwości i funkcje zdefiniowane przez klasę. Dlatego mówimy o programowaniu obiektowym – z pomocą klas tworzymy obiekty, które mają wykonywać określone działania i przechowywać określone wartości.

<?php
class Drabina { // definicja klasy
    public $ilosc_szczebli = 10; // właściwość klasy
    public $stan = 'stoi'; // właściwość klasy

    public function przewroc() { // metoda klasy
        $this->stan = 'lezy'; // zmiana właściwości klasy
    }
}

$drabina1 = new Drabina(); // obiekt klasy Drabina
$drabina1->ilosc_szczebli = 5; // nadanie wartości dla właściwości "ilosc_szczebli"

$drabina2 = new Drabina(); // kolejny obiekt klasy Drabina
$drabina2->ilosc_szczebli = 15; // nadanie innej wartości dla wlasciwosci drugiego obiektu

Warto zapamiętać:

  1. Klasa to szkic. Sama w sobie nic nie posiada i nic nie wykonuje. Definiuje jedynie właściwości i metody, dzięki którym tworzymy indywidualne obiekty.
  2. Obiekt to indywidualny zestaw cech (właściwości) i możliwości (metod), które sprawiają, że szkic przestaje być tylko rysunkiem na papierze.
  3. Nazwy klas, właściwości, obiektów itd. w języku polskim zostały tu użyte tylko dla przykładu i lepszego zobrazowania tematu dla polskich czytelników. Zalecamy posługiwanie się językiem angielskim w nazewnictwie i w ogóle w kodzie 🙂

Dziedziczenie

Aby umożliwić definiowanie klas, które posiadają cechy wspólne, ale różnią się od siebie, wprowadzono tzw. dziedziczenie. W świecie owoców klasą nadrzędną byłaby klasa Owoc, a klasami, które dziedziczą po niej byłby Banan, Gruszka, Kiwi lub Malina. Co więcej, każda z tych klas może mieć podklasy, które jeszcze bardziej precyzyjnie podchodzą do różnic, jak np. Gruszka Konferencja, Gruszka Rocha itd.

Dziedziczenie umożliwia definiowanie klas, które rozwijają klasę nadrzędną. Klasa Owoc nie mówi nam jak wiele różnorodnych owoców mamy do dyspozycji w warzywniaku, a jedynie definiuje istnienie pewnej grupy. Klasa Owoc posiada właściwości takie jak kolor, kształt czy smak, ale dopiero klasy podrzędne, rozwijające klasę nadrzędną za pomocą słowa „extends” mogą rzeczywiście wskazać różnice pomiędzy jednym owocem a drugim:




<?php
class Owoc { // definicja klasy
    public string $kolor; // właściwość klasy
    public string $ksztalt; // właściwość klasy
    public string $smak; // właściwość klasy

    public function ugryz() {} // metoda klasy
}

class Banan extends Owoc { // definicja klasy
    public $kolor = 'zolty'; // właściwość klasy
    public $ksztalt = 'podlozny'; // właściwość klasy
    public $smak = 'bananowy'; // właściwość klasy
}

Widoczność

Widoczność jest cechą właściwości obiektów i ich metod. Jest ściśle związana z dostępem do obiektu klasy i z dziedziczeniem. Rozróżniamy 3 poziomy dostępu do właściwości i metod klasy:

  1. Public – mamy pełny dostęp do tej właściwości lub metody
  2. Protected – właściwość lub metoda mogą być dostępne w klasie lub w klasach dziedziczących
  3. Private – właściwość lub metoda dostępne są tylko w danej klasie

Jeśli określamy jakąś właściwość lub metodę jako publiczną, dostęp do niej mamy w każdym wypadku. Dzięki temu Banan, podklasa Owocu, ma dostęp do metody ugryzc().

Inaczej sprawa ma się z widocznością private i protected. Widoczność protected oznacza, że tylko sama klasa i klasy pochodne mogą modyfikować właściwość lub metodę – nie możemy tego uczynić w utworzonym obiekcie. Natomiast prywatny dostęp oznacza, że dana właściwość lub funkcja mogą być modyfikowane tylko wewnątrz klasy.

<?php
class Owoc {
    public string $nazwa;
    protected string $kolor;
    private string $waga;
}

$malina = new Owoc();
$malina->nazwa = 'malina'; // OK
$malina->kolor = 'rozowy'; // Fatal Error
$malina->waga = '45g'; // Fatal Error

Warto zapamiętać:

  1. Dostęp do właściwości klasy i obiektu to dwie różne rzeczy. Definiując dostęp private, protected czy public wskazujemy różny dostęp z poziomu klas. W przypadku obiektów private i protected w ogóle nie są dostępne i próba dostępu generuje błąd.
  2. Widoczność właściwości obiektu możemy kontrolować również z pomocą metod. Jeśli nie chcemy umożliwiać zmodyfikowanie właściwości bezpośrednio (co nie jest dobrą praktyką), możemy stworzyć tzw. gettery i settery, czyli metody,które z wewnątrz danej klasy umożliwią dostęp do właściwości. O ile $owoc->kolor nie będzie dostępny, o tyle metoda publiczna getColor() { return $this->kolor; } umożliwi już zwrócenie tej konkretnej właściwości w kontrolowany sposób.

Konstruktor i destruktor

Jeśli kupowane przez nas zabawki dla dzieci mają w środku baterie, lub telewizory posiadają system operacyjny, to tę operację nadawania wartości „na starcie” możemy nazwać konstrukcją. Konstruktor to specjalna metoda __construct() {} w której możemy zdefiniować czynności i właściwości wykonywane przy inicjowaniu nowego obiektu tejże klasy.

<?php
class Rower {
    public bool $siodelko;
    public bool $kierownica;
    public function __construct(bool $kierownica) {
        $this->siodelko = true;
        $this->kierownica = $kierownica;
    }
}

$rower = new Rower(true); // brawo, rower posiada kierownicę!
$rower = new Rower(); // błąd, nie wskazaliśmy wartości dla argumentu $kierownica

W powyższym przykładzie konstruktor zajmuje się nadaniem 2 wartości dla właściwości $siodelko i $kierownica. W ten sposób możemy wymusić, aby każdy tworzony obiekt posiadał już pewne właściwości zdefiniowane w określony sposób. W przypadku samochodu możesz sobie wyobrazić, że tych domyślnych parametrów będzie dość sporo.

Destruktor to elegancki sposób na zwolnienie pamięci użytej przez obiekt klasy. Wywołując metodę __destruct() informujemy kompilator, że nie będziemy już używać tego zasobu w tej sesji.

Interfejs

Dziedziczenie obiektów wydaje się oczywiste ze względu na odniesienia do klasyfikacji naturalnej: rower jest pojazdem, autobus jest pojazdem i quad jest pojazdem. Ale chwila… Czy istnieje sposób na zagwarantowanie, że każda klasa dziedzicząca po klasie Pojazd będzie miała funkcję jeżdżenia i silnik?

Tą konstrukcją językową jest Interfejs znany z wielu języków programowania (np. Java). Interfejs umożliwia zdefiniowanie jeszcze bardziej ogólnego szablonu od klasy, który pełni rolę arbitra: każda podklasa Pojazdu musi mieć silnik i musi mieć sposób na poruszanie się.

<?php
interface Pojazd {
    public function ruszaj($klucz);
}

class Pojazd {
    public string $silnik;
    // klasa nie musi posiadać żadnych właściwości, może być jedynie wskazówką lub posiadać właściwości, których nie modyfikują podklasy
}

class Motorower extends Pojazd implements Pojazd {
    public $silnik;
    public function ruszaj(): bool { // błąd! nie wskazaliśmy parametru $klucz
        return false;
    }
}

class Samochod extends Pojazd implements Pojazd {
    public $silnik = 'spalinowy';
    public function ruszaj(bool $klucz): bool {
        // logika dla parametru $klucz
        return true;
    }
    // powodzenie! zapewniliśmy wszystko, czego wymaga interfejs
}

Interfejsy są niezwykle przydatne: pozwalają kontrolować, czy pamiętamy o wszystkim, co dotyczy cech wspólnych. Są także powodem wielu nerwów: jeśli okazuje się, że dana klasa nie może spełnić wymagań interfejsu, może to wymagać stworzenia innego interfejsu i przeprogramowania naszej aplikacji.

Warto wiedzieć:

  1. Interfejsy są niezależne od klas i mogą definiować właściwości i metody dla klas różnego pochodzenia.
  2. Interfejsy tak jak klasy mogą być dziedziczone.
  3. Każda metoda realizująca założenia interfejsu musi mieć zdefiniowane te same parametry o tym samym typie (jeśli zdefiniowano typ, co jest świetną praktyką).

Co jeszcze powinniśmy wiedzieć?

Programowanie obiektowe w PHP tak jak w innych językach to bardzo rozległy temat. Powyższe wskazówki to tylko wierzchołek góry lodowej. Ale spokojnie – nie rozbijemy statku jeśli tylko będziemy ćwiczyć prace z obiektami.

W tematyce obiektowej pozostaje jeszcze kilka ważnych pojęć ja klasy abstrakcyjne, iteracje czy autoloading, które postaram się przybliżyć w jednym z kolejnych wpisów. Daj znać w komentarzu czy powyższy artykuł jest dla Ciebie zrozumiały i czy w jakikolwiek sposób pomógł Tobie zrozumieć świat obiektów i klas w PHP.