Pokazywanie post贸w oznaczonych etykiet膮 PHP. Poka偶 wszystkie posty
Pokazywanie post贸w oznaczonych etykiet膮 PHP. Poka偶 wszystkie posty

sobota, 13 listopada 2021

Core Code & Infrastruktura

Najpro艣ciej rzecz ujmuj膮c - Core Code - jest to kod niezale偶ny od infrastruktury i kontekstu wywo艂ania. Wzorce s艂u偶膮ce do jego implementacji to: Command, Serwis, Repozytorium, Fabryka, Read Model, Write Model (Encja) czy Value Object. Z drugiej strony mamy tytu艂ow膮 Infrastruktur臋 do kt贸rej nale偶膮 np. implementacje interfejs贸w repozytori贸w czy kontrolery HTTP/CLI. Stosuj膮c podzia艂 na Core Code i Infrastruktur臋 realizujemy wa偶ny cel: odseparowanie kodu realizuj膮cego funkcjonalno艣膰 od szczeg贸艂贸w technicznych. Istotnym elementem w poznaniu ka偶dego z tych wzorc贸w jest zrozumienie w jaki spos贸b dzia艂aj膮 ze sob膮 nawzajem, oraz na jakim etapie cyklu 偶ycia aplikacji wyst臋puj膮. W tym wpisie nie skupie si臋 na wspomnianych wzorcach, ale na istocie tytu艂owego podzia艂u na Core Code i Infrstruktur臋 - czyli dlaczego jest on istotny i jakie korzy艣ci przyniesie.  
 

Odseparowanie od Infrastruktury. Why even bother...?

    Odseparowanie od infrastruktury mo偶emy zrealizowa膰 poprzez stosowania wy偶ej wymienionych wzorc贸w. Jakby si臋 na tym zastanowi膰 to pilnuj膮c by kod dzieli艂 si臋 na Core Code i Infrastruktur臋, realizujemy g艂贸wne za艂o偶enia OOP - wprowadzanie warstw abstrakcji. Bior膮c za przyk艂膮d Serwis Aplikacyjny - czytaj膮c jego kod, nie musimy zaprz膮ta膰 sobie g艂owy szczeg贸艂ami technicznymi zapisu danych do bazu poniewa偶 Serwis posiada zale偶no艣膰 w postaci Interfejs Repozytorium, kt贸ra skutecznie ukrywa z艂o偶ono艣膰 tego zagadnienia. Je偶eli b臋dziemy potrzebowali tej wiedzy, po prostu wyszukamy Implementacje Repozytorium ukrywaj膮c膮 techniczne aspekty zapisy. Dzi臋ki temu nie jeste艣my zbyt wcze艣nie obci膮偶eni wi臋dz膮, kt贸rej na danym etapie w og贸le nie potrzebujemy. 

    Cz臋sto wymienian膮 zalet膮 odseparowania Core Code od Infrastruktury jest wymienialno艣膰 szczeg贸艂贸w implementacyjnych jak np. baza danych. Prawde m贸wi膮c wymiana bazy na inn膮 jest do艣膰 rzadko wyst臋puj膮ca czynno艣膰 w czasie 偶ycia projektu, ale je偶eli byliby艣my do niej zmuszeni np. z przyczyn wydajno艣ciowych to przy wydzielonej Warstwie Infrastruktury jeste艣my do tego zdolni por贸wnywalnie mniejszym nak艂adem pracy. Poj臋cie szczeg贸艂u implementacyjnego nie ogranicza si臋 jedynie do bazy danych, ale jesto to o wiele szersze spektrum, gdzie do Infrastruktury zaliczamy ca艂e frameworki, biblioteki, po艂膮czenia z zewn臋trznymu systemami. Od wszystkich tych zale偶no艣ci mo偶emy si臋 odgrodzi膰 warstw膮 abstrakcji co w nieznanej przysz艂o艣ci mo偶e si臋 okaza膰 nieocenion膮 zalet膮. Jak to powiedzia艂 Michael Feathers:  

"Avoid littering direct calls to library classes in your code. You might think that you’ll never change them, but that can become a self-fulfilling prophecy"

    Mo偶na wywnioskowa膰, 偶e to co robimy to izolowanie si臋 od technologii s艂u偶膮cej do realizowania funkcjonalno艣ci, kt贸ra nie definiuje funkcjonalno艣ci samej w sobie. Przez to, 偶e mamy fizycznie rozdzielone miejsca obs艂ugi zapisu do bazy danych konkretnej Encji i definicji samej Encji - utrzymanie projektu staje si臋 prostrze. Na przyk艂ad chc膮c podnie艣膰 wersje biblioteki obs艂uguj膮cej po艂膮czenie z baz膮 danych b臋dziemy operowali jedynie na Warstwie Infrastruktury nie ruszaj膮c przy tym kwestii biznesowych, w wyniku czego ryzyko nieumy艣lnego wprowadzenia zmiany funkcjonalno艣ci biznesowej znacz膮co maleje.

Fizyczne rozdzielenie - czyli tworzenie oddzielnych klas domeny problemu oraz infrastrukturowych w osobnych Warstwach. Warstwy wed艂ug standardowego modelu dziel膮 si臋 na Aplikacj臋, Domen臋 oraz Infrastruktur臋 - realizowane s膮 przez namespace'y. Jest jasno okre艣lone, jak膮 wiedz臋 mog膮 posiada膰 klasy w danej warstwie o bytach (klasy, interfejsy, enum) z innej warstwy. Jako, 偶e PHP natywnie nie ma zaimplementowanego mechanizumu enkapsulacji przestrzeni nazw, nic nie stoi na przeszkodzie by zaimplementowa膰 z艂y kierunek komunikacji np. Wastwa Aplikacji posiada odwo艂ania do klas z Warstwy Infrastruktury. Jedynie dyscyplina developer贸w w przestrzeganiu zasad mo偶e pilnowa膰 tej poprawno艣ci (by膰 mo偶e przy pomocy analizy statycznej).  

    Z wyizolowan膮 Warstw膮 Infrastruktury sprawiamy, 偶e mo偶emy stosowa膰 TDD bo klasy z Core Code s膮 艂atwe w testowaniu jednostkowym. Dodatkowy nak艂ad pracy mo偶e prowadzi膰 do powstania modelu domenowego wedle DDD. Stosowanie tych technik niew膮tpliwie przyczyni si臋 do jako艣ci wytwarzanego oprogramowania. Dodatkowo, tworzenie klas z my艣l膮 o odseparowanej infrastrukturze, naturalnie prowadzi do implementacji popularnego wzorca architektonicznego Porty i Adaptery.  

Wewn臋trzna jako艣膰 oprogramowania

    Przez Jako艣膰 Wewn臋trzn膮 rozumie si臋 szerokopoj臋t膮 czytelno艣膰 kodu, 艂atwo艣膰 w utrzymaniu i rozwijaniu aplikacji. Wydaje si臋, 偶e na jako艣ci kodu zale偶y g艂贸wnie programistom, gdy偶 dla ludzi biznesu jej istotno艣膰 na pierwszy rzut oka nie jest taka oczywista. Jako 偶e oprogramowanie tworzone jest w spos贸b iteracyjny, pisane klasy musz膮 realizowa膰 sp贸jne funkcjonalno艣ci, tworzy膰 lu藕no powi膮zane wi臋ksze struktury i by膰 pokryte nale偶yt膮 ilo艣ci膮 test贸w jednostkowych. Nigdy nie wiadomo jakie zmiany najdejd膮 w kolejnych iteracjach, wi臋c musimy stara膰 si臋 tworzy膰 solidne elementy budulcowe (klasy/grupy klas/modu艂y) w ca艂ym projekcie. Utrzymuj膮c 艣cis艂膮 dyscypline przy tworzeniu wysokiej jako艣ci oprogramowania sprawiamy, 偶e b臋dziemy mogli modyfikowa膰 zachowanie oprogramowania w spos贸b przewidywalny i bezpieczny, minimalizuj膮c ryzyko, 偶e zmiana b臋dzie wymaga艂a du偶ej przer贸bki. 

    Oprogramowanie jest oczywi艣cie tworzone do realizowania cel贸w biznesowych, ale musi ono te偶 s艂u偶y膰 developerom. Mogliby艣my napisa膰 kiepski kod spe艂niaj膮cy w 100% wymagania klienta (o wysokiej jako艣ci zewn臋trznej), ale wprowadzanie zmian w oprogramowaniu niskiej jako艣ci jest skomplikowane, nieprzewidywalne i ryzykowne - z czasem wymagaj膮ce coraz wi臋kszej uwagi developera i nak艂adu czasu. 

Podsumowuj膮c: 

  1. im 艂atwiej i pewniej wprowadza膰 zmiany w oprogramowaniu tym lepiej
  2. by 艂atwiej wprowadza膰 zmiany w oprogramowaniu trzeba pisa膰 kod o wysokiej jako艣ci wewn臋trznej
  3. istnieje szereg technik do osi膮gni臋cia wysokiej jako艣ci kodu   

    Jak wida膰 tematy te s膮 pochodn膮 wydzielania Core Code i wzajemnie si臋 zaz臋biaj膮. Stosuj膮c wzorce Rdzenia Aplikacji b臋dziemy mogli 艂atwo przetestowa膰 jednostkowo tworzone obiekty, co dowodzi 偶e kod jest modu艂owy oraz niezale偶ny od kontekstu co jest r贸wnoznaczne z wysok膮 jako艣ci膮.    

Wzorce Projektowe

    Wymienione na samym pocz膮tku wzorce, pomagaj膮 w pisaniu kodu odseparowanego od infrastruktury. Opracowane by艂y na podstawie sumy do艣wiadcze艅 innych programist贸w w budowaniu aplikacji webowych. S膮 one dla programisty zestawem jasno zdefiniowanych i sprawdzonych element贸w budulcowych. Stosowanie wzorc贸w sprawia, 偶e kod jest czytelny, modu艂owy i 艂atwy w testowaniu. Developer znaj膮cy ca艂膮 palet臋 wzorc贸w mo偶e korzysta膰 z niej jak przybornika z narz臋dziami - dobieraj膮c narz臋dzie najlepiej dopasowane do probelmu. Nie trzeba wtedy wymy艣la膰 ko艂a na nowo i zastanawia膰 si臋 nad tym czy zastosowane rozwi膮zanie jest dobre. Maj膮c sprawdzony zestaw wzorc贸w oraz posiadaj膮c praktyczn膮 wiedz臋 ich stosowania jeste艣my w stanie modyfikowa膰 oprogramowanie szybciej i pewniej. 

    W r臋kach developera jest to czy poszerza on swoj膮 wiedz臋 w dziedzinie wytwarzania oprogramowania wysokiej jako艣ci. Zg艂臋bianie informacji na temat wzorc贸w projektowych sprawia 偶e, zaczynamy widzie膰 wi臋cej. Implementuj膮c kod mo偶emy wybiec w przyszo艣膰, przewiduj膮c jakie b臋dzie ni贸s艂 ze sob膮 konsekwencje, przed jakimi potencjalnymi problemami nas chroni, w jaki spos贸b sprawia 偶e jest wysokiej jako艣ci. Korzystaj膮c z danego wzorca dziesi膮tki b膮d藕 setki razy, pos艂ugujemy si臋 nim coraz lepiej, widzimy jakie warianty si臋 najlepiej sprawdzaj膮, a w jakich sytuacjach lepiej go nie stosowa膰.

    Mo偶na powiedzie膰, 偶e przybornik z narz臋dziami developera uzupe艂niany jest o nowe elementy gdy poszerza on swoj膮 wiedz臋 - teoretyczn膮 i praktyczn膮. Warto szlifowa膰 swoje umiej臋tno艣ci pos艂ugiwania si臋 tymi narz臋dziami gdy偶 bezpo艣rednio przek艂adaj膮 si臋 na umiej臋tno艣ci tworzenia kodu wysokiej jako艣ci, czyni膮c nasza prac臋 prostsz膮.

Na sam koniec wrzucam ciekawy cytat Matthias'a Noback'a daj膮cy wiele do my艣lenia:   

 “Software always becomes a mess, even if you follow all the best practices for software design  but I’m convinced that if you follow these practices it will take more time to become a mess  and this is a huge competitive advantage”

殴r贸d艂a 

Wpis jak i ca艂a koncepcja zosta艂a zaczerpni臋ta przede wszystkim z przemy艣le艅 Matthias'a Noback'a (blog) zawartych w jego dw贸ch ksi膮偶kach:

馃摃 Advanced Web Application Architecture (2020)
馃摃 Object Design Style Guide (2019) 

Wzmianka o Wewn臋trznej jako艣ci oprogramowania zosta艂a zaczerpni臋ta z ksi膮偶ki:

馃摃 Growing Object-Oriented Software, Guided by Tests (2009)


niedziela, 7 listopada 2021

ID w instrukcjach warunkowych

Przyk艂ad

    Biznes decyduje si臋 by konkretni u偶ytkownicy mieli naliczani rabat do robionego zam贸wienia. W tym wpisie nie b臋d臋 si臋 skupia艂 na naliczaniu samego rabatu, tylko na to komu ma on zosta膰 przyznany. Poni偶ej znajduje si臋 najprostsza implementacja tego zadania: 

 

namespace App\Context\Application;

final class OrderService 
{
	// ...
	public function createOrder(
		//...
		User $user	
	): void {
		// ...
		if (in_array($user->id(), [101,102])) {
			// give a discount
		}
		// ...
	}
} 

 

    Jak wida膰 rabat dotyczy tylko u偶ytkownik贸w z ID 101 & 102.Serwis Aplikacyjny ma teraz wiedz臋 o konkretnych ID z bazy MySQL, kt贸re nie powinny wychodzi膰 poza Warstw臋 Infrastruktury. Mo偶na by utworzy膰 dodatkow膮 klas臋, kt贸ra hermetyzowa艂a by identyfikatory, ale tak czy inaczej musia艂aby si臋 znale藕膰 w Warstwie Aplikacji (lub co gorsza Domeny). Najprostrzym rozwi膮zaniem b臋dzie wprowadzenie wyspecjalizowanego Repozytorium sk艂adaj膮cego si臋 na interfejs:


namespace App\Context\Application\Repository;

interface UserDiscountRepository 
{
	public function discountAllowed(int $userId): bool;
}


Oraz jego implementacji InMemory:


namespace App\Context\Infrastructure\Repository;

use App\Context\Application\Repository\UserDiscountRepository;

final class UserDiscountInMemoryRepository implements UserDiscountRepository 
{
	public function discountAllowed(int $userId): bool 
	{
		return in_array($userId, [101,102]);
	}
}

 

    Wystarczy doda膰 zale偶no艣膰 w postaci UserDiscountRepository do Serwisu Aplikacyjnego. W przypadku gdy b臋dziemy rozwija膰 funkcjonalno艣膰, a u偶ytkownicy z naliczanym rabatem b臋d膮 dodawani do systemu dynamicznie - implementacja OrderService nie powinna ulec zmianie.

 

namespace App\Context\Application;

use App\Context\Application\Repository\UserDiscountRepository;

final class OrderService 
{
	public function __construct(
		private UserDiscountRepository $userDiscountRepository
	) {}
	
	// ...
	public function createOrder(
		//...
		User $user	
	): void {
		// ...
		if ($this->userDiscountRepository->discountAllowed($user->id())) {
			// give a discount
		}
		// ...
	}
}

sobota, 6 listopada 2021

殴r贸d艂o danych InMemory

Cz臋sto dodaj膮c nowe funkcjonalno艣ci opieramy si臋 na schemacie: 

  1. pobieranie danych ze bazy danych...
  2. ...przeprowadzanie na nich pewnych operacji

    Niekiedy dane te nie zmieniaj膮 si臋 w czasie, s膮 z g贸ry ustalone i w zwi膮zku z tym nie musz膮 by膰 umieszczane w zewn臋trznych bazach typu MySQL/Redis. Mo偶emy bardziej sk艂ania膰 si臋 ku przechowywaniu ich bezpo艣rednio w repozytorium kodu PHP. Przyk艂adowymi danymi tego typu mo偶e by膰 tablica asocjacyjna reprezentuj膮ca flow zmiany status贸w jakiej艣 konkretnej Encji. Tak mog艂aby prezentowa膰 si臋 jej przyk艂adowa implementacja:

namespace App\Context\Domain;

final class ChangeStatusProvider 
{
	public static function mapping(): array 
	{
		return [
			1 => 4,
			2 => 5,
			3 => 5
		];
	}
}

Albo z wykorzystaniem sta艂ych:

namespace App\Context\Domain;

interface ChangeStatusProvider 
{
	public const MAPPING = [
		1 => 4,
		2 => 5,
		3 => 5
	];
}

    Klasa tego typu sama w sobie nie nastr臋cza problem贸w, ale spos贸b jej wykorzystania przez klienta ju偶 mo偶e. Jako klienta mo偶emy wyobrazi膰 sobie Value Object, kt贸ry jest polem Encji, a ta elementem Agregatu. Mo偶na za艂o偶y膰, 偶e ta tablica asocjacyjna nie mo偶e znajdowa膰 si臋 bezpo艣rednio wewn膮trz Value Object'u poniewa偶 jest wykorzystywana r贸wnie偶 przez innego klienta w zupe艂nie innym kontek艣cie.

ValueObject wywo艂uje wewn臋trznie metod臋 statyczn膮 w celu pobrania danych.

Wspomniany Value Object b臋dzie odpowiada艂 za zwracanie nowego statusu, pyta膰 o niego b臋dzie Encja, a Encje - Agregat.  

namespace App\Context\Domain\ValueObject;

use App\Context\Domain\ChangeStatusProvider;

final class SomeValueObject 
{
	// ...
	public function nextStatus(): int
	{
		$newStatus = ChangeStatusProvider::MAPPING($this->status);
		//...
		return $newStatus;
	}
	//...
}

    Dla klienta (Encji) nie ma r贸偶nicy czy otrzymany nowy status zosta艂 wstrzykni臋ty przez konstruktor Value Object'u, czy tak jak teraz - pochodzi z metody statycznej. Nie ma znaczenia bo otrzymuje gotowy Value Object jako parametr konstruktora. Dla niego wygl膮da po prostu jak integralna cz臋艣膰 Value Object. Wszystko wi臋c wygl膮da dobrze do czasu...

Zmiany wymaga艅 biznesowych

    Okazuje si臋 偶e, z czasem Biznes oczekuje konfigurowalnych zmian stanu. Administratorzy aplikacji maj膮 mie膰 mo偶liwo艣膰 modyfikowania flow zmiany stanu wed艂ug okre艣lonych ram. Dla developer贸w b臋dzie to oznacza艂o, 偶e mapowania status贸w nie b臋d膮 d艂u偶ej realizowane przez klas臋/interfejs ChangeStatusProvider. Zostanie utworzona tabela w relacyjnej bazie danych + prosty interfejs do zarz膮dzania jej zawarto艣ci膮. 

    W obszarze aplikacji przedstawionym w przyk艂adzie, musimy dokona膰 zmian. Value Object musi teraz otrzymywa膰 dane z zewn膮trz (poprzez konstruktor). Dane o mapowaniu status贸w pochodzi膰 b臋d膮 z nowo utworzonego Repozytorium. Je偶eli instancje Value Object tworzymy wewn膮trz Serwisu Aplikacyjnego/Fabryki to tam b臋dzie musia艂o by膰 wstrzykni臋te wspomniane Repozytorium. Gorzej je偶eli odbywa si臋 to wewn膮trz Agregatu lub Encji - wtedy dane z Repozytorium b臋d膮 musia艂y by膰 przekazane przez kolejne warstwy co jest utrudnieniem w implementacji zmian (jest to ciekawy temat na inny wpis).     

    W tym przypadku, do tego typu implementacji nigdy by nie dosz艂o gdyby developer nie zdecydowa艂 si臋 na przechowywanie danych w metodzie statycznej (lub sta艂ych klasy). G艂贸wnym czynnikiem, kt贸ry za to odpowiada jest nierozwa偶anie danych z metody statycznej jako danych zewn臋trznych. Gdyby od razu zosta艂o przyj臋te takie za艂o偶enie, programista pewnie z automatu

1. utworzy艂by interfejs Repozytorium (Warstwa Aplikacji)

namespace App\Context\Application;

interface StatusRepository 
{
	/** @return array<int, int> */
	public function mapping(): array;
}

2. jego implementacje (Warstwa Infrastruktury)

namespace App\Context\Infrastructure;

use App\Context\Application\StatusRepository; 

final class StatusInMemoryRepository implements StatusRepository 
{
	/** @inheritDoc */
	public function mapping(): array 
	{
		return [
			1 => 4,
			2 => 5,
			3 => 5
		];
	}
}

3. dane do modeli domenowych (Encji/Value Object'贸w) wprowadzane by by艂y przez ich konstruktor/metody

namespace App\Context\Application;

final class AggregateFactory 
{
	public function __construct(
		// ...
		private StatusRepository $statusRepository,
		// ...
	) {}

	public function create(/*...*/): Aggregate 
	{
		// ...
		$someValueObject = new SomeValueObject(
			// ...
			$this->statusRepository->mapping()
			// ...
		);
		// ...
	}
}

 

    Dlatego istotne jest by - za ka偶dym razem gdy zamierzamy umie艣ci膰 dane w kodzie PHP - zastanowi膰 si臋 czy nie b臋dzie prowadzi艂o to do wy偶ej opisanych problem贸w w przysz艂o艣ci. Maj膮c to na uwadze, mo偶emy u艂atwi膰 sobie i innym utrzymanie i rozw贸j projektu. A wi臋c zamiast refaktoryzowa膰 metody statyczne w kierunku Wzorca Repozytorium, nale偶y zaimplementowa膰 od razu takie rozwi膮zanie. Dodatkowo nale偶y pilnowa膰 by dane do Agregat贸w, Encji, Value Object'贸w przekazywane by艂y przez ich konstruktory.   

Commandy Aplikacyjne niezale偶ne od infrastruktury (cz. 1)

    Za艂贸偶my, 偶e posiadamy obiekt Data Transfer Object np. Command. Obiekt tego typu zajmuje si臋 jedynie przechowywaniem danych i nie oferuj膮 偶adnych zachowa艅 poza udost臋pnianiem swojego stanu. Wykorzystujemy go do przekazywania parametr贸w do konkretnego Serwisu Aplikacyjnego realizuj膮cego funkcjonalno艣膰 biznesow膮. W uproszczonej formie klasa typu Command przechowuj膮ca dane potrzebne do utworzenia u偶ytkownika prezentowa艂aby si臋 nast臋puj膮co:     


namespace Application;

final class CreateUserCommand 
{
	public function __construct(
		public readonly string $email,
		public readonly int $type,
		public readonly \DateTimeImmutable $birthDate
	) {} 
}

     

    Jako, 偶e obiekty te tworzone s膮 na podstawie danych z poza granic aplikacji, np. z Request'a HTTP, mog膮 posiada膰 Fabryczne Metody Pomocnicze kt贸re tworz膮 instancj臋 na podstawie danych z m.in.:

  • JSON/XML z cia艂a Request'a HTTP
  • argument贸w Command'a CLI
  • obiektu Symfony Form
  • danych z pliku CSV

 

Budowanie Command na podstawie zewn臋trznych danych

    JSON na podstawie kt贸rego budujemy obiekty Command pochodzi z poza granic naszej aplikacji tzw. Outside World. Jest on wys艂any po HTTP pomi臋dzy r贸偶nymi aplikacjami.

JSON z poza granic aplikacji. Obiekt Command jest budowany na jego podstawie.
 

    Jak wynika z diagramu, Command budowany jest na podstawie danych z obiektu JSON. Pomimo tego, 偶e znajduje si臋 on w Warstwie Aplikacji, jego instancjonowanie ma miejsce w Warstwie Infrastruktury - w tym przypadku Kontrolerze HTTP. 

Przyjrzyjmy si臋 najpierw samemu obiektowy JSON

{
	"email": "john.doe@gmail.com",
	"user_type": 2,
	"birth_date": "1992-11-05"
}

Wiedza jak膮 powinna dysponowa膰 klasa kt贸ra b臋dzie go odczytywa膰 to:

  • jakie posiada klucze

  • jakiego typu warto艣ci dany klucz mo偶e przechowywa膰

Warto zaznaczy膰, 偶e te informacje pochodz膮 z wspomnianego Outside World. Oto przyk艂ad klasy odpowiadaj膮cej za mapowanie JSON'a do obiektu Command:

 

namespace Infrastructure;

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Application\CreateUserCommand;

final class UserController 
{
	public function createUserAction(Request $request): Response 
	{
		/** @var array $payload */
		$payload = $request->getParsedBody();
		
		$command = new CreateUserCommand(
			$payload['email'],
			$payload['user_type'],
			new \DateTimeImmutable($payload['birth_date'])
		);
	
		// ...
	}
}

 

    Jak wida膰 Kontroler z Warstwy Infrastruktury posiada wiedz臋 o strukturze zewn臋trznego obiektu, ale jest to nieco naiwna implementacja. Nie mo偶emy ufa膰 klientowi, co do tego czy dostarczy艂 prawid艂ow膮 struktur臋/dane. JSON mo偶e 馃挘 nie zawiera膰 danego klucza b膮d藕 馃挘 przechowywa膰 nieoczekiwane warto艣ci. Mogliby艣my ograniczy膰 si臋 do sprawdzenia czy dany klucz istnieje, w przeciwnym wypadku zadaj膮c NULL oczekuj膮c tym samym wyrzucenia TypeError (przechwycony gdzie艣 wy偶ej):

 

try {
	$command = new CreateUserCommand(
		$payload['email'] ?? null,
		$payload['user_type'] ?? null,
		isset($payload['birth_date']) 
        	? new \DateTimeImmutable($payload['birth_date'])
            : null
	);
	// ...
} catch (TypeError $e) {
	// ...
}

 

B膮d藕 u偶y膰 asercji (r贸wnie偶 rzucaj膮cych wyj膮tkiem InvalidArgumentException), kt贸re bardziej opisowo przedstawiaj膮 warunki kt贸re ma spe艂nia膰 poprawny Request Payload:

try {
	Assert::notEmptyString($payload['email'] ?? null);
	Assert::integerish($payload['user_type']  ?? null);
	Assert::stringDateTime($payload['birth_date']  ?? null);

	$command = new CreateUserCommand(
		$payload['email'],
		(int) $payload['user_type'],
		new \DateTimeImmutable($payload['birth_date'])
	);
	// ...
} catch (InvalidArgumentException $e) {
	// ...
}

 

Warto zauwa偶y膰, 偶e wiedza na temat struktury JSON'a pozostaje na poziomie Warstwy Infrastruktury i nie przenika do Warstwy Aplikacji. Jak wida膰 Akcja Kontrolera sta艂a si臋 do艣膰 obszerna poniewa偶 w przewa偶aj膮cej cz臋艣ci zajmuje si臋 tworzeniem poprawnego obiektu Command. Warto zastanowi膰 si臋 nad czytelniejszym rozwi膮zaniem.

Command z Metod膮 Fabryczn膮

    Mo偶na by pokusi膰 si臋 o przeniesienie kodu odpowiadaj膮cego za interpretacj臋 JSON'a (ze 艣wiata zewn臋trznego) do samej klasy Command:

final class CreateUserCommand 
{
	public function __construct(
		public readonly string $email,
		public readonly int $type,
		public readonly \DateTimeImmutable $birthDate
	) {} 
	
	/** 
	 * @param array<int|string, mixed> $payload 
	 * @throws InvalidArgumentException on invalid payload
	 */
	public static function createFromRequest(array $payload): self 
	{
		Assert::notEmptyString($payload['email'] ?? null);
		Assert::integerish($payload['user_type']  ?? null);
		Assert::stringDateTime($payload['birth_date']  ?? null);
		
		return new self(
			$payload['first_name'],
			(int) $payload['user_type'],
			new \DateTimeImmutable($payload['birth_date'])
		);
	}
}

 

    Znacznie upro艣ci艂o by to implementacj臋 Kontrolera poniewa偶 utworznie obiektu Command sprowadza艂o by si臋 tylko do jednej linii...

$command = CreateUserCommand::createFromRequest($payload);

...Ale

Warstwa Aplikacji by艂aby zale偶na od JSON'a z poza granic aplikacji. Na ka偶d膮 zmian臋 struktury JSON'a (Warstwa Infrastruktury) np. zmiany nazwy klucza z snake_case na camelCase, musieliby艣my modyfikowa膰 byt z rdzenia aplikacji w postaci Command'a Aplikacyjnego. Pomimo, 偶e nazwa klucza mo偶e si臋 wydawa膰 tylko niewinnym string'iem to tak naprawd臋 jest to zale偶no艣膰, a w tym przypadku - b艂臋dnie ukierunkowana zale偶no艣膰.

Command Aplikacyjny jest bezpo艣rednio zale偶ny od Warstwy Infrastruktury.

Command posiada od teraz dwie odpowiedzialno艣ci: przechowywanie danych & mapowanie obiektu JSON na sw贸j stan. Dwie odpowiedzialno艣ci === dwa powody do zmian.  

❌ dodanie obs艂ugi kolejnych medi贸w przekazu (np. CLI Command) sprawia, 偶e klasa staje si臋 coraz wi臋ksza.

Pragmatyzm

    Jak wynika z analizy powy偶ej, umieszczanie kodu interpretuj膮cego JSON'a w Warstwie Aplikacji to rozwi膮zanie 艂ami膮ce zasady czystego kodu. Z drugiej strony pozostawienie go w Kontrolerze HTTP te偶 nie wydaje si臋 s艂uszne. W nadchodz膮cym wpisie zostanie opisane jeszcze jedno rozwi膮zanie: wprowadzenie klasy Fabrycznej odpowiadaj膮cej za powi膮zanie JSON'a z Command'em. Mo偶na zastanowi膰 si臋 czy tworzenie nowej klasy dla tego typu zadania nie jest przerostem formy nad tre艣ci膮. Je偶eli patrzymy tylko pod k膮tem czytelno艣ci, to dodanie Metody Fabrycznej do Command'a wydaje si臋 zadowalaj膮ce poniewa偶 w jednym miejscu mamy powi膮zane ze sob膮 zagadnienia. Dodatkowo programi艣ci, je偶eli b臋d膮 chcieli utworzy膰 now膮 instancj臋 Command'a nie b臋d膮 musieli szuka膰 dodatkowych Klas Fabrycznych - wszystko maj膮 pod r臋k膮. 

    Z drugiej strony, gdy zale偶y nam na utrzymaniu odpowiedniego kierunku zale偶no艣ci - Metoda Fabryczna nie spe艂nia tego wymagania. Zale偶no艣ci do Warstwy Infrastruktury s膮 niejawne i subtelne poniewa偶 opieraj膮 si臋 jedynie na string'owym kluczu array'a otrzymanego jako parametr. Nie zmienia to jednak faktu, 偶e jakakolwiek zmiana w obiekcie JSON b臋dzie musia艂a by膰 naniesiona w Warstwie Aplikacji. Wymienione wy偶e typy array i string sprawiaj膮 wra偶enie, 偶e nie nios膮 ze sob膮 obci膮偶enia zale偶no艣ciami. Gdyby by艂y reprezentowane przez Value Object'y z Warstwy Infrastruktury, zale偶no艣ci sta艂by si臋 jawne i od razu widoczne w sekcji use klasy CreateUserCommand.   

艣roda, 19 maja 2021

SOLID - zasada Open/Closed w praktyce

 

"Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification"

 

    Analiza dotyczy艂a b臋dzie rozbudowania kodu o now膮 funkcjonalno艣膰 bez  modyfikowania istniej膮cych klas. Spotka艂em si臋 z sytuacj膮 gdzie klasa buduj膮ca model domenowy na podstawie danych wyci膮gni臋tych z bazy danych powinna te偶 tworzy膰 ca艂e kolekcje takich modeli. Mo偶na by:

  1.  zmodyfikowa膰 klas臋 budowniczego dodaj膮c now膮 metod臋 buildCollection(int ...$ids): array,

  2.  istniej膮c膮 metod臋 budowniczego build($id): Model wywo艂a膰 w p臋tli w klasie klienta,

Wiele zapyta艅 do bazy

    Drugie rozwi膮zanie z perspektywy zasady Open/Closed wydaje si臋 lepsze bo nie wymaga od nas modyfikacji istniej膮cego kodu, ale wi膮偶e si臋 z nim pewien problem. W ciele metody Builder::build wykonywane jest zapytanie do bazy danych (RepositoryInterface::featchById) przez co im wi臋cej obiekt贸w do utworzenia tym gorzej dla optymalno艣ci rozwi膮zania. 

Jedno zapytanie do bazy

    Mo偶na zaimplementowa膰 jednak rozwi膮zanie nie ingeruj膮ce w istniej膮cy kod wykonuj膮ce tylko jedno zapytanie do bazy. Dzi臋ki temu, 偶e klasa Builder posiada wiedz臋 tylko o interfejsie repozytorium mo偶emy utworzy膰 kolejn膮 implementacj臋 RepositoryInterface - InMemoryRepository.

InMemoryRepository posiada naprawd臋 prosta implementacj臋 i to na jej podstawie b臋dzie tworzony obiekt Builder w klasie klienta zwracaj膮cego kolekcje modeli.

 
final class InMemoryRepository implements RepositoryInterface 
{
	/**
     * @param Row[] $rows
     */
	public function __construct(private array $rows) {}
	
	public function featchById($id): ?Row 
    {
		return $this->rows[$id] ?? null; 
	}
	
	/** 
     * @return Row[] 
     */
	public function featchByIds(int ...$ids): array 
    {
		return array_filter(
			$this->rows,
			static fn (Row $row): bool => in_array($row->id(), $ids);  
		);
	}
}

Pozostaje tylko nape艂ni膰 instancj臋 InMemoryRepository danymi z ju偶 istniej膮cego PDORepository

 

Query: GetModel (dotychczasowe) 

    Istniej膮ca funkcjonalno艣膰 pobierania tylko jednego modelu zamkni臋ta jest w klasie GetModel kt贸ra realizuje t膮 funkcjonalno艣膰 na podstawie obiektu Builder pobranego z kontenera zale偶no艣ci.

Odbywa si臋 tu tylko jedna istotna czynno艣膰:

$this->builder->build($id);
 

Query: GetModelCollection (nowe)

W przeciwie艅stwie do poprzedniej klasy GetModel tutaj b臋dziemy bezpo艣rednio operowa膰 na PDORepository (pod przykrywk膮 RepositoryInterface). Dzieje si臋 tutaj o wiele wi臋cej ni偶 w poprzednim przypadku:

  1. pobranie danych z bazy RepositoryInterface::featchByIds,
  2. utworzenie na ich podstawie instancji InMemoryRepository,
  3. utworzenie instancji Builder na podstawie InMemoryRepository,
  4. wywo艂anie metody Builder::build w p臋tli.
/** 
 * @return Model[]
 */
public function __invoke(int ...$ids): array
{
	$builder = new Builder(
		new InMemoryRepository(
			$this->repository->featchByIds(...$ids)
		)
	);
	
	return array_map([$builder, 'build'], $ids);
} 

Podsumowanie

  • ✅ Nie zmodyfikowali艣my 偶adnej istniej膮cej klasy,
  • nowa funkcjonalno艣膰 bazuje na dzia艂aj膮cych ju偶 wcze艣niej klasach,
  • ❌ kolejne u偶ycie s艂owa kluczowego "new" dla klasy Builder,
  • Repository::fetchByIds musi by膰 idealnie zgrane z wywo艂aniem Builder::build() w p臋tli. Repository musi dawa膰 gwarancj臋, 偶e zawsze zwracany jest komplet danych, w przeciwnym razie powinien wyrzuca膰 wyj膮tek @throws NotFoundAllIds

    Dzi臋ki takiej implementacji nie ingerowali艣my w dzia艂aj膮cy koda, a do nowej funkcjonalno艣ci wykorzystali艣my sprawdzone/przetestowane klasy. Dodatkowo rozwi膮zanie jest ca艂kowicie odseparowane (nowa klasa Query) dzi臋ki temu w ca艂o艣ci zachowali艣my funkcjonalno艣膰 pobierania pojedynczego modelu.

    Takie podej艣cie uwydatnia swoje zalety gdy proces budowania modelu w budowniczym jest skomplikowany a sam Builder posiada wiele zale偶no艣ci.     

 


pi膮tek, 25 pa藕dziernika 2019

Przeci膮偶anie w PHP



Przeci膮偶anie p贸l klasy


__get(string $propertyName): mixed
__set(string $propertyName, mixed $value): void


✅ gdy pole klasy jest z modyfikatorem dost臋pu protected i private,
✅ pola kt贸re nie s膮 jawnie zadeklarowane,
❌ gdy pole jest publiczne,
❌ gdy pole jest publiczne i z warto艣ci膮 NULL,
❌ gdy wcze艣niej dynamicznie przypisali艣my do pola jak膮艣 warto艣膰,


__isset(string $propertyName): bool
__unset(string $propertyName): void


__isset() wzbudzane przez: 
  • isset(),
  • empty(),
  • operator ??
__unset() wzbudzane przez:
  • unset(),

 gdy pole klasy jest z modyfikatorem dost臋pu protected i private,
  gdy pole klasy nie jest jawnie zadeklarowane,
❌ gdy pole jest publiczne,
❌ gdy pole jest publiczne i z warto艣ci膮 NULL,
❌ gdy wcze艣niej dynamicznie przypisali艣my do pola jak膮艣 warto艣膰,

Tutaj mo偶emy by膰 zdezorientowani przez to, 偶e pola klasy mog膮 przyjmowa膰 warto艣ci kt贸re np. w normalnym u偶yciu z konstruktem empty() mog膮 by膰 rozpatrywane pozytywnie (zwraca膰 true) dla warto艣ci: (int) 0, (string) '0', (double) 0.0, (array) [], null. Tak naprawd臋 __isset() b臋dzie wzbudzane tylko gdy pole jest ukryte przez modyfikator protected/private albo nie jest zadeklarowane - warto艣膰 nie ma znaczenia. 


Przeci膮偶anie metod klasy 


     Do przeci膮偶ania metod klasy mo偶na wykorzysta膰 magiczn膮 metod臋 __call() wzbudzan膮 w w przypadku gdy metoda klasy jest prywatna b膮d藕 nie jest zadeklarowana.  

class Person {

/**
* @var string
*/
private $email;

public function __construct(string $email) {
    $this->email = $email;
}

public function getEmail(): string {
return $this->email;
}

private function getFirstName(): string {
    return 'nie wywo艂ane';
}

public function __call(string $methodName, array $args) {
return $args[0];

}

$obj = new Person('johny@mail.com');
echo $obj->getEmail();            // johny@mail.com
echo $obj->getFirstName('Jason'); // jason
echo $obj->getLastName('Doe');    // Doe



Przeci膮偶anie konstruktora klasy


    By przeci膮偶y膰 konstruktor te偶 musimy zastosowa膰 obej艣cie poniewa偶 PHP nie pozwala na deklarowanie kilku konstruktor贸w naraz. Tak jak w przypadku przeci膮偶ania metod klasy, tak i tutaj problemem mo偶e by膰 brak wsparcia ze strony IDE (nie b臋dzie podpowiada膰 oczekiwanych parametr贸w) jak i 艣cis艂a dyscyplina przestrzegana przez developer贸w podczas projektowania i instancjonowania klas. Do wykonania zadania wykorzystam funkcj臋: 
  • func_num_args(): int 
    • Zwraca warto艣膰 typu Integer reprezentuj膮cej ilo艣膰 przekazanych do funkcji argument贸w,
  • func_get_arg(int $argNumber): mixed
    • Zwraca przekazany do funkcji argument o zadanym indeksie, zaczynaj膮c od zera,   
  • func_get_args(): array
    • Zwraca argumenty w kolejno艣ci w jakiej zosta艂y przekazane w kodzie klienckim w tablicy - opcjonalnie zamiast dw贸ch poprzednich funkcji,
W normalnym przypadku klas臋 mo偶na zaprojektowa膰 w taki spos贸b:

class Person {
    public function __construct(
        string   $username
        DateTime $birthDate) {
        $this->username  = $username;
        $this->birthDate = $birthDate;
    }
}


    Jak wida膰 ka偶de z p贸l jest TypeHint'owane na konkretny typ co jest dobr膮 praktyk膮 ;). Jednak nas interesuje bardziej dynamiczne rozwi膮zanie, kt贸re b臋dzie w stanie imitowa膰 przeci膮偶anie konstruktora - oczywi艣cie z perspektywy kodu klienckiego. Oto przyk艂ad takiej implementacji:

class Person {
    public function __construct() {
        if (func_num_args() === 2) {
            
            if (false === is_string(func_get_arg(0))) {
                throw new TypeError('First param must be string');
            }

            if (false === func_get_arg(1) instanceof DateTime) {

                throw new TypeError('Second param must DateTime instance');
            }

            $this->username  = func_get_arg(0);
            $this->birthDate = func_get_arg(1);
  
        } else if (func_num_args() === 4) {
            if (false === is_string(func_get_arg(0))) {
                throw new TypeError('First param must be string');
            }

            if (false === is_string(func_get_arg(1))) {
                throw new TypeError('First param must be string');
            }

            if (false === is_string(func_get_arg(2))) {
                throw new TypeError('First param must be string');

            }

            if (false === func_get_arg(3) instanceof DateTime) {

                throw new TypeError('Second param must DateTime instance');
            }

            $this->username  = func_get_arg(0);
            $this->firstName = func_get_arg(1);
            $this->lastName  = func_get_arg(2);
            $this->birthDate = func_get_arg(3);
        } else {
            throw new ArgumentCountError('only 2 or 4 arguments'); 
        }     
    }
}

    Efekt ko艅cowy jest taki, 偶e nasz konstruktor jest przygotowany pod dwa zestawy argument贸w, cia艂o funkcji jest do艣膰 obszerne, a przez to ma艂o czytelne. W przypadku np. czterech zestaw贸w argument贸w, implementacja konstruktora w najlepszym przypadku by艂a by tylko dwa razy wi臋ksza. Czy jest to dobre podej艣cie do projektowania klas? moim zdaniem nie. Za ka偶dym razem przed tworzeniem obiektu trzeba b臋dzie zagl膮da膰 do pliku z klas膮 bo nasze IDE nie podpowie nam nic o wymaganych argumentach. Jedynym ratunkiem b臋dzie w艂a艣nie przeczytanie DocBlocka, analiza kodu b膮d藕 zdanie si臋 na w艂asn膮 pami臋膰. Mo偶na by艂o by wykorzysta膰 opcjonalne argumenty w sygnaturze konstruktora - oszcz臋dza艂a by ilo艣膰 kodu potrzebn膮 do implementacji, lecz sprawi艂a by 偶e ca艂o艣膰 sta艂a by si臋 mniej plastyczna :

class Person {
    public function __construct(
         string   $username
         DateTime $birthDate,
        ?string   $firstName = null,
        ?string   $lastName  = null 
    ) {
        $this->username  $username;
        $this->birthDate = $birthDate;
        $this->firstName = $firstName;
        $this->lastName  = $lastName;
    }
}

Teraz musimy pilnowa膰 by dodatkowe argumenty zawsze by艂y na ko艅cu no i nie wszystkie przypadki dynamicznych zestaw贸w mo偶emy 'obs艂u偶y膰'.



  • metody magiczne mo偶emy wywo艂ywa膰 bezpo艣rednio w kodzie klienckim,
  • jak zadeklarujemy metod臋 __call() tylko z jednym argumentem to dostaniemy nieprzechwytywalny Fatal Error, 
  • w przypadku gdy w metodzie __set() zadeklarujemy return, nie zostanie wyrzucony Fatal Error ale s艂owo kluczowe return zostanie zignorowane,
  • Gdy poza funkcj膮 b臋dziemy pr贸bowali u偶y膰 funkcji func_get_args(), func_num_args(), func_get_arg(1): otrzymamy Warning'a,




殴r贸d艂a:

niedziela, 20 pa藕dziernika 2019

Standardowe b艂臋dy PHP


Dziel膮 si臋 na FATAL i NON-FATAL:

                                                                                                                               
+---------------------------------------------------------+         
|                          E_ALL                          |
+---------------------------------------------------------+ 
   __            __             __          __          __
  |  |          |  |           |  |        |  |        |  | 
  ERROR   RECOVERABLE_ERROR   WARNING     NOTICE    DEPRECATED
  |  |          |  |           |  |        |  |        |  | 
CORE_ERROR      |  |        CORE_WARNING   |  |        |  |  
  |  |          |  |           |  |        |  |        |  |
COMPILE_ERROR   |  |       COMPILE_WARNING |  |        |  | 
  |  |          |  |           |  |        |  |        |  |
USER_ERROR      |  |       USER_WARNING USER_NOTICE USER_DEPRECATED
  |  |          |  |           |  |        |  |        |  |
  PARSE         |  |           |  |        |  |       STRICT
  |  |          |  |           |  |        |  |        |  | 
__|  |__      __|  |__       __|  |__    __|  |__    __|  |__  
\      /      \      /       \      /    \      /    \      / 
 \    /        \    /         \    /      \    /      \    / 
  \  /          \  /           \  /        \  /        \  /
   \/            \/             \/          \/          \/  

+-----------------------+    +-------------------------------+
|         FATAL         |    |           NON FATAL           |
+-----------------------+    +-------------------------------+ 


Mo偶emy te偶 przyj膮膰 inny podzia艂, ze wzgl臋du na poziom zagro偶enia: 


+-----------------+  +-----------------+  +-----------------+
|      NOTICE     |  |     WARNING     |  |      ERROR      |
|                 |  |                 |  |                 |
| *NOTICE         |  | *WARNING        |  | *ERROR          |
| *USER_NOTICE    |  | *USER_WARNING   |  | *USER_ERROR     |
*STRICT         |  | *COMPILE_WARNING|  | *COMPILE_ERROR  | 
| *DEPRECATED     |  | *CORE_WARNING   |  | *CORE_ERROR     |
| *USER_DEPRECATED|  |                 |  | *PARSE          |
|                 |  |                 |  | *RECOVERABLE    |
+-----------------+  +-----------------+  +-----------------+

ze wzgl臋du na jego 藕r贸d艂o:

+-----------------+  +-----------------+  +-----------------+
|       USER      |  |    COMPILER     |  |     RUNTIME     |
|                 |  |                 |  |                 |
| *USER_ERROR     |  | *PARSE          |  | *ERROR          |
| *USER_WARNING   |  | *CORE_ERROR     |  | *STRICT         |
| *USER_ERROR     |  | *CORE_WARNING   |  | *RECOVERABLE    |
| *USER_DEPRECATED|  | *COMPILE_ERROR  |  | *WARNING        |
|                 |  | *COMPILE_WARNING|  | *NOTICE         |
|                 |  |                 |  | *DEPRECATED     |
+-----------------+  +-----------------+  +-----------------+

czy s膮 mo偶liwe do przechwycenia przez procedur臋 obs艂ugi zadeklarowan膮 w funkcji set_error_handler():

⛔ E_ERROR
⛔ E_PARSE
⛔ E_CORE_ERROR, E_CORE_WARNING
⛔ E_COMPILE_ERROR, E_COMPILE_WARNING
⛔ E_STRICT (wi臋kszo艣膰)
✅ E_NOTICE
✅ E_WARNING
✅ E_DEPRECATED
✅ E_RECOVERABLE_ERROR
✅ E_USER_ERROR, E_USER_WARNING, E_USER_DEPRECATED, E_USER_NOTICE

Jest tak poniewa偶 mo偶liwe do obs艂ugi s膮 jedynie b艂臋dy wyst臋puj膮ce w czasie run-time, wyj膮tkiem od tej regu艂y jest E_ERROR i E_STRICT. Warto nadmieni膰, 偶e konfiguracja zawarta w funkcji error_reporting() nie b臋dzie mie膰 wp艂ywu na to czy handler przechwyci b艂膮d czy nie.


 E_WARNING  - (2) ostrze偶enie powsta艂e w czasie wykonywania (run-time) - nie powoduje zatrzymania wykonywania kodu. Wyrzucane gdy:

 inkludujemy nieistniej膮cy plik,
❌ u偶yjemy nieistniej膮cej sta艂ej,
❌ wywo艂ujemy nie-statyczn膮 metod臋 klasy w statyczny spos贸b,
❌ nie zapewnimy wszystkich wymaganych argument贸w funkcji/metody klasy, ale tylko do wersji 7.0 w艂膮cznie. Od PHP 7.1 wyrzucony b臋dzie Error ArgumentCountError. W przypadku gdy funkcja b臋dzie wymaga艂a dwa argumenty, a wywo艂uj膮c j膮 nie dodamy 偶adnego, b臋dziemy mieli dwa wywo艂ane komunikaty ostrzegaj膮ce.

R贸偶ne sposoby by ostrze偶enia NIE pokazywa膰: 
  • error_reporting(E_ALL ^ E_WARNING);
  • error_reporting(E_ALL & ~E_WARNING);
  • ini_set('error_reporting', E_ALL & ~E_WARNING);
  • error_reporting(E_ERROR | E_STRICT);

 E_COMPILE_WARNING  (128) - ostrze偶enia wygenerowane w czasie kompilowania skryptu przez silnik Zend domy艣lnie logowany do pliku error_log. Tego typu komunikaty otrzyma膰 mo偶na wywo艂uj膮c taki kod:

echo "some message";
declare(unexistedDirective='someValue');

Tak jak w przypadku zwyk艂ego ostrze偶enia, wykonywanie programu nie zostanie przerwane lecz z t膮 r贸偶nic膮, 偶e komunikat ostrze偶enia wy艣wietli si臋 na samym pocz膮tku:

Warning: Unsupported declare 'unexistedDirective'
some message

Jak wida膰 developer nie jest jawnie informowany o ostrze偶eniu w czasie kompilacji, wi臋c mo偶na to 艂atwo pomyli膰 ze zwyk艂ym ostrze偶eniem. Warto mie膰 to na uwadze podczas implementacji custom'owego handler'a z rozr贸偶nieniem na poszczeg贸lne b艂臋dy. 

 E_CORE_WARNING  (32)   - generowany przez silnik PHP w czasie jego inicjalizacji.
 E_USER_WARNING  (512) - mo偶liwy do wygenerowania przez developera za pomoc膮 funkcji trigger_error().


 E_NOTICE  - (8) nie do ko艅ca jest to b艂膮d poniewa偶 podczas wykonywania kodu powoduj膮cego ten komunikat, proces nie zostaje wstrzymany. Jest to po prostu informacja dla programisty, 偶e co艣 przebieg艂o nie tak. Kilka przyk艂ad贸w, kt贸re powoduj膮 wygenerowanie tego komunikatu:

❌ u偶ycie zmiennej kt贸ra nie istnieje,
❌ u偶yjemy pola obiektu kt贸re nie istnieje,
❌ odwo艂anie si臋 do indeksu tablicy, kt贸ry nie istnieje,


Ukrycie komunikatu: error_reporting(E_ALL ^ E_NOTICE);

b膮d藕 w pliku php.ini deklaruj膮c warto艣膰 dyrektywy na to samo co w nawiasach funkcji error_reporting().

 E_USER_NOTICE  (1024) - mo偶liwy do wygenerowania przez developera za pomoc膮 funkcji trigger_error() przekazuj膮c tylko jeden argument typu String b臋d膮cy wiadomo艣ci膮. Drugi argument nie jest konieczny poniewa偶 jego warto艣膰 domy艣lna to 1024.


 E_STRICT  - (2048) - Jest to podpowied藕 od interpretera, 偶e konkretne rozwi膮zanie nie jest najlepszym standardem/dobr膮 praktyk膮 lub mo偶e zosta膰 wycofane w przysz艂ych wersjach j臋zyka. Wchodzi w sk艂ad predefiniowanej sta艂ej E_ALL dopiero od wersji PHP 5.4 cho膰 dost臋pny jest od 5.0. Od wersji 7.0 ju偶 nie zdarzy si臋 nam ujrze膰 tego typu b艂臋du poniewa偶 zosta艂 zast膮piony komunikatami NOTICE, WARNING czy DEPRECATED. Ze wzgl臋du na utrzymanie kompatybilno艣ci wstecznej sta艂a E_STRICT zosta艂a jednak zachowana. Powodem tych zmian by艂o zapewnienie prostszego systemu b艂臋d贸w w PHP w kt贸rym rola E_STRICT by艂a do艣膰 niejasna. Wi臋cej informacji na ten temat znajduje si臋 w linku RFC:reclassify_e_strict. We wcze艣niejszych wersjach j臋zyka b艂膮d E_STRICT otrzymywali艣my np. gdy:


❌ odwo艂ywali艣my si臋 do nie-statycznej metody klasy w spos贸b statyczny,
❌ sygnatura metody w klasie jest inna ni偶 sygnatura tej samej metody w nad klasie,
❌ w klasie zadeklarujesz dwa konstruktory: za pomoc膮 metody magicznej __construct() i starym sposobem gdzie nazwa konstruktora jest taka sama jak nazwa klasy. 
masz metod臋 kt贸ra zwraca array'a, wywo艂ujesz j膮 w funkcji array_pop() - dostajesz komunikat: DEBUG Only variables should be passed by reference.
❌ zadeklarujemy sygnatur臋 abstrakcyjnej metody statycznej,


 E_DEPRECATED  (8192)  E_USER_DEPRECATED  - (16384) - dost臋pne od wersji 5.3. Jest to ostrze偶enie wyrzucane w czasie run-time o u偶ytych w kodzie funkcjach\klasach\konstruktach czy rozwi膮zaniach, kt贸re b臋d膮 wycofane w przysz艂ych wersjach j臋zyka. O tym czy dany wy偶ej wymienione b臋d膮 uznawane za przestarza艂e decyduj膮 ludzie zaanga偶owani w rozw贸j j臋zyka. Komunikaty te b臋d膮 si臋 r贸偶ni膰 w zale偶no艣ci od wersji j臋zyka z kt贸rej aktualnie korzystasz. W przypadku w艂asnych bibliotek, sam mo偶esz wygenerowa膰 b艂膮d E_USER_DEPRECATED za pomoc膮 wbudowanej w j臋zyk funkcji user_error() b臋d膮cej aliasem trigger_error() np w taki spos贸b:
trigger_error('Some message', E_USER_DEPRECATED);

Co powoduje wyrzucenie komunikatu E_DEPRECATED:

7.3
❌ podczas deklarowania sta艂ej case-insensitive za pomoc膮 funkcji define() - czyli przekazuj膮c jako trzeci parametr TRUE,
❌ flagi dla funkcji filter_var(): FILTER_FLAG_SCHEME_REQUIRED, FILTER_FLAG_HOST_REQUIRED.

7.2
❌ korzystanie z create_function(), each()__autoload(),
❌ rzutowanie warto艣ci na NULL: (unset) $someVariable,
❌ dyrektywa php.ini track_errors,
❌ assert() z parametrem typu String,
❌ zmienna $php_errormsg,
u偶ywanie funkcji parse_str() bez podawania drugiego argumentu,

7.1
❌ korzystanie z rozszerzenia mcrypt z racji tego, 偶e nie jest wspierane i trudne w u偶yciu. Usuni臋ty z PHP w wersji 7.2 i zasob贸w PECL,
❌ modyfikator e dla funkcji (Multibyte String Function) mb_ereg_replace() i mb_eregi_replace(),

7.0
❌ konstruktory klasy w stylu PHP 4 czyli taki gdzie nazwa funkcji jest taka sama jak nazwa klasy w kt贸rej si臋 znajduje,

Wymienione wy偶ej przypadki zostan膮 usuni臋te w PHP 8.0.

 E_RECOVERABLE_ERROR  (4096) - dost臋pny od wersji 5.2. Jest to w艂a艣ciwie Fatal Error mo偶liwy do przechwycenia przez handler zdefiniowany za pomoc膮 funkcji set_error_handler(). Kod kt贸ry powoduje wyrzucenie tego komunikatu jest powa偶nym b艂臋dem, ale nie pozostawia silnika PHP w niestabilnym stanie, dlatego je偶eli tylko zostanie przechwycony, wykonywanie programu nie zostaje przerwane (w przeciwnym razie program zachowa si臋 jak w przypadku E_ERROR).

Co wyrzuca 'odzyskiwalne b艂臋dy' pisa艂em w tym wpisie. To jedyne przypadki, kt贸re uda艂o mi si臋 jak dot膮d namierzy膰. Ale... instancjonowanie obiektu Closure inaczej mo偶na obs艂u偶y膰 w zale偶no艣ci od wersji:

PHP 5.6 w set_error_handler() poniewa偶 wyrzucany jest E_RECOVERABLE_ERROR.

PHP 7.0: w bloku try/catch poniewa偶 wyrzuci to E_ERROR.

Co ciekawe w przypadku b艂臋d贸w zwi膮zanych z __toString() (ze wspomnianego wpisu) one w dalszym ci膮gu s膮 obs艂ugiwane przez set_error_handler().




 E_ERROR  (1) - b艂膮d krytyczny wywo艂ywany w czasie wykonywania powoduj膮cy wy艣wietlenie komunikatu typu Fatal Error, a w zwi膮zku tym natychmiastowe przerwanie skryptu. Przechwycenie tego typu b艂臋du we w艂asnej procedurze obs艂ugi zadeklarowanej za pomoc膮 funkcji set_error_handler() nie jest mo偶liwe. R贸偶nica mi臋dzy PHP 5.6 a 7.0 jest tak, 偶e teraz mamy mo偶liwo艣膰 godnego obs艂u偶enia niekt贸rych b艂臋d贸w krytycznych:

B艂臋dy kt贸re do wersji 5.6  powodowa艂y nieobs艂ugiwane Fatal Error'y, a od 7.0 mo偶na je przechwytywa膰 w bloku try/catch, TypeHint'uj膮c na klas臋 Error:
 wywo艂anie nieistniej膮cej funkcji,
 instancjonowanie nieistniej膮cej klasy,
 instancjonowanie interfejsu,
 odwo艂anie si臋 do nieistniej膮cego statycznego pola klasy,
 odwo艂anie si臋 do nieistniej膮cej metody statycznej klasy,

B艂臋dy kt贸re nawet w wersjach 7.x nie s膮 mo偶liwe do obs艂ugi:
❌ implementowanie interfejsu kt贸ry nie istnieje,
❌ dziedziczenie po nieistniej膮cej klasie,
❌ pr贸ba przypisania do pola, pola statycznego, sta艂ej klasy innej warto艣ci ni偶 litera艂 np. funkcji anonimowej,
❌ dodanie cia艂a metody w interfejsie,
pr贸ba przypisania warto艣ci do $this,
❌ stosowanie isset() na litera艂ach np. isset(0.0);



 E_CORE_ERROR  (16) - generowany przez silnik PHP. B艂膮d wyst膮pi gdy mamy PHP 5.4 (i wi臋ksze) a w php.ini mamy zadeklarowan膮 dyrektyw臋 safe_mode=On. Kod developera nie zostaje w og贸le wykonywany.

 E_COMPILE_ERROR  (64) - wyrzucane gdy na zmiennej b臋dziemy pr贸bowali zastosowa膰 ::class. Otrzymamy wtedy komunikat: FATAL ERROR Dynamic class names are not allowed in compile-time ::class fetch.

 E_USER_ERROR  (256) - mo偶liwy do wygenerowania przez developera za pomoc膮 funkcji trigger_error()i tym razem - inaczej ni偶 w przypadku E_ERROR - mo偶liwy do obs艂ugi za pomoc膮 set_error_handler().


 E_PARSE  (4) - pojawia si臋 gdy kompilator nie mo偶e zinterpretowa膰 kodu developera - niemo偶liwy do obs艂ugi w jakikolwiek spos贸b. Powoduje wy艣wietlenie komunikatu typu Parse error: syntax errorKod developera nie zostaje w og贸le wykonywany. Wyst臋puje mi臋dzy innymi w nast臋puj膮cych przypadkach zaimplementowanego kodu:

❌ final interface MyInterface {}
❌ abstract interface MyInterface {}
❌ class MyClass() {}
❌ echo 'something'
❌ if (if(true) {} else {}) {}


 FATAL ERROR  - jest to b艂膮d krytyczny i gdy wyst膮pi nast臋puje przerwanie wykonywania programu. Domy艣lne zachowanie sprowadza si臋 do wy艣wietlenia komunikatu b艂臋du.

 FATAL ERROR: UNCAUGHT ERROR  - jest to Fatal Error mo偶liwy do przechwycenia. Dopisek Uncaught error daje informacj臋 o tym i偶 jest to b艂膮d do przechwycenia w klauzuli try/catch, a wi臋c TypeHint'uj膮c w bloku catch na klas臋 Error albo Throwable mamy mo偶liwo艣膰 jego obs艂ugi. Procedura obs艂ugi w funkcji set_error_handler() nie jest w stanie obs艂u偶y膰 tego b艂臋du. Rozszerzony komunikat Fatal Error pojawi艂 si臋 dopiero w PHP 7.0.

 FATAL ERROR: UNCAUGHT EXCEPTION  - tak samo jak powy偶ej analogicznie do nieprzechwyconych wyj膮tk贸w (czyli jak ich jawnie nie obs艂u偶ymy to zamieniaj膮 si臋 w Fatal Error'y).

Standardowe b艂臋dy PHP i klasa ErrorException


Dokumentacja zaleca by przechwytywa膰 ostrze偶enia WARNING czy komunikaty NOTICE w procedurze obs艂ugi zdefiniowanej w funkcji set_error_handler() i tam wyrzuca膰 wyj膮tek ErrorException (PHP 5.1) stworzony z intencj膮 o takim zastosowaniu. Dzi臋ki temu b臋dziemy mogli obs艂u偶y膰 wyrzucone ostrze偶enie (przyk艂ad poni偶ej) w kontek艣cie jego wywo艂ania:


set_error_handler(function(int $errorCode) {
    if ($errorCode === E_WARNING) {
        throw new ErrorException('Some warning');
    }
});

try {
    echo A;
} catch (ErrorException $e) {
    echo $e->getMessage();
}



Na sam koniec wrzucam fajny kalkulator do wyliczania kodu b艂臋d贸w. 



殴r贸d艂a: