KissCore requires PHP 8.4.x or higher.
All code must be compatible with PHP 8.4+ features and syntax. Use modern PHP features when appropriate.
Use snake_case for function names:
function get_user_data(): array
function process_payment(): bool
function validate_email_address(string $email): boolUse snake_case for scalar variables:
$user_id = 123;
$email_address = 'user@example.com';
$is_active = true;
$total_amount = 99.99;Use camelCase for object methods:
class UserService
{
public function getUserById(int $id): ?User
public function validateCredentials(string $email, string $password): bool
public function sendNotification(User $user, string $message): void
}Use PascalCase for classes and enums:
class UserRepository
class PaymentProcessor
class DatabaseConnection
enum OrderStatus
enum UserRole
enum PaymentMethodUse $PascalCase for object variables:
$User = new User();
$PaymentProcessor = new PaymentProcessor();
$DatabaseConnection = DatabaseConnection::getInstance();Always place opening braces on the same line:
// Functions
function process_data() {
// code here
}
// Classes
class UserService {
// properties and methods
}
// Methods
public function getUserData(): array {
// code here
}
// Control structures
if ($condition) {
// code here
} elseif ($other_condition) {
// code here
} else {
// code here
}
foreach ($items as $item) {
// code here
}
while ($condition) {
// code here
}Always use elseif (one word) instead of else if (two words):
// ✅ Correct
if ($condition) {
// code
} elseif ($other_condition) {
// code
} else {
// code
}
// ❌ Wrong - will cause linter errors
if ($condition) {
// code
} else if ($other_condition) { // This breaks the linter
// code
}- NEVER use
mixed- always specify concrete types - Use specific types or union types instead
Use built-in PHP scalar types:
function calculate_total(int $quantity, float $price): float
function get_user_name(int $user_id): string
function is_user_active(int $user_id): bool
function get_user_tags(): arrayUse PHPStan type annotations for complex types:
/**
* @param array<string, int> $user_scores
* @param list<User> $users
* @return array{name: string, email: string, age: int}
*/
function process_user_data(array $user_scores, array $users): array
/**
* @param array<int, array{id: int, name: string}> $items
* @return Generator<int, User>
*/
function get_users_generator(array $items): Generator
/**
* @return array<string, string|int|bool>
*/
function get_config_data(): arrayUse PHP 8.0+ union types for multiple possible types:
function format_value(string|int|float $value): string
function get_user_data(int|string $identifier): ?User
function process_result(array|object $data): boolUse intersection types when objects must implement multiple interfaces:
function process_data(Countable&Iterator $data): void
function handle_request(RequestInterface&ValidatedInterface $request): ResponseUse readonly properties for immutable data:
class User
{
public function __construct(
public readonly int $id,
public readonly string $email,
public readonly DateTime $created_at
) {}
}Use enums for fixed sets of values:
enum UserStatus: string
{
case ACTIVE = 'active';
case INACTIVE = 'inactive';
case SUSPENDED = 'suspended';
public function getLabel(): string {
return match($this) {
self::ACTIVE => 'Active User',
self::INACTIVE => 'Inactive User',
self::SUSPENDED => 'Suspended User',
};
}
}Prefer match over switch for value returns:
function get_status_color(UserStatus $status): string {
return match($status) {
UserStatus::ACTIVE => 'green',
UserStatus::INACTIVE => 'gray',
UserStatus::SUSPENDED => 'red',
};
}Use named arguments for clarity in complex function calls:
create_user(
name: 'John Doe',
email: 'john@example.com',
is_active: true,
role: UserRole::ADMIN
);Use constructor property promotion to reduce boilerplate:
class DatabaseConfig
{
public function __construct(
private readonly string $host,
private readonly int $port,
private readonly string $database,
private readonly string $username,
private readonly string $password,
) {}
}Use nullable types when values can be null:
function find_user(int $id): ?User
function get_cached_data(string $key): ?string
function parse_json(string $json): ?arrayDefine complex type aliases for reusability:
/**
* @phpstan-type UserData array{id: int, name: string, email: string, active: bool}
* @phpstan-type ConfigArray array<string, string|int|bool>
* @phpstan-type RouteParams array<string, string>
*/
/**
* @param UserData $user_data
* @return ConfigArray
*/
function process_user_config(array $user_data): array<?php declare(strict_types=1);
/**
* @phpstan-type UserData array{id: int, name: string, email: string}
*/
class UserRepository
{
public function __construct(
private readonly DatabaseConnection $Connection,
private readonly LoggerInterface $Logger
) {}
/**
* @return list<UserData>
*/
public function getAllUsers(): array {
$query = "SELECT id, name, email FROM users";
return $this->Connection->fetchAll($query);
}
/**
* @param UserData $user_data
*/
public function createUser(array $user_data): int {
$user_id = $this->insertUserData($user_data);
$this->logUserCreation($user_id);
return $user_id;
}
public function getUserStatus(int $user_id): UserStatus {
$status = $this->Connection->fetchValue(
query: "SELECT status FROM users WHERE id = ?",
params: [$user_id]
);
return match($status) {
'active' => UserStatus::ACTIVE,
'inactive' => UserStatus::INACTIVE,
'suspended' => UserStatus::SUSPENDED,
default => UserStatus::INACTIVE,
};
}
private function insertUserData(array $user_data): int {
// implementation using modern PHP features
}
private function logUserCreation(int $user_id): void {
$this->Logger->info('User created', ['user_id' => $user_id]);
}
}/**
* @param array<string, string> $route_params
* @return array{action: string, params: array<string, string>}
*/
function parse_route_data(string $url, array $route_params): array {
$parsed_url = parse_url($url);
$path_segments = explode('/', trim($parsed_url['path'], '/'));
if (empty($path_segments[0])) {
return ['action' => 'home', 'params' => []];
}
return [
'action' => $path_segments[0],
'params' => $route_params
];
}- PHP Version: 8.4.x+ required
- Functions:
snake_case - Variables (scalars):
snake_case - Methods:
camelCase - Classes/Enums:
PascalCase - Objects:
$PascalCase - Braces: Same line
{ - Control structures: Use
elseifnotelse if - Types: Never
mixed, use specific types - Complex types: Use PHPStan annotations
- Nullable: Use
?Typewhen needed - Union types: Use
Type1|Type2syntax - Modern PHP: Use enums, match, readonly, constructor promotion
ALL code MUST pass linting checks before commit. Use these tools to ensure code quality:
# Check code style compliance
./bin/codestyle-check- Validates naming conventions
- Checks brace placement
- Ensures consistent formatting
- Must pass before commit
# Automatically fix code style issues
./bin/codestyle-fix- Fixes brace placement
- Corrects indentation
- Standardizes spacing
- Run before manual review
# Run PHPStan static analysis
./bin/codestyle-analyze- Validates type declarations
- Detects type errors
- Ensures no
mixedusage - Validates PHPStan annotations
- Must pass with zero errors
- Write code following naming conventions
- Run
./bin/codestyle-fixto auto-fix formatting - Run
./bin/codestyle-checkto validate style - Run
./bin/codestyle-analyzeto check types - Fix any errors reported by linters
- Commit only after all checks pass
- Style errors: Fix manually or use
codestyle-fix - Type errors: Update type annotations and fix code
- PHPStan errors: Never ignore, always resolve
- Mixed type usage: Replace with specific types
Remember: Linting errors are NOT optional - they must be resolved.