sobota, 23 lipca 2011

Własne wyjątki - a na co mi to?


O tym, czym są wyjątki można przeczytać w wikipedii lub na php.net. Ogólnie rzecz biorąc wyjątki powinny być wyrzucane wtedy, gdy w jakiś sposób nasza aplikacja podejmie próbę wykonania niedozwolonej operacji lub nie będzie w stanie wykonać operacji.
Od wersji 5 PHP jest dostarczany z bardzo ciekawą biblioteką SPL, która narzuca pewną hierarchię wyjątków i dzieli je na dwie główne grupy:
  • RuntimeException - są to wyjątki, które powinny być wyrzucane, gdy aplikacja z pewnych względów nie może poprawnie działać np. zerwanie połączenia z bazą danych.
  • LogicException - są to wyjątki, które powinny być wyrzucane, gdy aplikacja próbuje wykonać operacje, które są błędne np. wywołanie nieistniejącej metody na przekazanym obiekcie.
Szerszy opis wszystkich wyjątków, które są dostarczone wraz z biblioteką SPL znajdziecie tutaj.

Dobra, po trochę przydługim wstępie, wracam do głównego wątku:) A więc po co tworzyć własne wyjątki? Przecież można z łatwości wpisać odpowiedni message do konstruktora tego bazowego (Exception) i po sprawie. Oczywiście, że tak można, ale tylko do momentu, gdy wyjątki są rzeczywiście tylko i wyłącznie kontenerem na jakąś informację.

Oczywiście powodów stosowanie wyjątków jest kilka, ja jednak pozwolę sobie skupić się na, moim zdaniem najważniejszym, czyli utrzymywaniem spójnej logiki tzn. jeżeli mam klasę Product, Product_Factory, Product_Collection etc. to dobrze jest mieć jeszcze Product_Exception, który będzie wykorzystywany w tych właśnie klasach i tylko w tych. Dzięki temu już po samym typie wyjątku wiem, gdzie się wysypała aplikacja, a do dokładniejszego określenia miejsca załamania powinno się wykorzystywać wartość $message.
Oczywiście można tutaj popaść w pewną skrajność i zacząć tworzyć wyjątki dla wszystkiego i tak dostajemy: Product_Exception, Product_Factory_Exception, Product_Collection_Exception etc., gdzie każdy wyjątek jest wyrzucany jeden, no góra dwa razy w całym kodzie. I po co to? Niestety jest to częsta przypadłość programistów, którzy dopiero odkryli tą cudowną możliwość dziedziczenia po klasie Exception - po prostu zapominają o tym, że wyjątek posiada jeszcze argument $message.

Niestety złotego środka nie ma i już we własnym zakresie trzeba zdecydować, czy to już powinien być kolejny typ wyjątku, czy jeszcze ten sam, z odpowiednią wiadomością. Granica jest płynna, ważne jest to, aby pamiętać o dwóch rzeczach:
  • Dziedziczyć po klasie Exception jest w PHP dozwolone.
  • Konstruktor klasy Exception posiada możliwość przyjmowania parametrów m.in. $message i $code.

czwartek, 21 lipca 2011

foreach() i for() - subtelna różnica


Pewnie dla wielu osób, które z php mają do czynienia od dłuższego czasu, ten wpis nie będzie niczym odkrywczym.
Czym różni się foreach() od for()? W wielu przypadkach nie ma żadnej różnicy dla logiki aplikacji, której pętli się stosuje i można je stosować zamiennie bez wpływu na jej działanie.

Więc gdzie jest różnica? Otóż foreach() przechodzi po elementach tablicy w kolejności dodawania wartości, natomiast for() jest używany do iterowania tablicy w zdefiniowany przez nas sposób.
Poniższy kod wyjaśnia wszystko:
<?php
$a = array();
$a[3] = 'a';
$a[1] = 'b';
$a[0] = 'c';
$a[2] = 'd';

$arrayCount = count($a);

echo "FOR: \n";
for ($key = 0; $key < $arrayCount; $key++)
    echo 'key: ' . $key . ', value: ' . $a[$key] . "\n\n";

echo "FOREACH: \n";
foreach ($a as $key => $value)
    echo 'key: ' . $key . ', value: ' . $value . "\n";

Wykonanie go daje taki wynik:
FOR:
key: 0, value: c
key: 1, value: b
key: 2, value: d
key: 3, value: a

FOREACH:
key: 3, value: a
key: 1, value: b
key: 0, value: c
key: 2, value: d

czwartek, 14 lipca 2011

Abstrakcja vs Interfejs


Klasy abstrakcyjne i interfejsy są bytami, które nie tylko ułatwiają pisanie kodu, ale także sprawiają, że kod jest czytelniejszy i bardziej przejrzysty, a logika aplikacji prostsza do zrozumienia. Ich poprawne i przemyślane zastosowanie z pewnością opłaca się, gdy aplikacja zaczyna się rozrastać i/lub zmieniać.
O tym co to jest interfejs oraz klasa abstrakcyjna można przeczytać w wikipedii. I choć zrozumienie tego, czym są nie jest wcale takie skomplikowane, to poprawne ich stosowanie nie jest czasami oczywiste.

Nie chcę tytułować się żadnym ekspertem w tej dziedzinie, ale po pewnym czasie spędzonym nad projektowaniem aplikacji i wdrażaniem tych rozwiązań, chciałem się podzielić wnioskami.
Kiedy więc powinno się stosować interfejsy i/lub klasy abstrakcyjne?
Pozwolę sobie to wszystko zaprezentować na prostym przykładzie:
<?php
class Man
{
  public function sleep() {/*...*/}
  public function eat() {/*...*/}
  public function doMenStuff() {/*...*/}
}

class Woman
{
  public function sleep() {/*...*/}
  public function eat() {/*...*/}
  public function doFemaleStuff() {/*...*/}
} 

W powyższym przykładzie mamy dwie klasy (Man i Woman), w których jesteśmy w stanie wyodrębnić pewną część wspólną. W takiej sytuacji warto rozważyć utworzenie nadklasy abstrakcyjnej po której będą dziedziczyły obie klasy. W tym wypadku takie rozwiązanie jest idealne:
<?php
abstract class Human
{
  public function sleep() {/*...*/}
  public function eat() {/*...*/}
}

class Man extends Human
{
  public function doMenStuff() {/*...*/}
}

class Woman extends Human
{
  public function doFemaleStuff() {/*...*/}
}

W końcu i mężczyźni i kobiety śpią i jedzą w ten sam sposób, przynajmniej w większości :) Jeżeli przyjrzymy się tym klasom bliżej, to możemy dojść do wniosku, że metody doMenStuff() i doFemaleStuff() są logicznie podobne, obie odpowiadają za wykonywanie czynności, jednak ich działanie jest zależne od konkretnej klasy. W takim wypadku można by się pokusić o jeszcze jedno przeprojektowanie kodu:
<?php
abstract class Human
{
  public function sleep() {/*...*/}
  public function eat() {/*...*/}
  abstract public function doStuff();
}

class Man extends Human
{
  public function doStuff() {/*...*/}
}

class Woman extends Human
{
  public function doStuff() {/*...*/}
}

Teraz na pierwszy rzut oka widzimy, że każda klasa dziedzicząca po Human musi zaimplementować ciało metody doStuff(). Oczywiście klasy mogą posiadać jeszcze atrybuty wspólne i rozłączne (pomijam wszelkiego rodzaju gettery i settery):
<?php
abstract class Human
{
  private $_age;
  public function sleep() {/*...*/}
  public function eat() {/*...*/}
  abstract public function doStuff();
}

class Man extends Human
{
  public function doStuff() {/*...*/}
}

class Woman extends Human
{
  private $_pregnanciesNumber;
  public function doStuff() {/*...*/}
}


Mamy teraz ładną hierarchię klas, spójną i logiczną. Na podstawie klasy abstrakcyjnej widzimy, że każda klasa pochodna musi posiadać zaimplementowaną metodę doStuff() oraz, że posiada kilka dodatkowych metod, które dziedziczy po klasie bazowej.
Załóżmy jednak, że przyszło nam do głowy dodać do całego kodu klasę Bed:
<?php
class Bed
{
  public function use($user)
  {
    $user->sleep();
  }
}

Oczywiście zakładamy, że z łóżka mogą korzystać tylko obiekty, na których jesteśmy w stanie wykonać metodę sleep(). W takim wypadku możemy kod klasy Bed zmienić na:
<?php
class Bed
{
  public function use(Human $user)
  {
    $user->sleep();
  }
}

Już lepiej, ale czy tak naprawdę parametr powinien być typu Human? Nam przecież zależy, żeby można było na tym obiekcie wykonać metodę sleep(), a w Human jest ich trochę więcej. I tutaj z pomocą przychodzą interfejsy, które powinny być używane wszędzie tam, gdzie należy zadeklarować metody niezbędne dla logiki działania. Oczywiście można do tego celu użyć również klasy abstrakcyjnej z tym, że ma to sens tylko, gdy wszystkie metody publiczne klasy abstrakcyjnej są wykorzystywane w danym miejscu aplikacji, czyli nasza klasa Human musiałaby wyglądać tak:
<?php
abstract class Human
{
  public function sleep() {/*...*/}
}

W innym wypadku (jeżeli zostaniemy przy aktualnym projekcie klasy Human z trzema metodami, w tym jedną abstrakcyjną) programista, który również chciałby napisać klasę korzystającą z obiektów klasy Bed uważałby, że do działania metody use() mogą być niezbędne również metody eat() i doStuff() klasy Human, co oczywiście byłoby błędne. Aby nie doprowadzić do takich nieporozumień stosuje się interfejsy. I tak nasz kod po całej tej analizie wygląda tak:
<?php
interface AbleToSleep
{
  public function sleep();
}

abstract class Human implements AbleToSleep
{
  private $_age;
  public function sleep() {/*...*/}
  public function eat() {/*...*/}
  abstract public function doStuff();
}

class Man extends Human
{
  public function doStuff() {/*...*/}
}

class Woman extends Human
{
  private $_pregnanciesNumber;
  public function doStuff() {/*...*/}
}

class Bed
{
  public function use(AbleToSleep $user)
  {
    $user->sleep();
  }
}

Teraz już na pierwszy rzut oka widać, że obiekty klasy Human mogą korzystać z instancji klasy Bed oraz jakie metody muszą implementować przyszłe klasy, aby również z nich korzystać.