Code Style Guide¶
Coding standards and style guidelines for the Evolution API Laravel package.
Overview¶
This package follows:
- PSR-12 - Extended Coding Style Guide
- Laravel conventions - Framework patterns
- PHP 8.2+ features - Modern PHP syntax
We use Laravel Pint for automatic code formatting.
Running Code Style Checks¶
# Check for issues
composer cs-check
# or
./vendor/bin/pint --test
# Fix issues automatically
composer cs-fix
# or
./vendor/bin/pint
PHP Version¶
- Minimum: PHP 8.2
- Use modern PHP features when appropriate
Required Features¶
// Readonly properties
public readonly string $instanceName;
// Constructor property promotion
public function __construct(
public readonly string $name,
public readonly ?string $description = null,
) {}
// Named arguments
ApiResponse::success(
data: $result,
statusCode: 200,
);
// Match expressions
$status = match ($code) {
200 => 'success',
400 => 'bad_request',
default => 'error',
};
// Null-safe operator
$name = $user?->profile?->name;
// Enums
enum MessageType: string
{
case TEXT = 'text';
case IMAGE = 'image';
}
Strict Types¶
Always declare strict types:
Naming Conventions¶
Classes¶
Methods and Functions¶
// camelCase
public function sendMessage(): void {}
public function getConnectionState(): array {}
private function formatPhoneNumber(): string {}
Variables and Properties¶
// camelCase
private string $instanceName;
protected array $sentMessages = [];
$messageContent = $data['message'];
Constants¶
Database¶
Type Declarations¶
Always use type hints:¶
public function sendText(
string $instanceName,
string $number,
string $text,
array $options = []
): ApiResponse {
// ...
}
Use union types when appropriate:¶
Use nullable types:¶
Return Types¶
Always declare return types:¶
public function isConnected(): bool {}
public function getData(): array {}
public function process(): void {}
public function find(): ?Model {}
Use static for fluent interfaces:¶
public function stubResponse(string $operation, mixed $response): static
{
$this->responses[$operation] = $response;
return $this;
}
DocBlocks¶
When to use:¶
/**
* Use docblocks for:
* - Complex array structures
* - Generic collections
* - Inherited methods that need clarification
* - Public API methods
*/
/**
* Send a text message to a WhatsApp number.
*
* @param array<string, mixed> $options Additional options
* @return array<string, mixed> API response data
*
* @throws EvolutionApiException
*/
public function sendText(
string $instanceName,
string $number,
string $text,
array $options = []
): array {
// ...
}
When NOT to use:¶
// Don't add redundant docblocks
public function getMessage(): string
{
return $this->message;
}
// Type hint is sufficient, no docblock needed
Array type annotations:¶
/**
* @param array<string, mixed> $data
* @return array<int, Message>
*/
/**
* @var array<string, callable>
*/
private array $handlers = [];
Class Structure¶
Order class members as follows:
class ExampleService
{
// 1. Traits
use HasEvents;
use Loggable;
// 2. Constants
public const DEFAULT_TIMEOUT = 30;
private const CACHE_PREFIX = 'evolution_';
// 3. Static properties
protected static array $instances = [];
// 4. Properties (public, protected, private)
public readonly string $name;
protected Client $client;
private array $cache = [];
// 5. Constructor
public function __construct(
public readonly string $instanceName,
protected Client $client,
) {}
// 6. Static methods
public static function create(string $name): static {}
// 7. Public methods
public function send(): void {}
public function receive(): array {}
// 8. Protected methods
protected function validate(): bool {}
// 9. Private methods
private function formatData(): array {}
}
Method Length¶
- Keep methods under 20 lines when possible
- Extract complex logic into private methods
- Each method should do one thing well
// Good: Short, focused methods
public function send(Message $message): ApiResponse
{
$this->validate($message);
$formatted = $this->format($message);
return $this->client->post($formatted);
}
private function validate(Message $message): void
{
// Validation logic
}
private function format(Message $message): array
{
// Formatting logic
}
Conditionals¶
Early returns:¶
// Good: Early return
public function process(?string $data): void
{
if ($data === null) {
return;
}
// Process data...
}
// Avoid: Nested conditions
public function process(?string $data): void
{
if ($data !== null) {
// Deep nesting...
}
}
Match expressions over switch:¶
// Good: Match expression
$result = match ($status) {
'sent' => MessageStatus::SENT,
'delivered' => MessageStatus::DELIVERED,
default => MessageStatus::UNKNOWN,
};
// Avoid: Switch statement for simple cases
switch ($status) {
case 'sent':
$result = MessageStatus::SENT;
break;
// ...
}
Spacing and Formatting¶
Array formatting:¶
// Short arrays on one line
$simple = ['a', 'b', 'c'];
// Long arrays with trailing commas
$config = [
'timeout' => 30,
'retries' => 3,
'verify_ssl' => true,
];
Method chaining:¶
Closures:¶
// Short closures (arrow functions)
$names = array_map(fn ($user) => $user->name, $users);
// Multi-line closures
$filtered = array_filter($items, function ($item) {
return $item->isActive()
&& $item->hasPermission();
});
Imports¶
Order and grouping:¶
<?php
declare(strict_types=1);
namespace Lynkbyte\EvolutionApi\Services;
// PHP classes
use Exception;
use InvalidArgumentException;
// Vendor classes
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Collection;
// Package classes
use Lynkbyte\EvolutionApi\DTOs\ApiResponse;
use Lynkbyte\EvolutionApi\Exceptions\EvolutionApiException;
Never use aliases unless necessary:¶
// Good
use Lynkbyte\EvolutionApi\Exceptions\ValidationException;
// Only if there's a conflict
use Lynkbyte\EvolutionApi\Exceptions\ValidationException as EvolutionValidationException;
use Illuminate\Validation\ValidationException;
Error Handling¶
Use specific exceptions:¶
// Good: Specific exception
throw new InstanceNotFoundException($instanceName);
// Avoid: Generic exception
throw new Exception('Instance not found');
Exception messages:¶
// Good: Descriptive message
throw new ValidationException(
"Phone number '{$number}' is not a valid format. Expected format: +5511999999999"
);
// Avoid: Vague message
throw new ValidationException('Invalid phone');
Pint Configuration¶
Our pint.json configuration:
{
"preset": "laravel",
"rules": {
"declare_strict_types": true,
"final_class": false,
"ordered_class_elements": {
"order": [
"use_trait",
"constant_public",
"constant_protected",
"constant_private",
"property_public_static",
"property_protected_static",
"property_private_static",
"property_public",
"property_protected",
"property_private",
"construct",
"destruct",
"method_public_static",
"method_protected_static",
"method_private_static",
"method_public",
"method_protected",
"method_private"
]
}
}
}
Static Analysis¶
We use PHPStan at level 8 for maximum strictness:
Common fixes:¶
// Add generic types
/** @var Collection<int, Message> */
private Collection $messages;
// Handle nullable values
$name = $user?->name ?? 'Unknown';
// Explicit array types
/** @param array<string, mixed> $data */
Git Commit Messages¶
Follow conventional commits:
type(scope): description
feat(messages): add support for template messages
fix(webhooks): handle null payload gracefully
docs(readme): update installation instructions
test(services): add coverage for rate limiting
refactor(client): extract retry logic to trait
chore(deps): update laravel/pint to 1.x
Types: feat, fix, docs, test, refactor, chore, style, perf
Next Steps¶
- Testing Guide - Writing tests
- Development Setup - Environment setup