Aktywne Wpisy
![drim](https://wykop.pl/cdn/c3397992/drim_Nd7N2F4pCR,q60.jpg)
drim 0
z czym jecie frytki bo keczap mi sie skonczyl
![Suchutkowy](https://wykop.pl/cdn/c3397992/Suchutkowy_zsbcWqQKmz,q60.jpg)
Suchutkowy +14
Czyli Brudas z Lasu i Oskarek robili sobie wydzieranki ze screenów byle by pasowały do ich narracji... nie mówię, że Dubiel jest teraz niewinny bo i tak dużo sytuacji się nie klei ale jak się okazuje Ci internetowi szeryfi to zwykli oszuści. 0 wiarygodności. Jak mi powiecie, że to "outstanding move niczym w Grze o Tron, pułapka na Boxdela i Dubiela" to mam jedynie receptę żebyście zaj€bali baranka w ścianę. Bo chciałbym
#NieDlaKlepaczyKodu:Symfony
: Różne sposoby na strony błędów w kontrolerach#niedlaklepaczykodusymfony <<< Subskrybuj ten tag po więcej albo czarnolistuj, jeśli nie chcesz widzieć wpisów z tej serii. Więcej info na dole wpisu.
Symfonowe kontrolery działają w ramach abstrakcji
HTTP
: przyjmują „żądanie” (reprezentowane przez obiektRequest
) i zwracają „odpowiedź” (obiektResponse
).Załóżmy, że chcemy odpowiedzieć klientowi (przeglądarce/użytkownikowi), że strona nie istnieje, czyli zwrócić odpowiedź ze statusem
404
.Najbardziej oczywistym sposobem będzie nadanie tego kodu obiektowi odpowiedzi:
#[1]
#use Symfony\Component\HttpFoundation\Response; // dalej będę pomijał
``````
return new Response('Not found', 404);
```Ale kto by pamiętał te wszystkie kody numeryczne odpowiedzi? Lepiej użyć stałej:```
[2]
#return new Response('Not found', Response::HTTP_NOT_FOUND);
```Jeśli będziemy rzucali taki wyjątek często, pojawia się duplikacja domyślnej wiadomości. Na szczęście mamy ją w zmiennej:```
[3]
#return new Response(
Response::$statusTexts[Response::HTTP_NOT_FOUND],
Response::HTTP_NOT_FOUND
);
```Zrobiło się sporo kodu, który znów możemy uznać za duplikację. Dodatkowo takie tworzenie stron błędów ma pewien minus: wyglądają kijowo w przeglądarkach.Dokumentacja Symfony [podpowiada](https://symfony.com/doc/current/book/controller.html#managing-errors-and-404-pages) więc, że możemy rzucić wyjątkiem [HttpExceptionInterface](https://github.com/symfony/http-kernel/blob/v3.0.6/Exception/HttpExceptionInterface.php), a Symfony wtedy zaprezentuje domyślną stronę błędów (którą możemy możemy zmodyfikować do swoich potrzeb):```
[4]
#throw $this->createNotFoundException();
```Owa metoda [kontrolera bazowego](https://github.com/symfony/framework-bundle/blob/v3.0.6/Controller/Controller.php#L238) nie robi nic więcej jak utworzenie obiektu wyjątku – możemy to zrobić sami:```
[5]
#use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; // dalej będę pomijał
``````
throw new NotFoundHttpException();
```Co jednak, gdy chcemy, żeby programista miał więcej info dlaczego wystąpił wyjątek? Możemy przekazać wiadomość, która zostanie potem zapisana do logów i/lub wyświetlona na stronie błędów w środowisku developerskim:```
[6]
#throw new NotFoundHttpException('Article not found');
```Komunikat może być bardziej rozbudowany, tu przydaje się funkcja formatowania napisu:```
[7]
#throw new NotFoundHttpException(sprintf(
'Article with id "%d" was not found',
$articleId
));
```Co jeśli w kontrolerze musimy tak zrobić w kilku miejscach? Wyciągamy metodę pomocniczą:```
[8]
#public function showArticleAction($articleId)
{
$article = $this->articleRepository->findById($articleId);
if (null === $article) {
throw $this->createArticleNotFoundHttpException($articleId);
}
}
``````
private function createArticleNotFoundHttpException($articleId)
{
return new NotFoundHttpException(sprintf(
'Article with id "%d" was not found',
$articleId
));
}
```W ten sposób unikamy duplikacji tworzenia odpowiedniego wyjątku i jego komunikatu.
Co jeśli chcemy tak zrobić w wielu kontrolerach? W OOP dziedziczenie idzie na ratunek:```
[9]
#abstract class ArticleController
{
protected function createArticleNotFoundHttpException($articleId)
{
return new NotFoundHttpException(sprintf(
'Article with id "%d" was not found',
$articleId
));
}
}
``````
// i analogicznie np CreateArticleController czy SearchArticleController
class ShowArticleController extends ArticleController
{
public function showArticleAction($articleId)
{
throw $this->createArticleNotFoundHttpException($articleId);
}
}
```Inny sposób, który da nam więcej elastyczności, to skorzystać z dziedziczenia przy wyjątkach:```
[10]
#class ArticleNotFoundHttpException extends NotFoundHttpException
{
public function __construct($articleId)
{
parent::__construct(sprintf(
'Article with id "%d" was not found',
$articleId
));
}
}
``````
class ArticleController
{
public function showArticleAction($articleId)
{
throw new ArticleNotFoundHttpException($articleId);
}
}
```Dlaczego mówiłem, że da nam to więcej elastyczności? Bo teraz możemy rozpoznać rodzaj wyjątku i zrobić z nim coś więcej niż domyślnie robi Symfony z wyjątkami HTTP.Możemy przykładowo zaprezentować dedykowaną stronę błędów dla nieznalezionych artykułów (w odróżnieniu od „ogólnej“ strony błędów 404). [Poczytaj dokumentację](https://symfony.com/doc/current/cookbook/controller/error_pages.html).Jak już „robimy coś więcej“ z tym wyjątkiem, to zapewne przyda się też więcej informacji. Możemy rozbudować jego klasę:```
[11]
class ArticleNotFoundHttpException extends NotFoundHttpException
{
private $articleId;
``````
public function __construct($articleId)
{
$this->articleId = $articleId;
parent::__construct(sprintf(
'Article with id "%d" was not found',
$articleId
));
}
``````
public function getArticleId()
{
return $this->articleId;
}
}
Być może zauważyłeś już pewien problem:
NotFoundHttpException
ma też swoje pola, które warto uzupełnić. Do tego być może dorobiłeś już własną obsługę wyjątków o braku artykułu (np strona błędów z dedykowaną grafiką czy linkami) => a jednak niekoniecznie chcesz mieć zawsze taki sam komunikat. Mógłbyś chcieć z kontrolera przekazać własny. Pełna więc wersja klasy z wyjątkiem:
#[12]
#class ArticleNotFoundHttpException extends NotFoundHttpException
{
private $articleId;
``````
public function __construct(
$articleId,
$message = null,
Exception $previous = null,
$code = 0
) {
$this->articleId = $articleId;
if (null === $message) {
$message = sprintf(
'Article with id "%d" was not found',
$articleId
);
}
parent::__construct($message, $previous, $code);
}
``````
public function getArticleId()
{
return $this->articleId;
}
}
```No i już możemy go rzucić bezpośrednio:```
[13]
#$article = $this->articleRepository->findOneById($articleId);
``````
if (null === $article) {
throw new ArticleNotFoundHttpException($articleId);
}
```Albo przechwycić wyjątek domenowy i dać własny komunikat:```
[14]
try {
$article = $this->articleRepository->findOneById($articleId);
} catch (ArticleNotFoundException $ex) {
throw new ArticleNotFoundHttpException(
$articleId,
sprintf(
'Article with id "%d" was not found by ArticleRepository',
$articleId
),
$ex
);
}
-----------------
To pierwszy wpis z serii #niedlaklepaczykodusymfony – będzie o tym jak pisać i nie pisać webaplikacji z wykorzystaniem Symfony.
Jeśli chcesz dostawać powiadomienia o nowych wpisach, zapraszam do subskrypcji taga.
Jeśli nie chcesz widzieć takich wpisów, wrzuć ten tag na #czarnolisto – zwłaszcza, że będę „spamował“ wieloma innymi tagami, jak #programowanie #php #symfony #symfony2 #symfony3 ;)
Zapraszam do komentowania, code review, pytań, sugestii, propozycji nowych wpisów!
@normanos: Niestety #maciej zepsuł https://wykop-code.appspot.com/
Ale gdzieś trzeba zacząć. Jak się okaże, że mam co pisać i są tacy co chcą czytać, to będę myślał nad lepszą warstwą persystencji i prezentacji ;-)
@MacDada: no właśnie. Optuje za drugą opcją ;) a czytać ma kto bo teksty na WM robią po kilkanaście tys. Uu Minimum.
createArticleNotFoundHttpException
zrobił createNotFoundHttpException gdzie przekazywałbym dwa parametry(co jako string i id), tj zatrzymał się na punkcie 9 i trochę go "ulepszył" aby móc skorzystać z tego w każdym kontrolerzeewentualnie aby w samym traitcie pobierało prefix kontrolera(o ile w ogóle jest coś takiego możliwe), ale to raczej głupie rozwiązanie bo nie zawsze jakiś Controller będzie rzucał not found bo nie znaleziono
@Jurigag: Yep, jest też taka opcja. Chociaż ja raczej mam awersję do traitów, co też w sumie mógłbym opisać w dedykowanym artykule ;)