integrify / core
Integrify API inteqrasiyalarını rahatlaşdıran bünövrə (core) kitabxanadır.
Requires
- php: >=8.2
- php-http/discovery: ^1.20
- psr/http-client: ^1.0
- psr/http-client-implementation: ^1.0
- psr/http-factory: ^1.1
- psr/http-factory-implementation: ^1.0
- psr/http-message: ^1.1 || ^2.0
- psr/log: ^2.0 || ^3.0
Requires (Dev)
- guzzlehttp/guzzle: ^7.9
- nyholm/psr7: ^1.8
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^11.4
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Caution
Bütün sorğular rəsmi dokumentasiyalara uyğun yazılsalar da, Integrify qeyri-rəsmi API klient-dir.
Integrify is an unofficial library, even though it is based on official documentation.
Integrify API inteqrasiyalarını rahatlaşdıran sorğular kitabxanasıdır. Bu kitabxana, başqa Integrify alt-kitabxanaları üçün "bünövrə" (core) kitabxanadır.
Dokumentasiya: https://integrify.mmzeynalli.dev
Kod: https://github.com/integrify-sdk/integrify-php/tree/main/packages/core
Python qarşılığı: integrify-core
Əsas özəlliklər
- Kitabxanadakı bütün sinif və funksiyalar tamamilə dokumentləşdirilib.
- Kitabxanadakı bütün sinif və funksiyalar tipləndirildiyindən, IDE-də avtomatik
tamamlama və refactoring annotasiyasız işləyir; PHPStan
level: maxtəmizdir. - Sorğuların məntiq axını (flowsu) izah edilib.
- HTTP qatı PSR-18/PSR-17 üzərindədir — Guzzle, Symfony HttpClient və ya istənilən digər implementasiya ilə işləyir.
- Bütün xətalar vahid
IntegrifyExceptioniyerarxiyasındadır.
Note
PHP-də Python-dakına bənzər async dəstəyi olmadığı üçün bütün klientlər sinxrondur.
Kitabxananın yüklənməsi
composer require integrify/core
Bu paket PSR-18 klienti və PSR-17 factory-ləri tələb edir. Layihənizdə yoxdursa:
composer require guzzlehttp/guzzle nyholm/psr7
Nədən ibarətdir
| Sinif | Məqsəd |
|---|---|
Integrify\Client |
İnteqrasiya klientlərinin baza class-ı |
Integrify\Response |
Cavab: status, headers, body + DTO-ya çevirmə |
Integrify\Environment |
Test / Prod |
Integrify\Http\Transport |
Sorğu göndərən qatın interfeysi |
Integrify\Http\HttpTransport |
PSR-18 implementasiyası |
Integrify\Http\RecordingTransport |
Testlər və debug üçün implementasiya |
Integrify\Http\Request |
Göndəriləcək sorğunun dəyişməz təsviri |
Integrify\Dto\Data |
DTO-ların baza class-ı |
Integrify\Dto\Attribute\Field |
Field adı və validasiya qaydaları |
Integrify\Exception\* |
IntegrifyException iyerarxiyası |
İstifadəsi
Klient yazmaq
Sorğular adi, tipli metodlardır — magic dispatch yoxdur. Baza class yalnız url
qurmağı, header-ləri və Transport-a ötürməyi öz üzərinə götürür.
use Integrify\Client; use Integrify\Dto\Attribute\Field; use Integrify\Dto\Data; use Integrify\Http\HttpTransport; use Integrify\Http\Transport; final readonly class PaymentRequest extends Data { public function __construct( public int $amount, #[Field(name: 'order_id')] public string $orderId, ) { } } final readonly class Payment extends Data { public function __construct( public ?string $status = null, #[Field(name: 'transaction_id')] public ?string $transactionId = null, ) { } } final class MyClient extends Client { public function __construct(?Transport $transport = null) { parent::__construct($transport ?? new HttpTransport(), 'https://api.example.com'); } /** * Ödənişin başladılması. * * **POST** `/pay` */ public function pay(int $amount, string $orderId): Payment { return $this->post('/pay', new PaymentRequest($amount, $orderId))->to(Payment::class); } /** * **GET** `/payments/{id}` */ public function payment(string $id): Payment { return $this->get($this->uri('/payments/{id}', ['id' => $id]))->to(Payment::class); } /** * @return list<Payment> */ public function listPayments(int $page = 1): array { return $this->get('/payments', ['page' => $page])->toList(Payment::class); } protected function defaultHeaders(): array { return [...parent::defaultHeaders(), 'Authorization' => 'Bearer ...']; } }
Baza class-ın verdiyi köməkçilər:
| Metod | Nə edir |
|---|---|
get() / post() / put() / delete() |
Sorğu göndərir |
send() |
Ümumi forma (metod, path, body, query, header) |
uri($template, $params) |
{ad} şablonunu rawurlencode ilə doldurur |
listBody(iterable $items) |
DTO siyahısını kök səviyyəli JSON array-ə çevirir |
objectBody(array $body) |
Boş massivi JSON obyekti ({}) kimi göndərir |
defaultHeaders() |
Hər sorğuya əlavə olunan header-lər (auth, dil) |
mergeHeaders() |
Header-ləri böyük-kiçik hərf fərqi olmadan birləşdirir |
assertSameHost() |
Mütləq url-in host yoxlaması (genişləndirilə bilər) |
Url qurmaq
Dəyişən path hissələri həmişə uri() şablonundan keçirilməlidir:
$this->uri('/payments/{id}', ['id' => $id]); // ✅ kodlanır '/payments/' . $id; // ❌
Sətir birləşdirməsində $id içindəki ?, # və / url-in mənasını dəyişir:
42?admin=1 öz query parametrini sorğuya əlavə edir, ../../internal isə baza
path-dən çıxır. Bu səbəbdən path-də ? və ya # görünəndə InvalidRequest atılır.
Mütləq url-ə (https://...) icazə verilir — səhifələmə linkləri belə gəlir — lakin
yalnız baseUrl ilə eyni host-a. defaultHeaders() adətən API açarı daşıyır, və
yad host-a sorğu onu sızdırardı. Başqa host lazımdırsa assertSameHost()-u override edin.
Sorğu cavabı
Yuxarıdakı sorğuların (və ya istənilən sorğunun) cavab formatı Response class-ıdır:
final readonly class Response { public int $status; /** Cavab sorğusunun status kodu */ public array $headers; /** Cavab sorğusunun header-ləri */ public string $body; /** Cavab sorğusunun xam body-si */ }
| Metod | Nə edir |
|---|---|
isSuccessful() |
Status kodu 400-dən kiçikdirsə true (Python-dakı ok) |
header($name) |
Header-in ilk dəyəri |
headerValues($name) |
Header-in bütün dəyərləri (Set-Cookie, Link və s.) |
toArray() |
Body-ni massiv kimi. JSON deyilsə exception yox, boş massiv |
to($dto) |
Body-ni DTO-ya çevirir |
toList($dto) |
Kök səviyyəli JSON array-i DTO siyahısına çevirir |
Response::json($payload, $status) |
Testlər üçün hazır cavab qurur |
Cavab JSON deyilsə (məs., gateway xətası zamanı HTML səhifə və ya boş body),
toArray() boş massiv qaytarır ki, sorğu axını crash olmasın; xətanı status və
isSuccessful() ilə öyrənmək olar. toList() isə kök səviyyədə obyekt gələndə
ValidationFailed atır — açarları səssizcə atmaq data itkisidir.
DTO-lar
DTO-lar readonly class kimi, konstruktorda promote olunmuş property-lərlə yazılır.
API-nin gözlədiyi ad və validasiya qaydaları #[Field] atributu ilə verilir:
final readonly class GoodsItem extends Data { public function __construct( #[Field(name: 'namE_OF_GOODS', maxLength: 500)] public string $name, #[Field(name: 'goodsList', of: GoodsItem::class, maxItems: 40)] public array $items = [], ) { } }
#[Field] parametrləri: name, of (massivin element tipi), maxLength, minLength,
pattern, min, max, minItems, maxItems.
of yalnız Data alt class-ı və ya enum ola bilər — səhv ad LogicException verir,
çünki əks halda bütün element validasiyası səssizcə söndürülərdi.
| Metod | Nə edir |
|---|---|
Data::from(array) |
Massivdən DTO qurur və validasiya edir |
Data::wrap(array|Data) |
Massiv və ya hazır DTO qəbul edir |
Data::wrapAll(iterable) |
Siyahını DTO siyahısına çevirir |
$dto->toArray(skipNull:, only:) |
API-nin gözlədiyi massiv |
Data::properties() |
Property adları, elan olunma sırasında |
Həm PHP-dəki property adı, həm də API-dəki ad qəbul olunur. Validasiya xətaları bir yerə
toplanıb tək ValidationFailed-də qaytarılır:
try { GoodsItem::from(['name' => str_repeat('a', 600)]); } catch (ValidationFailed $failure) { $failure->errors; // ['name' => 'must be at most 500 characters, got 600'] }
Detallar:
- Uzunluqlar simvol sayına görə ölçülür (bayt yox).
- Enum-lar avtomatik cast olunur; tanınmayan dəyər
ValidationFailedverir,TypeErroryox. - Nested DTO-lar və DTO siyahıları rekursiv qurulur; xəta mesajı elementin indeksini
göstərir (
items: [2]: ...). - Ədədlər səssizcə kəsilmir: kəsr dəyər,
NAN/INFvəintdiapazonundan kənar ədədlər rədd edilir. - Union tiplərində (
Circle|Square) hər variant ayrıca yoxlanılır.
Xətalar
| Exception | Nə vaxt |
|---|---|
ValidationFailed |
DTO validasiyası uğursuz (sorğu göndərilmir) |
InvalidRequest |
Sorğu qurularkən klient kodundakı səhv: kodlanmamış path, doldurulmamış {placeholder}, yad host |
RequestFailed |
Şəbəkə xətası, və ya 400+ status kodu — ->request və ->response üzərində detallar |
MissingConfiguration |
Məcburi environment dəyişəni yoxdur |
Hamısı Integrify\Exception\IntegrifyException interfeysini implement edir, ona görə
kitabxanadan gələn hər şeyi bir yerdə tutmaq mümkündür.
Metodların qaytarış tipləri konkret olduğu üçün uğursuzluğu qaytarış dəyəri ilə bildirmək mümkün deyil — ona görə HTTP səviyyəsindəki xətalar exception kimi qalxır. Servisin özünün HTTP 200 ilə bildirdiyi məntiqi xətalar buraya daxil deyil; onlar cavab DTO-sunda modelləşdirilir.
Mühit
Environment::parse($_ENV['APP_ENV'] ?? null); // tanınmayan dəyər -> Test Environment::parse($_ENV['APP_ENV'] ?? null, strict: true); // tanınmayan dəyər -> exception
Tanınan yazılışlar: prod, production, live → Prod; test, testing, sandbox,
dev, development, local → Test.
Testlər
RecordingTransport sorğuları göndərmir, yadda saxlayır — Python-dakı dry rejiminin
qarşılığıdır, lakin klientin qaytarış tiplərini pozmadan:
$transport = new RecordingTransport(); $transport->queue(Response::json(['status' => 'ok'])); $client = new MyClient($transport); $client->pay(100, '12345678'); $transport->lastRequest()->uri; $transport->lastRequest()->headers; $transport->lastRequest()->body; $transport->count();
Xəta axınını yoxlamaq üçün növbəyə RequestFailed də qoymaq olar.
RecordingTransport real transport-la eyni müqaviləni saxlayır:
- 400+ status kodlu cavab növbəyə qoyulsa,
RequestFailedatılır; - JSON-a çevrilə bilməyən payload (
INF,NAN, yararsız UTF-8) rədd edilir; - növbə boşdursa və fallback verilməyibsə, gözlənilməyən sorğu exception qaldırır —
"bir dəfə çağırılır" testi dörd çağırışda keçməsin deyə. Bunu istəmirsinizsə:
new RecordingTransport(Response::json([])).
Logging
HttpTransport konstruktorda PSR-3 logger qəbul edir; verilməsə heç nə yazılmır.
Şəbəkə xətası və 400+ cavablar error səviyyəsində yazılır.
new HttpTransport(logger: $monolog);
