Programowanie obiektowe w PHP - praktyczny kurs OOP

Czy zastanawiałeś się kiedyś, po co uczyć się programowania obiektowego? Po co zmieniać stare, dobre programowanie strukturalne na jakieś klasy czy obiekty ( pomijając to, że większość ogłoszeń o pracę programisty PHP tego wymaga ;) )? Jeżeli tak, w kilku następnych artykułach chciałbym Ci pokazać piękno i praktyczną stronę OOP – a przy okazji nauczyć jego podstawowych reguł.

Zacznijmy od tego, że programowanie obiektowe w PHP można wykorzystać na różne sposoby. Część z tych sposobów pozwala tworzyć niezwykłe systemy, a część – jest czasami sztuką dla sztuki ( co wprowadza więcej zamieszania niż pożytku ). Jednym z tych sensownych zastosowań, jest tworzenie niezależnych komponentów – swego rodzaju „klocków”, które raz napisane, mogą zostać wykorzystane w wielu projektach ( i przez różnych programistów ). Jeśli zostanie to dobrze zrobione, będą one wygodne, funkcjonalne – i pozwolą zaoszczędzić mnóstwo czasu. I chociaż jest to jedynie preludium tego, co można uzyskać dzięki OOP ( w połączeniu z odpowiednim sposobem myślenia, dobrze zaprojektowaną architekturą MVC, wyjątkami czy automatycznymi systemami testowania aplikacji ), to już tylko ten etap pozwoli Ci inaczej spojrzeć na Twoje projekty.

A więc do dzieła !

W celach naukowo-dydaktycznych, stworzymy bardzo prosty komponent do autentykacji. Jego zadaniem będzie weryfikacja użytkownika na podstawie loginu i hasła oraz zapisywanie odpowiednich zdarzeń ( kto się zalogowal, kiedy, etc ) do pliku logów. Tak stworzony komponent będzie można wykorzystywać w dowolnej aplikacji PHP – niezależnie czy będzie oparta na klasycznym kodzie spaghetti czy architekturze MVC ( i niezależnie od rodzaju interfejsu ).

Od czego zacząć? Przede wszystkim, w przypadku programowania obiektowego nasze zadanie jest podobne do zadania architekta – tzn, korzystając ze składni obiektowej musimy zaprojektować i zakodować strukturę naszego komponentu (tzw. klasę). W tej klasie określimy co nasz komponent ma robić ( np. sprawdzać login i hasło ) oraz jak ma to robić. Dopiero poźniej, mając taką klasę odpowiednim poleceniem tworzy się w programie docelowym konkretny „obiekt” - podobnie jak na podstawie dokumentacji architekta buduje się dom ( i dopiero wtedy można z niego korzystać ).

Krok 1 - projektujemy klasę i tworzymy pierwszy obiekt

W katalogu htdocs stworzmy podkatalog „klasy”, a w nim plik o nazwie : „Autentykacja.class.php” - będzie on zawierał naszą klasę . W pliku tym, wstawmy taki kod:

<?php

class Autentykacja{


}

?>

W ten sposób, zdefiniowaliśmy najprostszą klasę – i nadaliśmy jej nazwę „Autentykacja”. Klamerki wyznaczają ramy naszej klasy – cały kod dotyczący komponentu będziemy umieszczać właśnie w nich.

Spróbujmy naszą klasę przetestować i stwórzmy na jej podstawie obiekt ( aczkolwiek taki, który na razie nie będzie umiał nic robić ). Stwórzmy plik „testuj_komponent.php” w katalogu htdocs:

<?php

//podepnij plik, który zawiera klasę z której chcemy skorzystać
include('klasy/Autentykacja.class.php');

//na podstawie klasy Autentykacja stworz zmienną obiektową ( „obiekt”) o nazwie $komponent
$komponent = new Autentykacja();

?>

Kiedy uruchomimy nasz program w przeglądarce, na ekranie oczywiście nic nie zobaczymy. Nie zmienia to jednak faktu, że tworzy w pamięci nowy obiekt - komponent obsługujący autentykację :)

Krok 2 - dodajmy do obiektu tzw. właściwości

Co to są właściwości? W sumie, to nic nowego - są to po prostu zmienne, które umieszczamy wewnątrz naszego komponentu. Na początek, umieścimy dwie - "autor" i "wersja".

<?php

class Autentykacja{

  //zmienna na autora
  public $autor = 'Jan Kowalski' ;

  //zmienna na wersję
  public $wersja = 1.1 ;

}

?>

Zwróć uwagę, że kiedy tworzymy wewnątrz klasy jakieś zmienne, to zawsze określamy ich dostępność. W tym przypadku, obie zmienne mają dostępność ustawioną na "public". Oznacza to, że program korzystający z komponentu, będzie mógł odczytać ich wartość ( lub je zmienić ). A dokładniej, będzie można zrobić coś takiego:

<?php

//podepnij plik, który zawiera klasę z której chcemy skorzystać
include('klasy/Autentykacja.class.php');

//na podstawie klasy Autentykacja stworz zmienną obiektową ( „obiekt”) o nazwie $komponent
$komponent = new Autentykacja();

//odczytaj wartość zmiennej autor, znajdującej się wewnątrz obiektu $komponent
echo $komponent->autor; //Jan Kowalski
echo $komponent->wersja; //1.1

//zmien wartość zmiennej wersja, znajdującej się wewnątrz obiektu $komponent
$komponent->wersja='1.8';

echo $komponent->wersja; //1.8

?>

Na przykładzie powyżej widać, że dostaliśmy się do zmiennej w obiekcie - ale w dość nietypowy sposób. A dokładniej, użyliśmy składni: $obiekt STRZALKA nazwa_zmiennej ( STRZALKA - czyli myslinik i znak mniejszosci ). Czyli jak widać, najpierw musieliśmy podać nazwę obiektu, użyć operatora strzałki, i dopiero wtedy podać nazwę zmiennej. Ważne: zwróć uwagę, że przed nazwą zmiennej nie ma znaku dolara - jest tylko przed nazwą obiektu. Generalnie, należy zapamiętać że nawet jeśli kiedyś będziesz chciał zastosować coś takiego: $samochod->silnik->tlok->pozycja , to dolar i tak będzie tylko jeden ( w tym przypadku, odczytujemy wartość zmiennej pozycja, znajdującej się w obiekcie tlok, który znajduje się w obiekcie silnik, który to z kolei znajduje się w obiekcie samochod ;) ). Prawdę mówiąc, można zastosować dolar - ale da to całkiem inny efekt od zamierzonego.

Krok 3 - odetnijmy światu dostęp do niektórych właściwości

Analizując poprzedni kod można zauważyć, że programista korzystający z komponentu zmienił wartość zmiennej $wersja. Oczywiście, w tym przypadku zrobił to świadomie - ale równie dobrze mógł to zrobić niechcący ( np. w if'ie z powodu literówki, zamiast operatora porównania (==) użył operatora przypisania (=). ) Problem jednak w tym, że obiekt może z jakiegoś powodu brać pod uwagę wartość zmiennej $wersja - o czym nie wie programista który z niego korzysta.

Co w takim razie może zrobić projektant komponentu (twórca klasy), aby zabezpieczyć się przed pomyłkową modyfikacją zmiennej? No cóż - implementując klasę może określić rodzaj dostępu konkretnej zmiennej nie na "public", ale na "private" :

<?php

class Autentykacja{

  //zmienna na autora
  public $autor = 'Jan Kowalski' ;

  //zmienna na wersję - teraz jest ona PRYWATNA
  private $wersja = 1.1 ;

}

?>

Jaki da to efekt ? Jeżeli programista korzystający z obiektu będzie chciał dostać się do zmiennej określonej jako private - silnik PHP wygeneruje Fatal error ! . Przetestuj to - zmień public na private w klasie, zapisz zmiany i odśwież program testujący :). Następnie zakomentuj kod modyfikujący zmienną - zauważysz, że nie można jej także odczytać.

Krok 4 - zastosujmy "metodę" aby uzyskac dostęp do składowej obiektu

Założę się, że zwróciłeś teraz uwagę na ważny problem: co prawda nie musimy się obawiać że ktoś zmieni wartość zmiennej, ale z drugiej strony - nie można jej odczytać. W takiej sytuacji, jednym z najlepszych rozwiązań jest wyposażenie klasy w tzw. "metodę" - czyli po prostu funkcję, którą będzie mógł uruchomić sobie programista korzystający z obiektu.

<?php

class Autentykacja{

  //zmienna na autora
  public $autor = 'Jan Kowalski' ;

  //zmienna na wersję - teraz jest ona PRYWATNA
  private $wersja = 1.1 ;

  //funkcja, jako składnik obiektu, zawsze ma dostęp do wszystkich innych jego składników
  //(również prywatnych)

  public function zwrocWersje(){
    return $this->wersja;   //zwróć wartość zmiennej znajdującej się wewnątrz obiektu     
  }

}

?>

Ok, spróbujmy teraz przeanalizować kod:) Wewnątrz klasy ( w klamrach ) umieściliśmy funkcję. Podobnie jak w przypadku zmiennych, musieliśmy określić czy jest ona prywatna, czy publiczna. Ponieważ chcemy aby programista korzystający z komponentu mógł ją uruchomić, ustawiliśmy dostęp na "public". Zadaniem tej funkcji jest po prostu zwrócenie (return) wartości zmiennej, która jest składnikiem obiektu ( i jest prywatna ). W ten sposób, obeszliśmy nasz problem !

Przypuszczalnie zastanawiasz się jednak nad tym, czym jest ta zmienna (obiekt) $this . Nic prostszego - $this reprezentuje "obiekt, w którym właśnie jesteś" - dzięki temu, możesz dostać się do dowolnej jego właściwości lub skorzystać z jego metody. Gdybyśmy pominęli "$this->", i wpisali po prostu $wersja, otrzymalibyśmy ostrzeżenie - silnik PHP szukałbym zmiennej lokalnej wewnątrz naszej funkcji ( i oczywiście, w naszym przypadku by jej nie znalazł ).

No dobrze - a jak będzie wyglądało teraz korzystanie z naszej funkcji?

<?php

//podepnij plik, który zawiera klasę z której chcemy skorzystać
include('klasy/Autentykacja.class.php');

//na podstawie klasy Autentykacja stworz zmienną obiektową ( „obiekt”) o nazwie $komponent
$komponent = new Autentykacja();

//odczytaj wartość zmiennej autor, znajdującej się wewnątrz obiektu $komponent
echo $komponent->autor; //Jan Kowalski
//echo $komponent->wersja; // POKAZAŁOBY BŁĄD

//$komponent->wersja='1.8'; //tego też nie można zrobić


echo $komponent->zwrocWersje(); //1.1 - to już zadziała

?>

Jak widać w kodzie powyżej - aby uruchomić metodę danego obiektu, wystarczy podać jego nazwę, naszą znajomą strzałkę, a następnie funkcję :)

Krok 5 - dodajmy najważniejszą funkcjonalność

Nasz obiekt - jak sama nazwa wskazuje, ma zajmować się autentykacją - czyli sprawdzać, czy ktoś podał odpowiedni login i hasło. Teraz się tym zajmiemy - przy czym dla celów dydaktycznych, nasz komponent będzie zawierał tylko jeden login i hasło zapisane na stałe ( problem ten wyeliminujemy w następnym artykule, kiedy poznamy mechanizm konstruktora ).

<?php

class Autentykacja{

  //zmienna na autora
  public $autor = 'Jan Kowalski' ;

  //zmienna na wersję - teraz jest ona PRYWATNA
  private $wersja = 1.1 ;



  //zmienna z loginem
  private $login = 'adminek' ;

  //zmienna z haslem - dziewczyna admina ;)
  private $haslo = 'kunegunda' ;

  //funkcja, jako składnik obiektu, zawsze ma dostęp do wszystkich innych jego składników
  //(również prywatnych)

  public function zwrocWersje(){
    return $this->wersja;   //zwróć wartość zmiennej znajdującej się wewnątrz obiektu     
  }

  //funkcja, jako składnik obiektu, zawsze ma dostęp do wszystkich innych jego składników
  //(również prywatnych)

  public function test($login,$haslo){
    if (trim($login))=='') return false; //wymagane
    if (trim($haslo))=='') return false; //wymagane
    if ($this->login==$login and $this->haslo==$haslo){
      return true;
    }else{
      return false;
    }

    
  }

}

?>

A jak będzie wyglądało korzystanie z naszego komponentu w praktyce?

<?php

//podepnij klasę z komponentem
include('klasy/Autentykacja.class.php');

//na podstawie klasy Autentykacja stworz zmienną obiektową ( „obiekt”) o nazwie $komponent
$komponentAutentykacji = new Autentykacja();

if ($komponentAutentykacji->test($_POST['login'],$_POST['haslo'])){
  echo "witaj w systemie";
}else{
  echo "zly login lub haslo!";
}


?>

Krok 6 - kropka nad "i"

Przypuszczam, że teraz sobie myślisz: no dobrze, całkiem ładnie to wygląda...ale po co tyle pisania, jeśli można było użyć zwykłego if'a ! No cóż, gdybyśmy mieli poprzestać na tak prostym obiekcie, to miałbyś całkowitą rację. Było by to wytaczanie armaty na mrówkę. Ale dodajmy dodatkową funkcjonalność - coś, co warto robić w każdym projekcie: logowanie zdarzeń.

<?php

class Autentykacja{

  //zmienna na autora
  public $autor = 'Jan Kowalski' ;

  //zmienna na wersję - teraz jest ona PRYWATNA
  private $wersja = 1.1 ;



  //zmienna z loginem
  private $login = 'adminek' ;

  //zmienna z haslem - dziewczyna admina ;)
  private $haslo = 'kunegunda' ;

  private $plikLogow = 'logi.txt' ;

  //funkcja, jako składnik obiektu, zawsze ma dostęp do wszystkich innych jego składników
  //(również prywatnych)

  public function zwrocWersje(){
    return $this->wersja;   //zwróć wartość zmiennej znajdującej się wewnątrz obiektu     
  }

  //funkcja, jako składnik obiektu, zawsze ma dostęp do wszystkich innych jego składników
  //(również prywatnych)

  public function test($login,$haslo){
    if (trim($login))==''){
      $this->log("Podano pusty login");
      return false; //wymagane
    }
    if (trim($haslo))==''){
      $this->log("Podano puste haslo");
      return false; //wymagane
    }
    if ($this->login==$login and $this->haslo==$haslo){
      $this->log("Podano bledny login lub haslo");
      return true;
    }else{
      $this->log("Wykonano poprawna autentykację");
      return false;
    }

    
  }

  //funkcja, jako składnik obiektu, zawsze ma dostęp do wszystkich innych jego składników
  //(również prywatnych)

  private function log($tresc){
         file_put_contents($this->plikLogow,date("Y-m-d H:i:s")." ".$tresc ."\n");          
  }

}

?>

Dzięki takiej prostej modyfikacji masz pewność, że jeśli używasz tego komponentu, to w pliku zdarzeń rejestrujesz to, co się dzieje. Z jednej strony oszczędzasz czas ( nie musisz za każdym razem pisać tego samego kodu ), a z drugiej - masz pewność że proces autentykacji zawsze działa tak samo.