API do obsługi formularza kontaktowego - Symfony 7 + PostgreSQL.
Bez dokładnej znajomości stylu pracy firmy oraz dostępnego czasu (podano mi 5h) ciężko jest wyśrodkować ilość włożonej pracy tak, by nie zrobić overkilla oraz nie zrobić rzeczy zbyt prosto.
Co można zrobić prościej?
- Nie wydzielać
ContactMessageCreator.phpz kontrolera - Nie pisać tylu tekstu w DTO do walidacji, a moze zamiast DTO od razu mapować na encję
- Pisać więcej po angielsku (umyślnie na potrzeby rekrutacji)
- Nie dodawac nawet prostego dockera na testy
- Nie uzywac phpcbf/phpstan - ale to kwestia przyzwyczajenia
- Zrobić dużo prostszy opis OpenApi
# Klonowanie repozytorium
git clone https://github.com/iud/h2h.git
cd h2h
# Uruchomienie kontenerów
docker compose up -d
# Instalacja zależności
docker compose run --rm php composer install
# Utworzenie bazy i wykonanie migracji
docker compose run --rm php php bin/console doctrine:database:create --if-not-exists
docker compose run --rm php php bin/console doctrine:migrations:migrate --no-interaction
# Uruchomienie serwera
docker compose run --rm -p 8000:8000 php php -S 0.0.0.0:8000 -t publicGotowe, API działa na http://localhost:8000
Wymagania:
- PHP 8.2+ z pdo_pgsql
- Composer
- PostgreSQL 16+
# Klonowanie repozytorium
git clone https://github.com/iud/h2h.git
cd h2h
# Instalacja zależności
composer install
# Konfiguracja bazy w .env.local
DATABASE_URL="postgresql://user:pass@127.0.0.1:5432/contact_form_db?serverVersion=16&charset=utf8"
# Utworzenie bazy i wykonanie migracji
php bin/console doctrine:database:create --if-not-exists
php bin/console doctrine:migrations:migrate --no-interaction
# Uruchomienie serwera
php -S localhost:8000 -t publicDwa endpointy - zapis wiadomości i lista wiadomości. Wszystko w JSON.
Zapis wiadomości. Wymagane pola:
fullName- imię i nazwisko (min 2, max 255 znaków)email- adres email (poprawny format)message- treść wiadomości (min 10 znaków)consent- zgoda na RODO (musi byćtrue)
Przykład requestu:
{
"fullName": "Jan Kowalski",
"email": "jan@example.com",
"message": "Treść wiadomości",
"consent": true
}Sukces (201):
{
"id": 1,
"fullName": "Jan Kowalski",
"email": "jan@example.com",
"message": "Treść wiadomości",
"createdAt": "2025-12-17T10:30:00+00:00"
}Błąd walidacji (422) - zwraca listę błędów w formacie Symfony.
Pobiera listę wiadomości z paginacją (najnowsze pierwsze).
Query params:
page- numer strony (domyślnie 1, min 1)limit- ile na stronę (domyślnie 20, max 100)
Odpowiedź:
{
"items": [],
"meta": {
"page": 1,
"limit": 20,
"total": 42,
"pages": 3
}
}Pełna dokumentacja API dostępna pod http://localhost:8000/api/doc.
Dwa typy testów:
- PHPUnit - testy jednostkowe (warstwa aplikacji, encje)
- Codeception - testy integracyjne (API endpoints)
# Przygotowanie bazy testowej
docker compose run --rm php php bin/console doctrine:database:create --env=test --if-not-exists
docker compose run --rm php php bin/console doctrine:migrations:migrate --env=test --no-interaction
# Uruchomienie testów PHPUnit
docker compose run --rm php php bin/phpunit
# Uruchomienie testów Codeception
docker compose run --rm php vendor/bin/codecept runphp bin/phpunit
vendor/bin/codecept runTesty pokrywają: zapis wiadomości, wszystkie przypadki walidacji (brak pól, niepoprawne wartości), listę wiadomości, paginację.
Standardowa struktura Symfony:
src/Controller/- kontrolery APIsrc/Entity/- encje Doctrinesrc/DTO/- obiekty transferu danych z walidacjąsrc/Application/- logika biznesowasrc/Repository/- repozytoriatests/- testy (PHPUnit + Codeception)migrations/- migracje bazy danych
- Kod przeleciany przez phpcs (PSR-12) i phpstan
- Testy jednostkowe (PHPUnit) + testy integracyjne (Codeception)
- Warstwa aplikacji (
ContactMessageCreator) - oddzielenie logiki od kontrolera, ale to też taka atrapa bardziej niż real use - DTO z walidacją Symfony
- Docker setup - ekstremalnie lekki, tylko do testowania
- Dokumentacja Swagger/OpenAPI
Atrybuty OpenAPI w kontrolerze mogą go zaśmiecać. Można przenieść do osobnych klas lub użyć YAML.
Zamiast ręcznej paginacji można użyć KnpPaginatorBundle - mniej kodu, więcej funkcji (sortowanie, filtry).
Dla prostego CRUD można było użyć API Platform. Ale to też mógł być overkill.
Zamiast bezpośredniego wywołania serwisu, kontroler mógłby rzucać komendę przez Symfony Messenger.
Testy setterów są zbędne, ale to zadanie rekrutacyjne, to pokazuję, że umiem ;)