Klasy i obiekty

Nazwa programowania obiektowego wzięła się właśnie od wykorzystania klas i obiektów. Bez klas i obiektów oczywiście da się programować ale jeśli program jest większy niż jedna lub dwie funkcje to odradzam.

Ze spraw organizacyjnych to każdą klasę przechowujemy w innym pliku PHP. Bez tego będzie działać ale później odnalezienie klasy nie będzie tak wygodne. A tak poza tym to nazwy plików jak i nazwy klas robimy zgodne (takie same) oraz zaczynamy je z dużej litery.

Idąc za powyższym stwórzmy plik o nazwie Calculator.php a w nim klase o tej samej nazwie.

<?php
    class Calculator {

    }

Klasę tworzymy za pomocą słowa kluczowego class, jako że klasy to takie jakby szablony obiektów nasza jest w tym momencie bezużyteczna.

Klasy zawierają swoje pola i metody. Idźmy dalej za przykładem prostego Kalkulatora i dodajmy pola i medody typowe dla Kalkulatora.


class Calculator {
    private $a = 0;
    public $b = 0;
    public $action = '+';
    
    public function setA(float $value):void{
        $this->a = $value;
    }
    public function getA():float{
        return $this->a;
    }
    private function add():float{
        return $this->a + $this->b;
    }
    public function subtract():float{
        return $this->a - $this->b;
    }
    public function doAction(float $a, float $b,string $action):float{
        switch ($action) {
            case '-':
                return $a - $b;
            default:
                return $a + $b;
        }
    }
}

Na początku klasy definiujemy pola wraz z ich dostępnością. Od najnowszego PHP (na ten moment) definiujemy także typy tych pól, jednak w tym przykładzie jeszcze to pominiemy.

Pierwsza pole $a to pole prywatne, oznacza to że nie będzie do niego dostępu z zewnątrz klasy. Aby przypisać lub pobrać wartość do/z takiego pola należy utworzyć specjalne metody zwane setterami i getterami od angielskich słów set i get.

Kolejne pola to pola publiczne na drugą wartość oraz oznaczenie działania. Pola publiczne są dostępne spoza klasy i można dowolnie im przypisywać wartości oraz pobierać bez użycia dodatkowych metod.

Warto zwrócić uwagę że przypisaliśmy tutaj wartości domyślne.

Dalej są dwie wcześniej omówione metody do obsługi pola prywatnego setA oraz getA służące do zapisania wartości dla tego pola oraz pobrania wartości.

Metody add oraz subtract to metody, która robią coś konkretnego, czyli zwracają wynik dodawania/odejmowania wartości przypisanych do pól a i b.

Ostatnia metoda doAction pobiera trzy parametry, dwie liczby i działanie, a następnie zwraca liczbę zmiennoprzecinkową, która powinna być wynikiem odejmowania lub dodawania.

Tworzenie obiektów

Przejdźmy teraz do naszego pliku głównego index.php. Pierwszą czynnością jaką należy zrobić jest dołączenie pliku z naszą klasą Calculatora. Aby to zrobić należy wykorzystać instrukcję include

<?php
include 'Calculator.php';        

W tym momencie możemy utworzyć obiekt, aby to zrobić wykorzystujemy słowo new przed nazwą klasy. Zaraz po nazwie klasy dodajemy nawiasy okrągłe.


$calculator = new Calculator();       

Dalej przydały by się jakieś dane, na których można by było podziałać. W tym celu później utworzymy formularz a teraz napiszemy kod pobierający dane, tak jak to przerabialiśmy wcześniej.


$a = filter_input(INPUT_POST, 'a', FILTER_VALIDATE_FLOAT);
$b = filter_input(INPUT_POST, 'b', FILTER_VALIDATE_FLOAT);
$action = filter_input(INPUT_POST, 'action', FILTER_DEFAULT);      

Pole $a jest polem prywatnym czyli nie możemy się do niego odnieść bezpośrednio. Aby przypisać wartość do tego pola wykorzystujemy setter czyli metodę setA.


$calculator->setA($a ?? 0);      

W powyższym warto zwrócić uwagę na maksymalnie skróconą notację warunkową. Zapis $a ?? 0 oznacza że jeśli zmienna $a będzie pusta to zostanie użyte zero a jeśli nie to wartość tej zmiennej. W praktyce jak np nie wyślemy danych i filter_input zwróci false to zostanie przekazane zero, dzięki czemu typ wartości będzie zgodny z typem parametru i nie pojawi się błąd PHP.

Pole $b jest polem publicznym czyli możemy się do niego odnieść bezpośrednio.


$calculator->b = $b;    

Na koniec wykorzystamy metody obliczające.


echo "Wynik: " . $calculator->subtract() . '';
echo "Wynik drugi: " . $calculator->doAction($a ?? 0, $b ?? 0, $action ?? '+');   

Do naszego maksymalnie uproszczonego Calculatora potrzebujemy jeszcze tylko formularza HTML.


<form method="POST">
    <input type="number" name="a" />
    <input type="number" name="b" />
    <select name="action">
        <option value="-">Odejmowanie</option>
        <option value="+">Dodawanie</option>
    </select>
    <input type="submit" />
</form>  

Konstruktor

Konstruktor to metoda, która jest wykonywana w momencie tworzenia obiektu na podstawie klasy. Można także przekazywać do niej wartości. Spróbujmy dodać do naszej klasy Calculator konstruktor za pomocą, którego będzie można ustawić wartości a i b.


    ...
    function __construct(float $a = 0, float $b = 0) {
        $this->a = $a;
        $this->b = $b;
    }
    ...

Tutaj warto zwrócić uwagę że parametry funkcji mają określone wartości domyślne. Oznacza to że podanie parametrów nie jest obowiązkowe.

Podając liczby przy tworzeniu obiktu zostaną one zapisane do pól.


    ...
    $calculator = new Calculator(1,4);
    ...

Konstruktor można wykorzystywać także do innych celów niż do ustawienia wartości. O czym w kolejnych częściach.

Dziedziczenie

Dziedziczenie to wspaniały mechanizm pozwalający na rozszerzanie funkcjonalności klas poprzez inne klasy. Na przykład można napisać klasę do obsługi bazy danych i po niej dziedziczyć wszystkimi klasami, które korzystają z bazy danych.

Do dziedziczenia potrzebujemy dwóch klas i słowa kluczowego extends.


    class Calculator2 extends Calculator {
        private $a = 0;
        public function multiplication(){
            return $this->a * $this->b;
        }
    }

W powyższym przykładzie napisaliśmy klasę dziedziczącą rozbudowującą nasz Calculator o możliwość mnożenia. W prawdziwym programowaniu dziedzicenie jest w użytku codziennym i na przykład mamy wiele klas kontolerów rozszerzających klasę bazową kontrolera, ale o tym szerzej w kolejnych kursach.

Nadpisywanie metod

Bardzo często zdarza się że w klasie potomnej potrzebujemy metody o takiej samej nazwie jak w klasie nadrzędnej, która jednak robi trochę coś innego. Najczęściej jest to konstruktor.

Aby nadpisać metodę klasy nadrzędnej (tej po której dziedziczymy) należy napisać metodę o tej samej nazwie. Co więcej możemy wykorzystać funkcjonalność nadpisywanej metody poprzez słowo kluczowe parent

Wcześniej w naszym przykładzie napisaliśmy konstruktor, dzięki któremu można było przypisać dwie wartości do pól obiektu. Teraz spróbujemy nadpisać konstruktor rozszerzając jego funkcjonalność o możliwość przypisania działania.


    class Calculator2 extends Calculator {
        function __construct(float $a = 0, float $b = 0, string $action = '+') {
            parent::__construct($a, $b);
            $this->action = $action;
        }
        ...
    }

Powyższy przykład prawdopodobnie już nie jest najprostszy dla początkującego ale postaram się wytłumaczyć co tu się dzieje.

W klasie dziedziczącej po klasie Calculator napisaliśmy konstruktor, który podobnie jak w przypadku klasy nadrzędnej przyjmyje jako dwa pierwsze argumenty liczby z tym że tutaj jest jeszcze dodatkowo trzeci argument ze znakiem co jest rozszerzeniem metody nadrzędnej.

Wewnątrz metody używamy słowa parent, dwa dwukropki i użycie konstruktora z klasy nadrzędnej czyli rodzica. W ten właśnie sposób wykorzystujemy funkcjonalność klasy nadrzędnej czyli czyli przypisujemy do pól $a i $b wartości liczbowe tak jak wcześniej. Warto zwrócić uwagę że pól już nie definiujemy w klasie potomnej Calculator2 ponieważ zostały one odziedzicone, tak samo jak pole $action

W kolejnej linijce jest kod rozszerzający funkcjonalność konstruktora o przypisanie do pola $action wartości.

Teraz zmienimy nasz index.php tak aby wykorzystać te wszystki dobrodziejstwa dzidziczenia.


    include 'Calculator.php';
    include 'Calculator2.php';
    $a = filter_input(INPUT_POST, 'a', FILTER_VALIDATE_FLOAT);
    $b = filter_input(INPUT_POST, 'b', FILTER_VALIDATE_FLOAT);
    $action = filter_input(INPUT_POST, 'action', FILTER_DEFAULT);
    $calculator2 = new Calculator2($a ?? 0, $b ?? 0, $action ?? '+');
    echo "Wynik: " . $calculator2->subtract() . '';
    echo "Wynik drugi: " . $calculator2->multiplication();

Załączyliśmy tutaj plik z klasą dziedziczącą Calculator2, należy tutaj zwrócić uwagę na to że dołączamy plik z klasą dzidziczącą po klasie dziedziczonej ponieważ program wykonuje się od góry do dołu i jakbyśmy to zrobili odwrotnie po prostu otrzymalibyśmy błąd.

Tworzenie obiektu przenieśliśmy pod pobranie danych ponieważ teraz chcemy przypisać wartości szybciej, za pomocą konstruktora, co właśnie widać w momencie tworzenia obiektu.

W kolejnych dwóch liniach dokonujemy odejmowania za pomocą dziedziconej metody oraz testujemy działanie nowej metody multiplication

Metody statyczne

Metody statyczne to twór umożliwiający wykorzystywanie metod bez tworzenia obiektów klasy. Ma to jednak swój koszt, ponieważ w takiej metodzie nie za bardzo można wykorzystywać metody danej klasy, które nie są statyczne dlatego też używa się ich raczej rzadziej niż częściej.

Spróbujmy sobie dopisać metodę statyczną, odpowiadającą za dzielenie do naszej klasy potomnej.


        ...
        public static function division(float $a = 0, float $b = 1):float{
            return $a / $b;
        }
        ...

Tutaj słowem kluczowym jest static, którego używamy przed słowem function. Jak widać jest to zwykła funkcja, która wykonuje działanie dzielenia. Trzeba też pamiętać że nie można dzielić przez zero, ale to już zadanie dla obsługi wyjątków.

Teraz pozostaje tylko zaprezentować wykorzystanie metody statycznej w pliku index.php


        ...
        echo "Dzielenie: " . Calculator2::division($a ?? 0, $b ?? 1);
        ...

I to tyle na dzisiaj. To są dopiero podstawy programowania obiektowego. Jest jeszcze kilka ciekawych mechanizmów jak interfejsy, traity, abstrakcje plus coś jeszcze co omówimy sobie w kolejnym nieco bardziej zaawansowanym kursie. Do tego czasu postaraj się dobrze opanować materiał z tej lekcji.

Zadania

  1. Napisz klasę Dog, w której będzie pole $times określające ilość powtórzeń i metoda barking zwracająca string. W metodzie utwórz algorytm generujący string składający się z takiej ilości słowa „hau” jak podaje pole $times. Następnie stwórz formularz w którym podasz ilość szczeknięć i z pomocą obiektu i jego metody wyświetlisz „szczekanie”.
  2. Dodaj metodę zwracającą informację „co robi pies?”, a następnie zmodyfikuj index.php tak aby przed podaniem ilości lub jeśli zostanie podane 0 będzie wyświetlana informacja „Pies nic nie robi” w przeciwnym wypadku, tak jak wcześniej „szczekanie”.
  3. Napisz klasę Burek dziedziczącą po klasie Dog. W nowej klasie napisz metodę rolling czyli tarzanie. Metoda działająca analogicznie do szczekania z tym że będzie inny napis, np „tocz”. Formularz html rozbuduj o select z wyborem akcji, szczekanie czy taczanie. Metodę mówiącą o tym co robi pies nadpisz tak aby uwzględniała wybór tarzania.
  4. Napisz metodę statyczną aport. Metoda będzie zwracać string „Pies aportuje”. Spróbuj wykorzystać tę metodę.