Proste problemy wymagają prostych rozwiązań - ta myśl przeszyła moje myśli dzisiejszego poranka, kiedy kolega z pracy pokazał mi proste, aczkolwiek genialne w swojej prostocie rozwiązanie problemu dynamicznych pól obiektów w PHP. Jak zapewne wiesz, język PHP pozwala na programiście na wiele sztuczek, czasami aż do przesady. Jedną z nich jest właśnie możliwość dynamicznego deklarowania pól obiektów.
Jak to wygląda w innych językach programowania
Spójrzmy na przykładowy kod aplikacji w języku C# w .NET Core:
using System;
namespace Demo
{
class KittyCat
{
public string name { get; set; }
}
class Program
{
static void Main(string[] args)
{
var MyKitty = new KittyCat();
MyKitty.name = "MyCat";
MyKitty.age = 12;
}
}
}
Podczas próby kompilacji takiego kodu (ale także podczas wpisywania zawartości ostatniej linii w metodzie Main) pojawia się błąd krytyczny:
[Error] [CS1061] 'KittyCat' does not contain a definition for 'age' and no accessible extension method 'age' accepting a first argument of type 'KittyCat' could be found (are you missing a using directive or an assembly reference?
Jest to jak najbardziej oczekiwane zachowanie w kontekście myślenia obiektowego. Tworząc obiekt na podstawie jego definicji nie możemy ot tak sobie zadecydować, że obiekt może mieć dodatkowe właściwości. Jednak w języku PHP sytuacja jest zupełnie odwrotna. Stwórzmy alternatywny kod w tymże języku:
<?php
declare(strict_types=1);
namespace Demo;
class KittyCat
{
public $name;
}
$myKitty = new KittyCat();
$myKitty->name = "MyCat";
$myKitty->age = 12;
var_dump($myKitty);
Uruchamiając powyższy kod otrzymamy wynik:
object(Demo\KittyCat)#1 (2) {
["name"]=>
string(5) "MyCat"
["age"]=>
int(12)
}
No to mamy klopsiki w sosie własnym.
Problemy wynikające z dynamicznie generowanych pól
Powyższe zachowanie języka PHP implikuje kilka negatywnych skutków, z którymi borykamy się my, programiści PHP. Po pierwsze: możliwość popełnienia literówki. Ze względu na to, że możemy odwołać się do dowolnego pola w obiekcie i nie uświadczymy żadnego błędu podczas uruchomienia kodu, jeżeli popełnimy literówkę i tego nie zauważymy, błąd może być ciężki do wychwycenia i poprawienia (zarówno teraz, jak i w przyszłości). Po drugie: działanie takie zachęca do wybierania drogi na skróty i ułatwia zaciemnienie kodu (przez niedbalstwo programisty). Zdarzają się bowiem sytuacje, kiedy programista woli dopisać na szybko dodatkowe pole zamiast utworzyć nową, pochodną klasę.
Kolega z pracy wskazał mi dzisiaj jeden prosty trick :D, dzięki któremu możemy się jednak przeciw temu zabezpieczyć.
Ciekawe wykorzystanie dostępnych metod magicznych
W języku PHP mamy metody magiczne, które wywoływane są w określonych momentach. Dwie z nich możemy tutaj z powodzeniem wykorzystać: __get oraz __set, które wywoływane są w chwili próby dostępu do nieistniejącego pola obiektu. Stwórzmy trait jak poniżej:
<?php
trait DisableDynamicProperties
{
public function __set($property, $value)
{
trigger_error("Can't modify not existing property $property in " . self::class, E_USER_ERROR);
}
public function __get($property)
{
trigger_error("Can't access not existing property $property in " . self::class, E_USER_ERROR);
}
}
Następnie dodajmy go do naszej klasy:
<?php
declare(strict_types=1);
namespace Demo;
trait DisableDynamicProperties
{
public function __set($property, $value)
{
trigger_error("Can't modify not existing property $property in " . self::class, E_USER_ERROR);
}
public function __get($property)
{
trigger_error("Can't access not existing property $property in " . self::class, E_USER_ERROR);
}
}
class KittyCat
{
use DisableDynamicProperties;
public $name;
}
$myKitty = new KittyCat();
$myKitty->name = "MyCat";
$myKitty->age = 12;
var_dump($myKitty);
Wywołując teraz kod z pliku otrzymamy komunikat:
Fatal error: Can't modify not existing property age in Demo\KittyCat in Z:\Users\rombarte\index.php on line 11
Świetna wiadomość!
Komentarze
Ten wpis nie posiada komentarzy.
Dodaj komentarz
Pola oznaczone gwiazdką (*) są wymagane. Komentarze są wstępnie moderowane i mogą nie pojawić się na stronie.