Lekcja 25 (OOP 5): Słowo Kluczowe static - Właściwości i Metody Statyczne

Witaj w piątej lekcji naszego modułu o Programowaniu Obiektowym w PHP! Do tej pory omawialiśmy składowe klas (właściwości i metody), które są związane z konkretnymi instancjami (obiektami) tych klas. Każdy obiekt miał swój własny zestaw wartości dla właściwości, a metody operowały na stanie tego konkretnego obiektu za pomocą $this. Dzisiaj wprowadzimy nowe pojęcie: składowe statyczne, czyli właściwości i metody, które należą do samej klasy, a nie do jej poszczególnych obiektów. Do ich definiowania używa się słowa kluczowego static.

Zrozumienie statycznych właściwości i metod jest ważne, ponieważ pozwalają one na implementację funkcjonalności, które są wspólne dla wszystkich instancji danej klasy lub które powinny być dostępne bez konieczności tworzenia obiektu. Przykłady to liczniki instancji, metody fabrykujące, metody pomocnicze (utility methods) czy przechowywanie globalnej konfiguracji dla klasy. Dowiemy się, jak deklarować, odwoływać się i kiedy stosować składowe statyczne, a także jakie są ich ograniczenia.

Czym Są Składowe Statyczne?

W standardowym podejściu obiektowym, które omawialiśmy do tej pory, każda właściwość i metoda jest związana z konkretnym obiektem (instancją klasy). Na przykład, jeśli mamy klasę Samochod i dwa obiekty $fiat i $audi, to każdy z nich ma własną wartość właściwości $kolor czy $predkoscAktualna.

Składowe statyczne (static members) działają inaczej. Są one związane z samą klasą, a nie z jej indywidualnymi instancjami. Oznacza to, że:

Składowe statyczne są deklarowane za pomocą słowa kluczowego static umieszczonego po modyfikatorze dostępu (public, protected lub private).

Właściwości Statyczne (Static Properties)

Właściwość statyczna jest zmienną należącą do klasy, a nie do obiektu. Jest przechowywana centralnie i współdzielona przez wszystkie instancje tej klasy. Inicjalizuje się ją przy pierwszym załadowaniu klasy.

Deklaracja właściwości statycznej:

<?php
class MojaKlasaStatyczna
{
    // Publiczna właściwość statyczna
    public static $licznik = 0;

    // Prywatna właściwość statyczna z wartością domyślną
    private static $nazwaAplikacji = "Super Aplikacja";

    // Chroniona właściwość statyczna
    protected static $wersja = "1.0";
}
?>

Dostęp do Właściwości Statycznych

Do właściwości statycznych odwołujemy się za pomocą operatora zasięgu klasy (Scope Resolution Operator), którym są dwa dwukropki ::, poprzedzone nazwą klasy.

Przykład użycia właściwości statycznej jako licznika instancji:

<?php
class ObiektLiczony
{
    public static int $liczbaStworzonychObiektow = 0;
    public string $nazwaInstancji;

    public function __construct(string $nazwa)
    {
        $this->nazwaInstancji = $nazwa;
        // Inkrementacja statycznego licznika przy tworzeniu każdego obiektu
        self::$liczbaStworzonychObiektow++;
        // Można też: ObiektLiczony::$liczbaStworzonychObiektow++;
        // lub: static::$liczbaStworzonychObiektow++;
        echo "Utworzono obiekt: " . $this->nazwaInstancji . ". Łączna liczba obiektów: " . self::$liczbaStworzonychObiektow . "<br>";
    }

    public static function ileObiektow(): int
    {
        return self::$liczbaStworzonychObiektow;
    }
}

// Dostęp do publicznej właściwości statycznej spoza klasy (przed utworzeniem obiektów)
echo "Początkowa liczba obiektów: " . ObiektLiczony::$liczbaStworzonychObiektow . "<br>"; // Wynik: 0

$obj1 = new ObiektLiczony("Alfa");
$obj2 = new ObiektLiczony("Beta");
$obj3 = new ObiektLiczony("Gamma");

// Dostęp do publicznej właściwości statycznej spoza klasy (po utworzeniu obiektów)
echo "Aktualna liczba obiektów (przez właściwość): " . ObiektLiczony::$liczbaStworzonychObiektow . "<br>"; // Wynik: 3

// Dostęp do licznika przez metodę statyczną (lepsza praktyka, jeśli licznik byłby np. prywatny)
echo "Aktualna liczba obiektów (przez metodę): " . ObiektLiczony::ileObiektow() . "<br>"; // Wynik: 3

// Zmiana wartości właściwości statycznej wpływa na wszystkie odwołania
ObiektLiczony::$liczbaStworzonychObiektow = 100;
echo "Liczba obiektów po ręcznej zmianie: " . ObiektLiczony::ileObiektow() . "<br>"; // Wynik: 100
?>

W tym przykładzie $liczbaStworzonychObiektow jest współdzielona. Każde utworzenie nowego obiektu ObiektLiczony inkrementuje tę samą zmienną statyczną.

Typowanie Właściwości Statycznych

Podobnie jak zwykłe właściwości, właściwości statyczne również mogą (i powinny) być typowane od PHP 7.4.

<?php
class Konfiguracja
{
    public static string $urlAplikacji = "http://localhost";
    public static int $domyslnyLimitWynikow = 20;
    public static bool $trybDebugowania = false;
}

echo "URL: " . Konfiguracja::$urlAplikacji . "<br>";
Konfiguracja::$trybDebugowania = true; // Zmiana wartości
?>

Metody Statyczne (Static Methods)

Metoda statyczna jest funkcją należącą do klasy, którą można wywołać bezpośrednio na klasie, bez konieczności tworzenia obiektu tej klasy. Metody statyczne są często używane jako metody pomocnicze (utility methods), metody fabrykujące (factory methods) lub do operowania na statycznych właściwościach klasy.

Kluczowe cechy metod statycznych:

Deklaracja metody statycznej:

<?php
class NarzedziaMatematyczne
{
    public static float $PI = 3.1415926535;

    // Metoda statyczna do obliczania pola koła
    public static function obliczPoleKola(float $promien): float
    {
        // Odwołanie do statycznej właściwości $PI za pomocą self::
        return self::$PI * $promien * $promien;
        // Można też: NarzedziaMatematyczne::$PI * ...
    }

    // Metoda statyczna do dodawania dwóch liczb
    public static function dodaj(float $a, float $b): float
    {
        return $a + $b;
    }

    // Metoda niestatyczna (dla porównania)
    public function jakasMetodaNiestatyczna(): void
    {
        echo "To jest metoda niestatyczna. Mogę użyć PI: " . self::$PI . "<br>";
        // echo $this->jakasWlasciwosc; // Gdyby istniała właściwość niestatyczna
    }
}

// Wywołanie metod statycznych bez tworzenia obiektu
$promienKola = 5.0;
$pole = NarzedziaMatematyczne::obliczPoleKola($promienKola);
echo "Pole koła o promieniu " . $promienKola . " wynosi: " . $pole . "<br>";

$suma = NarzedziaMatematyczne::dodaj(10, 25);
echo "Suma 10 i 25 to: " . $suma . "<br>";

// Dostęp do publicznej właściwości statycznej
echo "Wartość PI używana przez klasę: " . NarzedziaMatematyczne::$PI . "<br>";

// Aby wywołać metodę niestatyczną, potrzebujemy obiektu:
$narzedziaObj = new NarzedziaMatematyczne();
$narzedziaObj->jakasMetodaNiestatyczna();
?>

Metody Fabrykujące (Factory Methods) jako Metody Statyczne

Metody statyczne są często używane do implementacji wzorca projektowego Fabryka (Factory). Metoda fabrykująca to metoda, której zadaniem jest tworzenie i zwracanie obiektów. Użycie statycznej metody fabrykującej pozwala na bardziej elastyczne tworzenie obiektów, ukrycie logiki konstrukcji lub zwracanie różnych typów obiektów w zależności od parametrów.

<?php
class Logger
{
    private string $typ;

    // Prywatny konstruktor, aby wymusić tworzenie przez metodę fabrykującą
    private function __construct(string $typ)
    {
        $this->typ = $typ;
        echo "Utworzono logger typu: " . $this->typ . "<br>";
    }

    public function log(string $wiadomosc): void
    {
        echo "[" . $this->typ . "] " . $wiadomosc . "<br>";
    }

    // Statyczna metoda fabrykująca dla loggera plikowego
    public static function createFileLogger(): Logger
    {
        // Tutaj mogłaby być logika inicjalizacji loggera plikowego
        return new self("Plikowy"); // Użycie self do stworzenia instancji tej samej klasy
    }

    // Statyczna metoda fabrykująca dla loggera bazodanowego
    public static function createDatabaseLogger(): Logger
    {
        // Tutaj mogłaby być logika inicjalizacji loggera bazodanowego
        return new self("Bazodanowy");
    }
}

// Tworzenie obiektów za pomocą metod fabrykujących
$loggerPlikowy = Logger::createFileLogger();
$loggerPlikowy->log("To jest wiadomość do pliku.");

$loggerBazy = Logger::createDatabaseLogger();
$loggerBazy->log("To jest wiadomość do bazy danych.");

// $bezposredniLogger = new Logger("Test"); // BŁĄD! Fatal error: Uncaught Error: Call to private Logger::__construct()
?>

W tym przykładzie konstruktor jest prywatny, co uniemożliwia bezpośrednie tworzenie obiektów Logger za pomocą new spoza klasy. Obiekty muszą być tworzone przez publiczne statyczne metody fabrykujące createFileLogger() i createDatabaseLogger().

Słowo Kluczowe self vs static w Kontekście Statycznym

Wewnątrz metod klasy (zarówno statycznych, jak i niestatycznych) do odwoływania się do składowych statycznych tej samej klasy możemy używać self:: lub static::.

Różnica staje się istotna przy dziedziczeniu i nadpisywaniu metod statycznych (lub gdy metoda statyczna klasy bazowej odwołuje się do innej metody statycznej, która może być nadpisana w klasie potomnej).

Przykład ilustrujący różnicę między self a static (LSB):

<?php
class KlasaBazowa
{
    protected static string $nazwaKlasy = "KlasaBazowa";

    public static function ktoJestemSelf(): void
    {
        echo "self::nazwaKlasy to: " . self::$nazwaKlasy . "<br>";
    }

    public static function ktoJestemStatic(): void
    {
        echo "static::nazwaKlasy to: " . static::$nazwaKlasy . "<br>";
    }
}

class KlasaPotomna extends KlasaBazowa
{
    // Nadpisujemy (lub raczej "ukrywamy", bo właściwości statyczne nie są tak naprawdę nadpisywane w sensie polimorfizmu)
    // statyczną właściwość rodzica. Każda klasa ma swoją własną kopię statycznej właściwości.
    protected static string $nazwaKlasy = "KlasaPotomna"; 
}

KlasaBazowa::ktoJestemSelf();   // Wynik: self::nazwaKlasy to: KlasaBazowa
KlasaBazowa::ktoJestemStatic(); // Wynik: static::nazwaKlasy to: KlasaBazowa

echo "<hr>";

KlasaPotomna::ktoJestemSelf();   // Wywołujemy metodę z KlasaBazowa, ale w kontekście KlasaPotomna
                                // self::$nazwaKlasy w KlasaBazowa nadal odnosi się do KlasaBazowa::$nazwaKlasy
                                // Wynik: self::nazwaKlasy to: KlasaBazowa

KlasaPotomna::ktoJestemStatic(); // Wywołujemy metodę z KlasaBazowa, ale w kontekście KlasaPotomna
                                 // static::$nazwaKlasy (dzięki LSB) odniesie się do KlasaPotomna::$nazwaKlasy
                                 // Wynik: static::nazwaKlasy to: KlasaPotomna
?>

Późne Wiązanie Statyczne (LSB) za pomocą static:: jest bardzo użyteczne, gdy chcemy, aby metody statyczne dziedziczone z klasy bazowej operowały na składowych statycznych klasy potomnej, która je wywołuje.

Kiedy Używać Składowych Statycznych?

Składowe statyczne są przydatne w określonych sytuacjach, ale nie należy ich nadużywać, ponieważ mogą prowadzić do kodu trudniejszego w testowaniu i mniej elastycznego (wprowadzają globalny stan lub zachowanie).

Dobre przypadki użycia dla właściwości statycznych:

Dobre przypadki użycia dla metod statycznych:

Czego unikać:

Stałe Klasowe (Class Constants)

Oprócz właściwości statycznych, klasy mogą również definiować stałe klasowe za pomocą słowa kluczowego const. Stałe klasowe, podobnie jak właściwości statyczne, należą do klasy, a nie do obiektu. Ich wartość jest ustalana w momencie definicji i nie może być później zmieniona.

Do stałych klasowych odwołujemy się również za pomocą operatora zasięgu klasy :: (NazwaKlasy::NAZWA_STALEJ lub self::NAZWA_STALEJ).

<?php
class UstawieniaAplikacji
{
    public const WERSJA_APLIKACJI = "2.5.1";
    public const DOMYSLNY_JEZYK = "pl";
    private const KLUCZ_API_WEWNETRZNY = "bardzoTajnyKlucz123";

    public static function getInfo(): string
    {
        return "Aplikacja w wersji: " . self::WERSJA_APLIKACJI . ", język: " . self::DOMYSLNY_JEZYK;
    }

    public function getKlucz(): string
    {
        // Dostęp do prywatnej stałej z metody niestatycznej
        return self::KLUCZ_API_WEWNETRZNY; 
    }
}

echo "Wersja: " . UstawieniaAplikacji::WERSJA_APLIKACJI . "<br>";
echo UstawieniaAplikacji::getInfo() . "<br>";

$ust = new UstawieniaAplikacji();
echo "Klucz (przez obiekt): " . $ust->getKlucz() . "<br>";
// echo UstawieniaAplikacji::KLUCZ_API_WEWNETRZNY; // BŁĄD! Fatal error: Cannot access private constant
?>

Stałe klasowe są idealne do definiowania wartości, które są niezmienne i związane z klasą, np. wersje, domyślne konfiguracje, nazwy kluczy itp. W odróżnieniu od właściwości statycznych, nie mogą być one modyfikowane po zdefiniowaniu.

Podsumowanie Lekcji

W tej lekcji nauczyliśmy się, czym są składowe statyczne w PHP – właściwości i metody deklarowane za pomocą słowa kluczowego static. Zrozumieliśmy, że należą one do samej klasy, a nie do jej poszczególnych instancji. Dowiedzieliśmy się, jak deklarować statyczne właściwości i metody, jak się do nich odwoływać za pomocą operatora zasięgu klasy :: oraz słów kluczowych self:: i static:: (wprowadzając pojęcie Późnego Wiązania Statycznego - LSB).

Omówiliśmy typowe przypadki użycia składowych statycznych, takie jak liczniki, metody pomocnicze czy metody fabrykujące, a także wskazaliśmy, kiedy należy ich unikać. Na koniec wspomnieliśmy również o stałych klasowych (const) jako alternatywie dla niezmiennych właściwości statycznych.

Składowe statyczne są ważnym elementem programowania obiektowego w PHP, pozwalającym na implementację funkcjonalności na poziomie klasy. W następnej lekcji przejdziemy do jednego z najważniejszych filarów OOP – dziedziczenia.


Zadanie praktyczne

Stwórz klasę KonwerterJednostek, która będzie zawierać tylko metody statyczne do konwersji jednostek.

  1. Dodaj publiczną statyczną metodę caleNaCentymetry(float $cale): float, która przelicza cale na centymetry (1 cal = 2.54 cm).
  2. Dodaj publiczną statyczną metodę centymetryNaCale(float $cm): float, która przelicza centymetry na cale.
  3. Dodaj publiczną statyczną metodę fahrenheitNaCelsjusz(float $fahrenheit): float, która przelicza stopnie Fahrenheita na Celsjusza (C = (F - 32) * 5/9).
  4. Dodaj publiczną statyczną metodę celsjuszNaFahrenheit(float $celsjusz): float, która przelicza stopnie Celsjusza na Fahrenheita (F = C * 9/5 + 32).
  5. (Opcjonalnie) Dodaj publiczną stałą klasową PRZELICZNIK_CAL_CM = 2.54; i użyj jej w metodach konwersji cali/cm.
  6. Przetestuj wszystkie metody statyczne, wywołując je bezpośrednio na klasie, bez tworzenia obiektu.

Kliknij, aby zobaczyć przykładowe rozwiązanie
<?php
class KonwerterJednostek
{
    public const PRZELICZNIK_CAL_CM = 2.54;

    public static function caleNaCentymetry(float $cale): float
    {
        return $cale * self::PRZELICZNIK_CAL_CM;
    }

    public static function centymetryNaCale(float $cm): float
    {
        return $cm / self::PRZELICZNIK_CAL_CM;
    }

    public static function fahrenheitNaCelsjusz(float $fahrenheit): float
    {
        return ($fahrenheit - 32) * 5 / 9;
    }

    public static function celsjuszNaFahrenheit(float $celsjusz): float
    {
        return $celsjusz * 9 / 5 + 32;
    }
}

// Testowanie
$cale = 10.0;
$cm = KonwerterJednostek::caleNaCentymetry($cale);
echo $cale . " cali to " . round($cm, 2) . " cm.<br>";

$cm2 = 50.8;
$cale2 = KonwerterJednostek::centymetryNaCale($cm2);
echo $cm2 . " cm to " . round($cale2, 2) . " cali.<br>";

$fahrenheit = 68.0;
$celsjusz = KonwerterJednostek::fahrenheitNaCelsjusz($fahrenheit);
echo $fahrenheit . "°F to " . round($celsjusz, 2) . "°C.<br>";

$celsjusz2 = 25.0;
$fahrenheit2 = KonwerterJednostek::celsjuszNaFahrenheit($celsjusz2);
echo $celsjusz2 . "°C to " . round($fahrenheit2, 2) . "°F.<br>";

/* Przykładowy wynik:
10 cali to 25.4 cm.
50.8 cm to 20 cali.
68°F to 20°C.
25°C to 77°F.
*/
?>

Zadanie do samodzielnego wykonania

Stwórz klasę WalidatorDanych z metodami statycznymi do walidacji różnych typów danych.

  1. Dodaj publiczną statyczną metodę czyPoprawnyEmail(string $email): bool, która sprawdza, czy podany ciąg jest poprawnym adresem email (możesz użyć funkcji filter_var z filtrem FILTER_VALIDATE_EMAIL).
  2. Dodaj publiczną statyczną metodę czyPusty(string $tekst): bool, która sprawdza, czy podany tekst jest pusty (po usunięciu białych znaków z początku i końca za pomocą trim()).
  3. Dodaj publiczną statyczną metodę czyLiczbaCalkowita(mixed $wartosc): bool, która sprawdza, czy podana wartość jest liczbą całkowitą (możesz użyć is_int() lub filter_var z FILTER_VALIDATE_INT).
  4. Dodaj publiczną statyczną metodę czyWZakresie(int $liczba, int $min, int $max): bool, która sprawdza, czy podana liczba całkowita mieści się w zadanym zakresie (włącznie).
  5. Przetestuj wszystkie metody, podając różne dane wejściowe (poprawne i niepoprawne).


FAQ - Słowo Kluczowe static

Jaka jest główna różnica między właściwością statyczną a stałą klasową (const)?

Główna różnica polega na tym, że wartość właściwości statycznej może być zmieniana w trakcie działania programu, natomiast wartość stałej klasowej jest ustalana w momencie definicji i nie może być później zmodyfikowana. Stałe są idealne dla wartości, które nigdy się nie zmieniają, a właściwości statyczne dla danych na poziomie klasy, które mogą wymagać aktualizacji.

Czy mogę odwołać się do właściwości statycznej przez obiekt ($obiekt::$wlasciwoscStatyczna)?

W PHP jest to możliwe dla publicznych właściwości statycznych ($obiekt::NAZWA_WLASCIWOSCI), ale jest to uważane za mniej czytelne i odradzane. Preferowany sposób to odwołanie przez nazwę klasy: NazwaKlasy::$nazwaWlasciwosci lub NazwaKlasy::NAZWA_STALEJ. Dla metod statycznych również: NazwaKlasy::metodaStatyczna().

Czy metody statyczne mogą być abstract lub final?

Metody statyczne nie mogą być deklarowane jako abstract (ponieważ metody abstrakcyjne muszą być implementowane przez niestatyczne metody w klasach potomnych). Mogą być jednak deklarowane jako final (final public static function ...), co oznacza, że nie mogą być nadpisane (lub raczej "ukryte") przez metody statyczne o tej samej nazwie w klasach potomnych.

Co to jest "Późne Wiązanie Statyczne" (Late Static Binding - LSB) i dlaczego jest ważne?

LSB, używane przez static::, pozwala metodzie statycznej odwoływać się do składowych statycznych klasy, w kontekście której została faktycznie wywołana, a niekoniecznie klasy, w której została zdefiniowana. Jest to kluczowe w hierarchiach dziedziczenia, gdy chcemy, aby odziedziczona metoda statyczna operowała na specyficznych dla klasy potomnej danych statycznych. self:: zawsze odnosi się do klasy, w której jest napisane.

Czy właściwości statyczne są inicjalizowane za każdym razem, gdy tworzę obiekt?

Nie. Właściwości statyczne są inicjalizowane tylko raz, gdy klasa jest po raz pierwszy ładowana przez interpreter PHP, a nie przy tworzeniu każdego obiektu. Ich wartość jest następnie współdzielona przez wszystkie instancje (i jest dostępna nawet bez instancji).

Czy mogę używać modyfikatorów dostępu (public, protected, private) dla składowych statycznych?

Tak, modyfikatory dostępu działają dla składowych statycznych (właściwości, metod i stałych - choć dla stałych protected i private pojawiły się w PHP 7.1) analogicznie jak dla składowych niestatycznych, kontrolując ich widoczność w kontekście klasy, hierarchii dziedziczenia i spoza klasy.

Kiedy powinienem wybrać metodę statyczną zamiast globalnej funkcji?

Jeśli masz grupę funkcji pomocniczych, które są logicznie powiązane z pewną koncepcją lub domeną, umieszczenie ich jako metod statycznych w klasie może poprawić organizację kodu i uniknąć zaśmiecania globalnej przestrzeni nazw. Klasa działa wtedy jak przestrzeń nazw dla tych funkcji. Na przykład, klasa StringUtils z metodami statycznymi do operacji na ciągach znaków.