Wymiana danych to chleb powszedni programisty. Masowo produkujemy REST-owe API, w tym celu używając coraz bardziej zautomatyzowanych narzędzi. Na przykładzie bramki płatniczej opowiem, jak łatwo zdenerwowaliśmy klientów psując używane przez nich API. Pokażę, jak prawidłowo rozwijać publiczne API zachowując jego świeżość, niezawodność i bezpieczeństwo.
3. PROSTA ENCJA
class Product
{
public function __construct(
public readonly string $id,
public readonly string $name,
public readonly string $price,
public readonly string $editedBy,
) {
}
}
5. MAŁY PATCH, DUŻY PROBLEM
ramsey/uuid 4.2.1
ramsey/uuid 4.2.2
$id = RamseyUuidUuid::uuid4();
C:35:"RamseyUuidLazyLazyUuidFromString":36:{23dc5ed8-4b73-4df8-9421-95e5c07a58e5}
O:35:"RamseyUuidLazyLazyUuidFromString":1:{s:6:"string";s:36:"4fdf0133-12be-4089-8fe5-45b4a3e2b
9. KIEDY CHCEMY JESZCZE ORM
class Product
{
public function __construct(
#[ORMId, ORMColumn]
public readonly string $id,
#[ORMColumn]
public readonly string $name,
#[ORMColumn]
public readonly string $price,
#[ORMColumn]
public readonly string $editedBy,
) {
}
}
10. KIEDY FRONTEND CHCE COŚ ZMIENIĆ
class Product
{
public function __construct(
#[ORMId, ORMColumn]
public readonly string $id,
#[ORMColumn]
#[SerializedName("productName")]
public readonly string $name,
#[ORMColumn]
public readonly string $price,
#[ORMColumn]
public readonly string $editedBy,
) {
}
}
11. KIEDY FRONTEND CHCE RÓŻNE DANE
class Product
{
public function __construct(
#[ORMId, ORMColumn]
#[Groups(['admin', 'cart'])]
public readonly string $id,
#[ORMColumn]
#[Groups(['admin', 'cart'])]
#[SerializedName("productName")]
public readonly string $name,
#[ORMColumn]
#[Groups(['cart'])]
public readonly string $price,
#[ORMColumn]
#[Ignore]
public readonly string $editedBy,
) {
}
}
12. KIEDY FRONTEND CHCE COŚ EKSTRA
class Product
{
// ...
public function getUniversalAnswer(): int
{
return 42;
}
#[Ignore]
public function getImportantBusinessLogic(): int
{
return 2 * 2;
}
}
13. DORZUCANIE RZECZY POD STOŁEM
final class SneakyProductNormalizer implements NormalizerInterface
{
/** @param Product $object */
public function normalize(mixed $object, string $format = null, array $context = []): ar
{
return [
'id' => $object->id,
'name' => $object->name,
'price' => $object->price,
'extraField' => 2 * 2,
];
}
// ...
}
15. ROZWIĄZANIE: DTO
class ProductAdminListDto
{
public function __construct(
public readonly string $id,
public readonly string $name,
) {
}
public static function fromEntity(Product $product): self
{
return new self($product->id, $product->name);
}
}
16. OBSŁUGA NULLI I WARTOŚCI
NIEZAINICJOWANYCH
class Product
{
public string $sku;
public ?string $rating = null;
}
17. OBSŁUGA NULLI I WARTOŚCI
NIEZAINICJOWANYCH
class Product
{
public string $sku;
public ?string $rating = null;
}
{
// "sku" pominięte, chyba że SKIP_UNINITIALIZED_VALUES = false
"rating": null // będzie pominięte dla SKIP_NULL_VALUES = true
}
20. HISTORIA TRUDNEJ MIGRACJI: JMS ->
SYMFONY
{
"type": "IP",
"value": "127.0.0.1",
"active": 123
}
$rule = $jms->deserialize($requestBody, Rule::class, 'json');
W PHP mamy active === TRUE...
21. USZCZELNIAMY TYPY, PO CZYM KLIENT
ZGŁASZA BŁĄD...
Zadanie soft skill: wytłumacz klientowi, że się pomylił
{
"currency": "GBP",
"amount": "1234" // 🔥
}
22. CIĘŻKA PRZEPRAWA Z ENCJAMI
inne atrybuty JMS/Symfony
gąszcz ExclusionPolicy, Expose, Ignore
constructor property promotion, nulle itd.
24. DODANIE NOWEGO POLA, A INTEGRACJA
KLIENTA
class Notification
{
public function __construct(
public readonly string $paymentId,
public readonly string $status,
public readonly string $receivedAt,
) {
}
}
26. DODANIE NOWEGO POLA, A INTEGRACJA
KLIENTA
class NotificationV2
{
public function __construct(
public readonly string $paymentId,
public readonly string $status,
public readonly string $receivedAt,
public readonly int $version = 2,
) {
}
}
27. ŹLE NAZWANE POLE OPCJONALNE
Walidacja przechodzi, ale funkcjonalność nie działa
Rozwiązanie: ALLOW_EXTRA_ATTRIBUTES = false
{
"currency": "GBP",
"amount": 1234,
"optionalFieldWithTypo": "foo" // 🔥
}
40. PODSUMOWANIE
unikać serialize()
nawet drobna różnica w API może popsuć integrację
testować API, łącznie z nietypowymi sytuacjami
DTO jako pośrednik między encjami a endpointami
wersjonowanie lub konfiguracja per klient
dokumentacja, która żyje