Skip to content

Commit 2a600e9

Browse files
Update AGENTS.md and add Kakao BMS documentation
- Updated AGENTS.md to reflect the latest SDK generation date and commit hash. - Enhanced the overview of the SOLAPI PHP SDK to include PSR-18 HTTP client abstraction. - Revised the structure section to detail the new BMS components and their organization. - Added a new AGENTS.md file for Kakao BMS, outlining its structure, chat bubble types, validation rules, and usage patterns.
1 parent a27d32f commit 2a600e9

2 files changed

Lines changed: 167 additions & 33 deletions

File tree

AGENTS.md

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,33 @@
11
# SOLAPI PHP SDK
22

3-
**Generated:** 2026-01-21
4-
**Commit:** b68825d
3+
**Generated:** 2026-01-27
4+
**Commit:** a27d32f
55
**Branch:** master
66

77
## OVERVIEW
88

9-
PHP SDK for SOLAPI messaging API (SMS, LMS, MMS, Kakao Alimtalk, Voice, Fax) targeting Korean telecom. Zero external dependencies, PHP 7.1+.
9+
PHP SDK for SOLAPI messaging API (SMS, LMS, MMS, Kakao Alimtalk/BMS, Voice, Fax) targeting Korean telecom. PSR-18 HTTP client abstraction, PHP 7.1+.
1010

1111
## STRUCTURE
1212

1313
```
1414
solapi-php/
1515
├── src/
1616
│ ├── Services/ # Entry point (SolapiMessageService)
17-
│ ├── Libraries/ # HTTP client, auth, utilities
17+
│ ├── Libraries/ # HTTP client, auth, utilities (4 files)
1818
│ ├── Models/
1919
│ │ ├── Request/ # API request DTOs (7 files)
2020
│ │ ├── Response/ # API response DTOs (17 files)
21-
│ │ ├── Kakao/ # Kakao message options
22-
│ │ ├── Voice/ # Voice message options
23-
│ │ └── Fax/ # Fax message options
24-
│ └── Exceptions/ # Custom exceptions (4 files)
21+
│ │ ├── Kakao/ # Kakao options (4 files)
22+
│ │ │ └── Bms/ # Brand Message Service (14 files) ← See Bms/AGENTS.md
23+
│ │ ├── Voice/ # Voice options (3 files)
24+
│ │ └── Fax/ # Fax options (1 file)
25+
│ └── Exceptions/ # Custom exceptions (5 files)
26+
├── tests/
27+
│ ├── Models/ # Unit tests
28+
│ └── E2E/ # Integration tests
2529
├── composer.json # PSR-4: Nurigo\Solapi\ → src/
26-
└── README.md
30+
└── phpunit.xml # Test configuration
2731
```
2832

2933
## WHERE TO LOOK
@@ -32,13 +36,17 @@ solapi-php/
3236
|------|----------|-------|
3337
| Send messages | `Services/SolapiMessageService.php` | Main entry point, all public methods |
3438
| Build message | `Models/Message.php` | Fluent builder, extends BaseMessage |
35-
| HTTP requests | `Libraries/Fetcher.php` | Singleton, CURL-based |
39+
| HTTP requests | `Libraries/Fetcher.php` | Singleton, PSR-18 client |
3640
| Auth header | `Libraries/Authenticator.php` | HMAC-SHA256, static method |
37-
| Kakao options | `Models/Kakao/KakaoOption.php` | pfId, templateId, buttons, bms |
41+
| HTTP transport | `Libraries/HttpClient.php` | stream_context-based, PSR-18 compliant |
42+
| Kakao Alimtalk | `Models/Kakao/KakaoOption.php` | pfId, templateId, buttons, variables |
43+
| Kakao BMS | `Models/Kakao/KakaoBms.php` | Brand messages, 8 chatBubbleTypes |
44+
| BMS validation | `Models/Kakao/Bms/BmsValidator.php` | Field requirements by type |
3845
| Voice options | `Models/Voice/VoiceOption.php` | voiceType, headerMessage, tailMessage |
39-
| Error handling | `Exceptions/` | BaseException, CurlException, MessageNotReceivedException |
40-
| Request params | `Models/Request/` | SendRequest, GetMessagesRequest, etc. |
41-
| Response parsing | `Models/Response/` | SendResponse, GroupMessageResponse, etc. |
46+
| Fax options | `Models/Fax/FaxOption.php` | fileIds array |
47+
| Error handling | `Exceptions/` | BaseException, HttpException, BmsValidationException |
48+
| Request DTOs | `Models/Request/` | SendRequest, GetMessagesRequest, etc. |
49+
| Response DTOs | `Models/Response/` | SendResponse, GroupMessageResponse, etc. |
4250

4351
## CODE MAP
4452

@@ -50,19 +58,33 @@ $response = $service->send($message);
5058

5159
**Call Flow:**
5260
```
53-
SolapiMessageService → Fetcher (singleton) → Authenticator (static)
54-
→ CURL → api.solapi.com
55-
→ Response DTOs
61+
SolapiMessageService
62+
→ Fetcher::getInstance() [singleton]
63+
→ Authenticator::getAuthorizationHeaderInfo() [static]
64+
→ NullEliminator::array_null_eliminate() [static]
65+
→ HttpClient::sendRequest() [PSR-18]
66+
→ stream_context + file_get_contents
67+
→ Response DTOs
5668
```
5769

5870
**Key Classes:**
5971
| Class | Type | Role |
6072
|-------|------|------|
6173
| `SolapiMessageService` | Service | Primary API (send, uploadFile, getMessages, getGroups, getBalance) |
62-
| `Message` | Model | Message builder with fluent setters |
63-
| `Fetcher` | Library | HTTP client singleton, handles all API requests |
64-
| `Authenticator` | Library | Generates HMAC-SHA256 auth headers |
65-
| `NullEliminator` | Library | Removes null values before JSON serialization |
74+
| `Message` | Model | Fluent builder with 12 setters |
75+
| `Fetcher` | Library | Singleton HTTP client, credential storage |
76+
| `HttpClient` | Library | PSR-18 stream-based implementation |
77+
| `Authenticator` | Library | HMAC-SHA256 auth header generation |
78+
| `NullEliminator` | Library | Recursive null removal for JSON |
79+
| `BmsValidator` | Validator | BMS field validation by chatBubbleType |
80+
81+
**Model Hierarchy:**
82+
```
83+
BaseMessage → Message (fluent builder)
84+
BaseKakaoOption → KakaoOption (fluent builder)
85+
└── KakaoBms (fluent builder, 8 types)
86+
└── Bms/* components (14 files)
87+
```
6688

6789
## CONVENTIONS
6890

@@ -71,43 +93,61 @@ SolapiMessageService → Fetcher (singleton) → Authenticator (static)
7193
**Patterns:**
7294
- Fluent builder: `$msg->setTo("...")->setFrom("...")->setText("...")`
7395
- Singleton: `Fetcher::getInstance($key, $secret)`
74-
- Public properties with getters/setters on models
75-
- Korean PHPDoc comments (domain-specific)
96+
- Public properties with getter/setter pairs on models
97+
- Korean PHPDoc comments (수신번호, 발신번호, 메시지 내용)
7698

7799
**Type Safety:**
78100
- Full type hints on method params/returns
79101
- PHPDoc `@var`, `@param`, `@return`, `@throws` annotations
102+
- PHP 7.1 compatible (no union types, no enums)
103+
104+
**Enum-Like Constants:**
105+
- `VoiceType::FEMALE`, `VoiceType::MALE`
106+
- `BmsChatBubbleType::TEXT`, `IMAGE`, `WIDE`, etc.
107+
- All have `values()` static method
80108

81109
**Tidy First (Kent Beck):**
82110
- Separate structural and behavioral changes into distinct commits
83111
- Tidy related code before making feature changes
84-
- Guard clauses, helper variables/functions, code proximity, symmetry normalization, delete unused code
112+
- Guard clauses, helper variables/functions, code proximity
85113

86114
## ANTI-PATTERNS
87115

88-
- **Avoid catch-all nulls:** Many get* methods return `null` on any exception — check response validity
89-
- **Singleton state:** Fetcher singleton retains credentials — don't mix different API keys in same process
116+
- **Silent null returns:** get* methods return `null` on any exception — always check response validity
117+
- **Singleton state:** Fetcher retains credentials — don't mix API keys in same process
90118
- **No interfaces:** Service/Fetcher have no contracts — mocking requires concrete class extension
91-
- **SSL verification disabled:** `CURLOPT_SSL_VERIFYPEER = false` in Fetcher
119+
- **Hardcoded timezone:** `Asia/Seoul` set in Authenticator — affects global timezone
120+
- **Hardcoded country:** `"82"` default in BaseMessage — Korean-only by default
92121

93122
## UNIQUE STYLES
94123

95-
- **Korean comments:** PHPDoc descriptions in Korean (수신번호, 발신번호, 메시지 내용)
96-
- **Default country:** `"82"` (Korea) hardcoded in BaseMessage
97-
- **Timezone:** `Asia/Seoul` set in Authenticator
124+
- **Korean comments:** PHPDoc descriptions in Korean
125+
- **PSR-18 via stream:** Uses `file_get_contents` + `stream_context_create`, not cURL
126+
- **Null elimination:** Removes nulls before JSON serialization
98127

99128
## COMMANDS
100129

101130
```bash
102131
# Install
103132
composer require solapi/sdk
104133

105-
# No local tests — see solapi-php-examples repo
134+
# Run all tests
135+
composer test
136+
137+
# Run unit tests only
138+
composer test:unit
139+
140+
# Run E2E tests only
141+
composer test:e2e
142+
143+
# Run with coverage
144+
composer test:coverage
106145
```
107146

108147
## NOTES
109148

110149
- **Examples:** External repo at `github.com/solapi/solapi-php-examples`
111150
- **API docs:** `developers.solapi.com`
112-
- **PHP requirement:** 7.1+ (ext-curl, ext-json required)
113-
- **TODO in README:** Missing documentation link (line 19)
151+
- **PHP requirement:** 7.1+ (ext-json, allow_url_fopen or custom PSR-18 client)
152+
- **Dependencies:** psr/http-client, psr/http-message, nyholm/psr7
153+
- **BMS details:** See `src/Models/Kakao/Bms/AGENTS.md` for Brand Message Service specifics

src/Models/Kakao/Bms/AGENTS.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Kakao BMS (Brand Message Service)
2+
3+
**Parent:** See root `AGENTS.md` for SDK overview.
4+
5+
## OVERVIEW
6+
7+
14-file subsystem for Kakao Brand Message Service. Supports 8 chatBubbleTypes with type-specific required fields and validation.
8+
9+
## STRUCTURE
10+
11+
```
12+
Bms/
13+
├── BmsChatBubbleType.php # 8 type constants + values()
14+
├── BmsValidator.php # Validates fields by chatBubbleType
15+
├── BmsButton.php # Button with 8 linkTypes
16+
├── BmsCarousel.php # head + list[] + tail
17+
├── BmsCarouselHead.php # Carousel header
18+
├── BmsCarouselTail.php # Carousel footer
19+
├── BmsCarouselFeedItem.php # Feed carousel item
20+
├── BmsCarouselCommerceItem.php # Commerce carousel item
21+
├── BmsCommerce.php # Product info with pricing
22+
├── BmsCoupon.php # Coupon with 5 title formats
23+
├── BmsVideo.php # Video (Kakao TV only)
24+
├── BmsMainWideItem.php # WIDE_ITEM_LIST main item
25+
├── BmsSubWideItem.php # WIDE_ITEM_LIST sub items (min 3)
26+
└── BmsButtonLinkType.php # 8 link type constants
27+
```
28+
29+
## CHAT BUBBLE TYPES
30+
31+
| Type | Required Fields | Notes |
32+
|------|-----------------|-------|
33+
| `TEXT` | (none) | Uses Message.text |
34+
| `IMAGE` | imageId | |
35+
| `WIDE` | imageId | |
36+
| `WIDE_ITEM_LIST` | header, mainWideItem, subWideItemList | subWideItemList min 3 items |
37+
| `COMMERCE` | imageId, commerce, buttons | |
38+
| `CAROUSEL_FEED` | carousel | Uses BmsCarouselFeedItem[] |
39+
| `CAROUSEL_COMMERCE` | carousel | Uses BmsCarouselCommerceItem[] |
40+
| `PREMIUM_VIDEO` | video | videoUrl must start with `https://tv.kakao.com/` |
41+
42+
## VALIDATION RULES
43+
44+
**BmsValidator.php** enforces:
45+
46+
1. **Required fields** by chatBubbleType (see table above)
47+
2. **WIDE_ITEM_LIST**: `subWideItemList` minimum 3 items
48+
3. **Commerce pricing** (mutually exclusive):
49+
- Cannot use both `discountRate` AND `discountFixed`
50+
- If `discountPrice` set, must have `discountRate` OR `discountFixed`
51+
- If `discountRate`/`discountFixed` set, must have `discountPrice`
52+
4. **PREMIUM_VIDEO**: `videoUrl` must start with `https://tv.kakao.com/`
53+
54+
## BUTTON LINK TYPES
55+
56+
| Type | Description | Required Fields |
57+
|------|-------------|-----------------|
58+
| `WL` | Web Link | linkMobile |
59+
| `AL` | App Link | linkMobile |
60+
| `AC` | App Channel | (none) |
61+
| `BK` | Bot Keyword | (none) |
62+
| `MD` | Message Delivery | (none) |
63+
| `BC` | Bot Channel | (none) |
64+
| `BT` | Bot Talk | (none) |
65+
| `BF` | Business Form | (none) |
66+
67+
## TARGETING TYPES
68+
69+
- `M`: Marketing consent + Kakao channel friend
70+
- `N`: Marketing consent only
71+
- `I`: Kakao channel friend only
72+
73+
## USAGE PATTERN
74+
75+
```php
76+
use Nurigo\Solapi\Models\Kakao\KakaoBms;
77+
use Nurigo\Solapi\Models\Kakao\Bms\BmsChatBubbleType;
78+
use Nurigo\Solapi\Models\Kakao\Bms\BmsValidator;
79+
80+
$bms = new KakaoBms();
81+
$bms->setTargeting('I')
82+
->setChatBubbleType(BmsChatBubbleType::IMAGE)
83+
->setImageId('uploaded-image-id');
84+
85+
// Validate before sending
86+
BmsValidator::validate($bms); // Throws BmsValidationException on failure
87+
```
88+
89+
## ANTI-PATTERNS
90+
91+
- **Missing validation:** Always call `BmsValidator::validate()` before sending
92+
- **Wrong type combinations:** Each chatBubbleType has specific required fields
93+
- **Invalid video URL:** PREMIUM_VIDEO only accepts Kakao TV URLs
94+
- **Insufficient sub items:** WIDE_ITEM_LIST requires exactly 3+ subWideItemList items

0 commit comments

Comments
 (0)