Skip to main content

Return Type Standards

This document outlines the team standards and best practices for implementing return types in our projects.

Introduction

Return types are a critical aspect of code quality and maintainability. Clear, specific return types improve code readability, enable better static analysis, and reduce bugs. This guide establishes our team's standards for implementing return types.

Core Principles

1. Avoid Void and Mixed Return Types

Whenever possible, avoid using void and mixed return types. If you find yourself using these types, question the design or architecture of that component.

// ❌ Avoid: Void return type
public function processOrder(Order $order): void
{
// Process logic
// No return value
}

// ✅ Better: Return a meaningful value
public function processOrder(Order $order): OrderResult
{
// Process logic
return new OrderResult($success, $message);
}

2. Action Classes Must Return Values

In Action classes, void return types are strictly prohibited. This is a strong contract (100%) that can only be overridden by team consensus (which is unlikely). Generally, with proper architecture, void returns naturally disappear.

// ❌ Prohibited: Action with void return
class CreateUserAction
{
public function execute(UserData $data): void
{
// Create user logic
}
}

// ✅ Required: Action with specific return type
class CreateUserAction
{
public function execute(UserData $data): UserCreationResult
{
// Create user logic
return new UserCreationResult($userId, $success, $message);
}
}

3. Handler Classes Should Return Values

In Handler classes, it's possible to avoid void return types in approximately 90% of cases. If you find yourself using void in a Handler, question the architecture, problem, and implementation to see if it can be changed (which is possible in 80-90% of cases).

// ❌ Questionable: Handler with void return
class PaymentNotificationHandler
{
public function handle(PaymentNotification $notification): void
{
// Process notification
}
}

// ✅ Preferred: Handler with specific return type
class PaymentNotificationHandler
{
public function handle(PaymentNotification $notification): HandlerResult
{
// Process notification
return new HandlerResult($success, $message);
}
}

4. Avoid Mixed Return Types

Avoid using mixed return types whenever possible. There are several better alternatives:

4.1 Union Types

Use union types (separating return types with |) instead of mixed. Although they might not look as elegant, they are much better, more precise, readable, and defensive than mixed.

// ❌ Avoid: Mixed return type
public function getConfig(string $key): mixed
{
// Return different types based on key
}

// ✅ Better: Union return type
public function getConfig(string $key): string|int|bool|null
{
// Return different types based on key
}

4.2 DTOs with Required and Optional Properties

Create DTOs with required and optional properties to handle varying return data.

// ✅ Good: Using a DTO instead of mixed
class ConfigResult
{
public readonly mixed $value;
public readonly string $type;
public readonly bool $exists;

public function __construct(mixed $value, string $type, bool $exists = true)
{
$this->value = $value;
$this->type = $type;
$this->exists = $exists;
}
}

public function getConfig(string $key): ConfigResult
{
// Logic to retrieve config
return new ConfigResult($value, gettype($value), $exists);
}

4.3 Typed Collections

Use collections with specific types instead of arrays or mixed types.

// ❌ Avoid: Array return type
public function getUsers(array $criteria): array
{
// Return users
}

// ✅ Better: Typed collection
public function getUsers(array $criteria): UserCollection
{
// Return users
return new UserCollection($users);
}

5. Array Return Types Are Prohibited

Array return types are generally prohibited, except when required by higher-level contracts outside your team's control (e.g., framework requirements).

// ❌ Prohibited: Array return type
public function getUserData(int $userId): array
{
// Return user data as array
}

// ✅ Required: Specific return type
public function getUserData(int $userId): UserData
{
// Return user data as a structured object
return new UserData($name, $email, $roles);
}

Layer Communication

Each layer should report results to the layer above it (success/failure, reason, output). This reduces coupling between layers.

// ❌ Problematic: Tightly coupled layers
class UserController
{
public function store(Request $request): Response
{
$user = new User();
$user->name = $request->name;
$user->email = $request->email;
$user->save();

return response()->json(['message' => 'User created']);
}
}

Summary

These standards establish our team's approach to return types. Following these guidelines will ensure consistency across our codebase and reduce potential bugs:

  1. Avoid void and mixed return types whenever possible
  2. Action classes must never use void return types
  3. Handler classes should avoid void return types in most cases
  4. Use union types, DTOs, or typed collections instead of mixed
  5. Avoid array return types in favor of structured objects
  6. Ensure each layer reports results to the layer above it
info

Team CTO Guidance:

"Clear and specific return types are a cornerstone of maintainable code. They create contracts between components that can be verified by static analysis tools and make the code more self-documenting. Always strive to return the most specific type possible."

Actionable Rule:

  • Review return types during code reviews to ensure they follow these standards
  • Consider using static analysis tools to enforce these rules automatically