|
| 1 | +# Barta Package Development Guidelines |
| 2 | + |
| 3 | +These guidelines ensure consistency across the Barta SMS package codebase. |
| 4 | + |
| 5 | +## PHP Code Style |
| 6 | + |
| 7 | +### General Rules |
| 8 | + |
| 9 | +- Always use `declare(strict_types=1);` at the top of every PHP file |
| 10 | +- Use PHP 8.4+ features (constructor promotion, named arguments, readonly, etc.) |
| 11 | +- Follow PSR-12 coding standards (enforced by Laravel Pint) |
| 12 | +- Run `composer format` before committing |
| 13 | + |
| 14 | +### Class Structure |
| 15 | + |
| 16 | +```php |
| 17 | +<?php |
| 18 | + |
| 19 | +declare(strict_types=1); |
| 20 | + |
| 21 | +namespace Larament\Barta\FeatureName; |
| 22 | + |
| 23 | +// Imports grouped: PHP core, Laravel, Package internal |
| 24 | +use Exception; |
| 25 | +use Illuminate\Support\Facades\Http; |
| 26 | +use Larament\Barta\Data\ResponseData; |
| 27 | + |
| 28 | +final class ClassName |
| 29 | +{ |
| 30 | + // 1. Properties (with type hints) |
| 31 | + // 2. Constructor |
| 32 | + // 3. Public methods |
| 33 | + // 4. Protected methods |
| 34 | + // 5. Private methods |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +### Naming Conventions |
| 39 | + |
| 40 | +| Element | Convention | Example | |
| 41 | +| ----------- | ---------------------------- | ------------------------------- | |
| 42 | +| Classes | PascalCase, singular | `EsmsDriver`, `SendSmsJob` | |
| 43 | +| Methods | camelCase, verb-first | `send()`, `formatPhoneNumber()` | |
| 44 | +| Properties | camelCase | `$recipients`, `$baseUrl` | |
| 45 | +| Config keys | snake_case | `api_token`, `sender_id` | |
| 46 | +| Env vars | UPPER_SNAKE_CASE with prefix | `BARTA_ESMS_TOKEN` | |
| 47 | + |
| 48 | +### Class Modifiers |
| 49 | + |
| 50 | +- Use `final` for concrete implementations (drivers, jobs) |
| 51 | +- Use `abstract` for base classes meant to be extended |
| 52 | +- Use `readonly` for immutable data objects |
| 53 | +- Combine traits on single line: `use Trait1, Trait2, Trait3;` |
| 54 | + |
| 55 | +--- |
| 56 | + |
| 57 | +## Driver Development |
| 58 | + |
| 59 | +### Creating a New Driver |
| 60 | + |
| 61 | +1. Extend `AbstractDriver` |
| 62 | +2. Mark as `final class` |
| 63 | +3. Implement `send(): ResponseData` |
| 64 | +4. Override `validate()` to check driver-specific config |
| 65 | +5. Use `$this->recipients` (array) and `$this->message` (string) |
| 66 | + |
| 67 | +### Driver Template |
| 68 | + |
| 69 | +```php |
| 70 | +<?php |
| 71 | + |
| 72 | +declare(strict_types=1); |
| 73 | + |
| 74 | +namespace Larament\Barta\Drivers; |
| 75 | + |
| 76 | +use Illuminate\Support\Facades\Http; |
| 77 | +use Larament\Barta\Data\ResponseData; |
| 78 | +use Larament\Barta\Exceptions\BartaException; |
| 79 | + |
| 80 | +final class NewGatewayDriver extends AbstractDriver |
| 81 | +{ |
| 82 | + private string $baseUrl = 'https://api.gateway.com'; |
| 83 | + |
| 84 | + public function send(): ResponseData |
| 85 | + { |
| 86 | + $this->validate(); |
| 87 | + |
| 88 | + $response = Http::baseUrl($this->baseUrl) |
| 89 | + ->withToken($this->config['api_token']) |
| 90 | + ->timeout($this->timeout) |
| 91 | + ->retry($this->retry, $this->retryDelay) |
| 92 | + ->acceptJson() |
| 93 | + ->post('/sms/send', [ |
| 94 | + 'to' => implode(',', $this->recipients), |
| 95 | + 'message' => $this->message, |
| 96 | + 'sender' => $this->config['sender_id'], |
| 97 | + ]) |
| 98 | + ->json(); |
| 99 | + |
| 100 | + if ($response['status'] !== 'success') { |
| 101 | + throw new BartaException($response['error']); |
| 102 | + } |
| 103 | + |
| 104 | + return new ResponseData( |
| 105 | + success: true, |
| 106 | + data: $response, |
| 107 | + ); |
| 108 | + } |
| 109 | + |
| 110 | + protected function validate(): void |
| 111 | + { |
| 112 | + parent::validate(); |
| 113 | + |
| 114 | + if (! $this->config['api_token']) { |
| 115 | + throw new BartaException('Please set api_token for NewGateway in config/barta.php.'); |
| 116 | + } |
| 117 | + |
| 118 | + if (! $this->config['sender_id']) { |
| 119 | + throw new BartaException('Please set sender_id for NewGateway in config/barta.php.'); |
| 120 | + } |
| 121 | + } |
| 122 | +} |
| 123 | +``` |
| 124 | + |
| 125 | +### Driver Checklist |
| 126 | + |
| 127 | +- [ ] Extends `AbstractDriver` |
| 128 | +- [ ] Uses `final class` |
| 129 | +- [ ] Calls `$this->validate()` first in `send()` |
| 130 | +- [ ] Uses `implode(',', $this->recipients)` for bulk support |
| 131 | +- [ ] Uses `$this->timeout`, `$this->retry`, `$this->retryDelay` |
| 132 | +- [ ] Returns `ResponseData` object |
| 133 | +- [ ] Throws `BartaException` on API errors |
| 134 | +- [ ] Validates required config in `validate()` |
| 135 | +- [ ] Registered in `BartaManager` |
| 136 | +- [ ] Config added to `config/barta.php` |
| 137 | +- [ ] Has comprehensive tests |
| 138 | + |
| 139 | +--- |
| 140 | + |
| 141 | +## Testing Guidelines |
| 142 | + |
| 143 | +### Test File Structure |
| 144 | + |
| 145 | +- Location: `tests/` mirrors `src/` structure |
| 146 | +- Naming: `{ClassName}Test.php` |
| 147 | +- Use Pest PHP (not PHPUnit classes) |
| 148 | + |
| 149 | +### Test Patterns |
| 150 | + |
| 151 | +```php |
| 152 | +<?php |
| 153 | + |
| 154 | +declare(strict_types=1); |
| 155 | + |
| 156 | +use Illuminate\Support\Facades\Http; |
| 157 | +use Larament\Barta\Drivers\NewDriver; |
| 158 | +use Larament\Barta\Exceptions\BartaException; |
| 159 | + |
| 160 | +beforeEach(function () { |
| 161 | + // Set up config for each test |
| 162 | + config()->set('barta.drivers.new.api_token', 'test_token'); |
| 163 | + config()->set('barta.drivers.new.sender_id', 'test_sender'); |
| 164 | +}); |
| 165 | + |
| 166 | +it('can instantiate the driver', function () { |
| 167 | + $driver = new NewDriver(config('barta.drivers.new')); |
| 168 | + expect($driver)->toBeInstanceOf(NewDriver::class); |
| 169 | +}); |
| 170 | + |
| 171 | +it('sends sms successfully', function () { |
| 172 | + Http::fake([ |
| 173 | + 'https://api.gateway.com/*' => Http::response(['status' => 'success'], 200), |
| 174 | + ]); |
| 175 | + |
| 176 | + $driver = new NewDriver(config('barta.drivers.new')); |
| 177 | + $response = $driver->to('8801700000000')->message('Test')->send(); |
| 178 | + |
| 179 | + expect($response->success)->toBeTrue(); |
| 180 | + |
| 181 | + Http::assertSent(function ($request) { |
| 182 | + return str_contains($request->url(), 'gateway.com') && |
| 183 | + $request['message'] === 'Test'; |
| 184 | + }); |
| 185 | +}); |
| 186 | + |
| 187 | +it('throws exception on api error', function () { |
| 188 | + Http::fake([ |
| 189 | + '*' => Http::response(['status' => 'error', 'error' => 'Failed'], 200), |
| 190 | + ]); |
| 191 | + |
| 192 | + $driver = new NewDriver(config('barta.drivers.new')); |
| 193 | + $driver->to('8801700000000')->message('Test')->send(); |
| 194 | +})->throws(BartaException::class); |
| 195 | + |
| 196 | +it('throws exception if config missing', function () { |
| 197 | + config()->set('barta.drivers.new.api_token', null); |
| 198 | + |
| 199 | + $driver = new NewDriver(config('barta.drivers.new')); |
| 200 | + $driver->to('8801700000000')->message('Test')->send(); |
| 201 | +})->throws(BartaException::class, 'Please set api_token'); |
| 202 | +``` |
| 203 | + |
| 204 | +### Test Coverage Requirements |
| 205 | + |
| 206 | +- Every driver: instantiation, success, bulk, error, config validation |
| 207 | +- Every public method must have at least one test |
| 208 | +- Use `Http::fake()` for API calls |
| 209 | +- Use `Bus::fake()` for queue tests |
| 210 | +- Use `Log::shouldReceive()` for log assertions |
| 211 | + |
| 212 | +### Test Commands |
| 213 | + |
| 214 | +```bash |
| 215 | +// turbo-all |
| 216 | +composer test # Run all tests |
| 217 | +composer test-coverage # Run with coverage report |
| 218 | +composer analyse # Run PHPStan |
| 219 | +``` |
| 220 | + |
| 221 | +--- |
| 222 | + |
| 223 | +## Exception Handling |
| 224 | + |
| 225 | +### Use Package Exception |
| 226 | + |
| 227 | +Always throw `BartaException` for package-related errors: |
| 228 | + |
| 229 | +```php |
| 230 | +use Larament\Barta\Exceptions\BartaException; |
| 231 | + |
| 232 | +// API errors |
| 233 | +throw new BartaException($response['error']); |
| 234 | + |
| 235 | +// Config errors (use descriptive message with config path) |
| 236 | +throw new BartaException('Please set api_token for ESMS in config/barta.php.'); |
| 237 | + |
| 238 | +// Use static factory methods when available |
| 239 | +throw BartaException::invalidNumber($number); |
| 240 | +throw BartaException::missingRecipient(); |
| 241 | +throw BartaException::missingMessage(); |
| 242 | +``` |
| 243 | + |
| 244 | +--- |
| 245 | + |
| 246 | +## Configuration |
| 247 | + |
| 248 | +### Config File Pattern |
| 249 | + |
| 250 | +```php |
| 251 | +'drivers' => [ |
| 252 | + 'drivername' => [ |
| 253 | + 'api_token' => env('BARTA_DRIVERNAME_TOKEN'), |
| 254 | + 'api_key' => env('BARTA_DRIVERNAME_API_KEY'), |
| 255 | + 'sender_id' => env('BARTA_DRIVERNAME_SENDER_ID'), |
| 256 | + ], |
| 257 | +], |
| 258 | +``` |
| 259 | + |
| 260 | +### Env Variable Naming |
| 261 | + |
| 262 | +- Prefix: `BARTA_` |
| 263 | +- Driver name in uppercase: `ESMS`, `MIMSMS` |
| 264 | +- Config key in uppercase: `TOKEN`, `SENDER_ID` |
| 265 | +- Full pattern: `BARTA_{DRIVER}_{KEY}` |
| 266 | + |
| 267 | +--- |
| 268 | + |
| 269 | +## ResponseData Contract |
| 270 | + |
| 271 | +Always return `ResponseData` from driver's `send()`: |
| 272 | + |
| 273 | +```php |
| 274 | +return new ResponseData( |
| 275 | + success: true, // bool: whether operation succeeded |
| 276 | + data: $response, // array: raw API response |
| 277 | + errors: [], // array: error messages (optional) |
| 278 | +); |
| 279 | +``` |
| 280 | + |
| 281 | +--- |
| 282 | + |
| 283 | +## Quality Assurance Checklist |
| 284 | + |
| 285 | +Before committing: |
| 286 | + |
| 287 | +- [ ] `composer format` - Code formatted with Pint |
| 288 | +- [ ] `composer test` - All tests pass |
| 289 | +- [ ] `composer analyse` - PHPStan passes (level max) |
| 290 | +- [ ] README updated if adding features |
| 291 | +- [ ] CHANGELOG.md updated for releases |
0 commit comments