Appearance
💎 PHP Standards
Professional PHP coding standards for Laravel applications. Following these guidelines ensures consistent, maintainable, and high-quality code across your entire team.
📋 PSR-12 Compliance
All PHP code must follow PSR-12 coding standards. This is non-negotiable and enforced through automated tools.
Core Formatting Rules
✅ Good Example
php
<?php
declare(strict_types=1);
namespace App\Services;
use App\Models\User;
use App\Repositories\UserRepositoryInterface;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Hash;
class UserService
{
public function __construct(
private UserRepositoryInterface $userRepository
) {}
public function createUser(array $userData): User
{
$userData['password'] = Hash::make($userData['password']);
$user = $this->userRepository->create($userData);
Log::info('User created successfully', [
'user_id' => $user->id,
'email' => $user->email,
]);
return $user;
}
public function updateUser(int $id, array $userData): User
{
$user = $this->userRepository->find($id);
if (isset($userData['password'])) {
$userData['password'] = Hash::make($userData['password']);
}
$this->userRepository->update($id, $userData);
return $user->refresh();
}
}❌ Bad Example
php
<?php
// Missing strict types declaration
namespace App\Services;
use App\Models\User;
// Missing other necessary imports
class UserService {
// Missing visibility modifier
function createUser($userData) { // Missing type hints
$user = User::create([
'name' => $userData['name'],
'email' => $userData['email'],
'password' => bcrypt($userData['password']), // Direct model access in service
]);
// Missing logging
return $user;
}
}🏷️ Naming Conventions
Consistent naming is crucial for code readability and maintainability.
Classes
| Type | Convention | Example |
|---|---|---|
| Controllers | PascalCase + Controller suffix | UserController, PaymentController |
| Models | PascalCase, Singular | User, Product, OrderItem |
| Services | PascalCase + Service suffix | UserService, PaymentService |
| Repositories | PascalCase + Repository suffix | UserRepository, ProductRepository |
| Actions | PascalCase, Verb-Noun | CreateUser, ProcessPayment |
| Jobs | PascalCase, Verb-Noun | SendEmailNotification, ProcessOrder |
| Events | PascalCase, Past tense | UserCreated, OrderShipped |
| Listeners | PascalCase, Verb-Noun | SendWelcomeEmail, UpdateInventory |
| Traits | PascalCase, Adjective | Searchable, Sortable |
| Interfaces | PascalCase + Interface suffix | UserRepositoryInterface |
Methods and Functions
✅ Good Example
php
<?php
class UserService
{
// Action methods - use verbs
public function createUser(array $data): User
public function updateUser(int $id, array $data): User
public function deleteUser(int $id): bool
// Query methods - use get/find/search
public function getUserById(int $id): ?User
public function findUserByEmail(string $email): ?User
public function searchUsers(string $query): Collection
// Boolean methods - use is/has/can
public function isUserActive(User $user): bool
public function hasPermission(User $user, string $permission): bool
public function canAccessResource(User $user, Resource $resource): bool
// Calculation methods - use calculate/compute
public function calculateTotalRevenue(Collection $orders): int
public function computeDiscountAmount(Order $order): int
}❌ Bad Example
php
<?php
class UserService
{
// Inconsistent naming
public function user_create(array $data): User // snake_case
public function UpdateUser(int $id, array $data): User // PascalCase
public function delete(int $id): bool // Too generic
// Unclear method names
public function get(int $id): ?User // What are we getting?
public function check(User $user, string $permission): bool // Check what?
public function do(Collection $orders): int // Do what?
}Variables and Properties
✅ Good Example
php
<?php
class OrderController
{
// Properties - descriptive camelCase
private OrderService $orderService;
private PaymentGateway $paymentGateway;
private int $maxRetryAttempts = 3;
public function store(Request $request): JsonResponse
{
// Local variables - descriptive camelCase
$validatedData = $request->validated();
$totalAmount = $this->calculateTotal($validatedData);
$processingFee = $this->calculateProcessingFee($totalAmount);
$finalAmount = $totalAmount + $processingFee;
return response()->json([
'total' => $finalAmount,
]);
}
}❌ Bad Example
php
<?php
class OrderController
{
// Poor variable naming
private $os; // What is this?
private $pg; // Unclear abbreviation
private $max = 3; // Max what?
public function store(Request $request): JsonResponse
{
$d = $request->validated(); // Single letter
$t = $this->calculateTotal($d); // Unclear
$f = $this->calculateProcessingFee($t); // Unclear
$x = $t + $f; // What is x?
return response()->json(['total' => $x]);
}
}Constants
✅ Good Example
php
<?php
class PaymentService
{
public const MAX_RETRY_ATTEMPTS = 3;
public const DEFAULT_CURRENCY = 'USD';
public const PAYMENT_STATUS_PENDING = 'pending';
public const PAYMENT_STATUS_COMPLETED = 'completed';
public const PAYMENT_STATUS_FAILED = 'failed';
private const API_TIMEOUT_SECONDS = 30;
private const MAX_TRANSACTION_AMOUNT = 1000000; // cents
}❌ Bad Example
php
<?php
class PaymentService
{
public const maxRetries = 3; // Should be UPPER_SNAKE_CASE
public const Default_Currency = 'USD'; // Inconsistent
private const timeout = 30; // Missing descriptive name
}🎯 Type Declarations
Always use strict type declarations for all method parameters, return types, and property types.
Method Type Declarations
✅ Good Example
php
<?php
declare(strict_types=1);
namespace App\Services;
use App\Models\User;
use App\Models\Order;
use Illuminate\Support\Collection;
class OrderService
{
public function createOrder(
User $user,
array $items,
?string $couponCode = null
): Order {
// Implementation
}
public function calculateTotal(Order $order): int
{
return $order->items->sum('price');
}
public function getUserOrders(User $user, int $limit = 10): Collection
{
return $user->orders()->limit($limit)->get();
}
public function isOrderValid(Order $order): bool
{
return $order->items->isNotEmpty() && $order->total > 0;
}
}❌ Bad Example
php
<?php
// Missing strict types declaration
namespace App\Services;
class OrderService
{
// Missing parameter types
public function createOrder($user, $items, $couponCode = null)
{
// Implementation
}
// Missing return type
public function calculateTotal($order)
{
return $order->items->sum('price');
}
// Inconsistent types
public function getUserOrders($user, $limit = 10): Collection
{
return $user->orders()->limit($limit)->get();
}
}Property Type Declarations
✅ Good Example
php
<?php
declare(strict_types=1);
namespace App\Services;
use App\Repositories\UserRepositoryInterface;
use Illuminate\Support\Facades\Cache;
class UserService
{
// Constructor property promotion with types
public function __construct(
private UserRepositoryInterface $userRepository,
private EmailService $emailService,
private int $cacheTimeout = 3600
) {}
// Typed properties
private string $defaultRole = 'user';
private bool $autoVerifyEmail = false;
private array $allowedRoles = ['user', 'admin', 'moderator'];
}❌ Bad Example
php
<?php
namespace App\Services;
class UserService
{
// Missing types
private $userRepository;
private $emailService;
private $cacheTimeout = 3600;
// No constructor type hints
public function __construct($userRepository, $emailService)
{
$this->userRepository = $userRepository;
$this->emailService = $emailService;
}
}🎯 Named Arguments (PHP 8.0+)
Named arguments allow you to pass arguments to a function based on the parameter name, rather than parameter position. This is a powerful feature introduced in PHP 8.0 that significantly improves code readability and maintainability.
Why Use Named Arguments?
- Self-Documenting Code - Makes function calls immediately understandable
- Skip Optional Parameters - No need for
nullplaceholders - Order Independence - Arguments can be in any order
- Better IDE Support - Enhanced autocomplete and inline documentation
- Easier Refactoring - Less prone to errors when parameter order changes
- Code Review Friendly - Reviewers understand intentions without context
When to Use Named Arguments
✅ Always use named arguments when:
- Calling functions with 3+ parameters
- Using optional parameters (skip defaults)
- Working with boolean flags
- Parameter names add clarity
- Order might be confusing
❌ Optional for:
- Single parameter functions
- Very obvious 2-parameter functions (e.g.,
max($a, $b)) - When positional is clearer (rare cases)
Basic Usage
✅ Good Example
php
<?php
declare(strict_types=1);
namespace App\Services;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
class UserService
{
public function createUser(
string $name,
string $email,
string $password,
bool $isActive = true,
?string $role = null
): User {
return User::create([
'name' => $name,
'email' => $email,
'password' => Hash::make(value: $password),
'is_active' => $isActive,
'role' => $role ?? 'user',
]);
}
}
// Usage with named arguments
$user = $userService->createUser(
name: 'John Doe',
email: '[email protected]',
password: 'SecurePassword123!',
isActive: true,
role: 'admin'
);
// Skip optional parameters
$user = $userService->createUser(
name: 'Jane Smith',
email: '[email protected]',
password: 'SecurePassword123!'
// isActive defaults to true
// role defaults to null
);
// Order-independent
$user = $userService->createUser(
password: 'SecurePassword123!',
name: 'Bob Johnson',
email: '[email protected]'
);❌ Bad Example
php
<?php
// Hard to understand what each parameter means
$user = $userService->createUser(
'John Doe',
'[email protected]',
'SecurePassword123!',
true,
'admin'
);
// Need to pass null for optional parameters
$user = $userService->createUser(
'Jane Smith',
'[email protected]',
'SecurePassword123!',
true,
null // Have to pass null even though we want the default
);Real-World Examples
Response Macros
php
// ✅ Self-documenting API responses
return response()->success(
data: new UserResource($user),
message: 'User created successfully',
status: 201,
meta: ['timestamp' => now()]
);
// ❌ Less clear
return response()->success(
new UserResource($user),
'User created successfully',
201,
['timestamp' => now()]
);Validation Rules
php
// ✅ Clear validation rules
$request->validate([
'email' => [
'required',
'email',
'unique:users,email',
Rule::unique(table: 'users', column: 'email')->ignore(id: $userId),
],
'password' => [
'required',
Password::min(length: 12)
->mixedCase()
->numbers()
->symbols()
->uncompromised(threshold: 3),
],
]);Database Queries
php
// ✅ Clear query building
User::where(column: 'status', operator: '=', value: 'active')
->orderBy(column: 'created_at', direction: 'desc')
->take(value: 10)
->get();
// ✅ Cache operations
Cache::remember(
key: 'users_active',
ttl: 3600,
callback: fn () => User::where('is_active', true)->get()
);Model Creation
php
// ✅ Clear model attributes
User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make(value: $data['password']),
'email_verified_at' => now(),
]);
// ✅ Model methods
$user->update(attributes: $validatedData);
$user->fill(attributes: $validatedData);Service Calls
php
// ✅ Clear service method calls
$report = $reportService->generate(
startDate: now()->subDays(days: 30),
endDate: now(),
format: 'pdf',
includeCharts: true,
recipientEmail: $user->email
);
// ✅ Notification dispatch
$user->notify(
instance: new InvoicePaid(invoice: $invoice)
);Event Dispatching
php
// ✅ Clear event dispatching
event(new UserRegistered(
user: $user,
ipAddress: $request->ip(),
userAgent: $request->userAgent()
));
// ✅ Job dispatching
ProcessPodcast::dispatch(
podcast: $podcast,
queue: 'processing'
)->delay(delay: now()->addMinutes(minutes: 5));Mixing Positional and Named Arguments
You can mix positional and named arguments, but positional arguments must come first:
php
// ✅ Good: Positional first, then named
$result = someFunction($required1, $required2, optional: $value);
// ❌ Bad: Cannot use positional after named
$result = someFunction(optional: $value, $required1, $required2);Array Spreading with Named Arguments
php
<?php
$defaults = [
'theme' => 'dark',
'language' => 'en',
'notifications' => true,
];
// ✅ Spread operator with named arguments
$settings = configureSettings(
userId: $user->id,
...$defaults,
theme: 'light' // Override default
);Best Practices for Named Arguments
1. Use for Clarity
php
// ✅ Good: Clear boolean flags
$notification->send(
immediately: true,
retryOnFailure: false
);
// ❌ Bad: What do true/false mean here?
$notification->send(true, false);2. Skip Unnecessary Parameters
php
// ✅ Good: Skip middle optional parameters
createUser(
name: 'John',
email: '[email protected]',
role: 'admin'
// Skip optional $isActive, $phone, etc.
);3. Complex Function Calls
php
// ✅ Good: Named arguments for complex calls
$report = generateReport(
type: 'financial',
startDate: '2024-01-01',
endDate: '2024-12-31',
includeGraphs: true,
groupBy: 'month',
currency: 'USD',
format: 'pdf'
);4. Constructor Calls
php
// ✅ Good: Clear constructor arguments
$service = new PaymentService(
gateway: $stripeGateway,
logger: $logger,
cache: $cache,
retryAttempts: 3,
timeout: 30
);Common Pitfalls
❌ Don't Overuse
php
// ❌ Bad: Overkill for simple calls
$max = max(num1: 5, num2: 10);
// ✅ Good: Use positional for obvious cases
$max = max(5, 10);❌ Avoid Renaming Parameters
When you use named arguments, parameter names become part of your public API. Renaming them is a breaking change:
php
// Before
public function sendEmail(string $recipient, string $subject) {}
// After (BREAKING CHANGE if callers use named arguments)
public function sendEmail(string $to, string $title) {}
// Callers using named arguments will break:
sendEmail(recipient: '[email protected]', subject: 'Hello'); // ❌ Breaks!IDE Configuration
Most modern IDEs support named arguments with autocomplete:
VS Code:
- PHP Intelephense extension automatically supports named arguments
- Shows parameter names in autocomplete suggestions
PHPStorm:
- Built-in support for named arguments
- Refactoring tools understand named arguments
- Can convert between positional and named arguments
Migration Strategy
When updating existing code:
- Start with new code - Use named arguments in all new functions
- Update during refactoring - Convert to named arguments when touching old code
- Focus on clarity - Convert where it improves readability most
- Team consistency - Ensure team agreement on when to use them
Summary
Named arguments are a powerful PHP 8.0+ feature that makes your code more readable, maintainable, and less error-prone. Use them liberally, especially in Laravel applications where method calls often have many parameters.
php
// ✅ Adopt this pattern throughout your codebase
return response()->success(
data: new UserResource($user),
message: 'Operation successful',
status: 200
);
$user = User::create([
'name' => $data['name'],
'email' => strtolower(string: $data['email']),
'password' => Hash::make(value: $data['password']),
]);
Cache::remember(
key: "user_{$userId}",
ttl: 3600,
callback: fn () => $this->userRepository->find(id: $userId)
);📝 Documentation and Comments
DocBlocks
✅ Good Example
php
<?php
declare(strict_types=1);
namespace App\Services;
use App\Models\User;
use App\Models\Order;
/**
* Service for managing user orders and order processing.
*
* This service handles order creation, validation, processing,
* and all business logic related to orders.
*/
class OrderService
{
/**
* Create a new order for the specified user.
*
* @param User $user The user placing the order
* @param array $items Array of order items with quantities
* @param string|null $couponCode Optional discount coupon code
* @return Order The created order instance
*
* @throws \InvalidArgumentException When items array is empty
* @throws \App\Exceptions\InvalidCouponException When coupon is invalid
*/
public function createOrder(User $user, array $items, ?string $couponCode = null): Order
{
// Implementation
}
}Inline Comments
✅ Good Example
php
<?php
public function processPayment(Order $order): PaymentResult
{
// Validate order before processing payment
if (!$this->isOrderValid($order)) {
throw new InvalidOrderException('Order validation failed');
}
// Calculate total including taxes and fees
$totalAmount = $this->calculateTotal($order);
// Apply discount if coupon is present
if ($order->coupon_code) {
$totalAmount = $this->applyDiscount($totalAmount, $order->coupon_code);
}
// Process payment through gateway
try {
$result = $this->paymentGateway->charge($totalAmount, [
'order_id' => $order->id,
'customer_id' => $order->user_id,
]);
} catch (PaymentException $e) {
// Log payment failure for monitoring
Log::error('Payment processing failed', [
'order_id' => $order->id,
'error' => $e->getMessage(),
]);
throw $e;
}
return $result;
}❌ Bad Example
php
<?php
public function processPayment(Order $order): PaymentResult
{
// do validation
if (!$this->isOrderValid($order)) {
throw new InvalidOrderException('Order validation failed');
}
// get total
$totalAmount = $this->calculateTotal($order);
// check coupon
if ($order->coupon_code) {
$totalAmount = $this->applyDiscount($totalAmount, $order->coupon_code);
}
// charge
$result = $this->paymentGateway->charge($totalAmount, [
'order_id' => $order->id,
'customer_id' => $order->user_id,
]);
return $result;
}⚠️ Error Handling
✅ Good Example
php
<?php
declare(strict_types=1);
namespace App\Services;
use App\Exceptions\UserNotFoundException;
use App\Exceptions\InvalidUserDataException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class UserService
{
public function updateUser(int $id, array $data): User
{
try {
DB::beginTransaction();
$user = $this->userRepository->find($id);
if (!$user) {
throw new UserNotFoundException("User with ID {$id} not found");
}
if (!$this->validateUserData($data)) {
throw new InvalidUserDataException('Invalid user data provided');
}
$user = $this->userRepository->update($id, $data);
DB::commit();
Log::info('User updated successfully', [
'user_id' => $id,
'updated_fields' => array_keys($data),
]);
return $user;
} catch (UserNotFoundException | InvalidUserDataException $e) {
DB::rollBack();
throw $e;
} catch (\Exception $e) {
DB::rollBack();
Log::error('Unexpected error updating user', [
'user_id' => $id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw new \RuntimeException('Failed to update user', 0, $e);
}
}
}❌ Bad Example
php
<?php
class UserService
{
public function updateUser(int $id, array $data): User
{
// No error handling
$user = User::find($id);
$user->update($data);
return $user; // What if user is null?
}
}📋 Best Practices Summary
✅ Do's
- Use
declare(strict_types=1)at the top of every PHP file - Type-hint everything - parameters, return types, properties
- Follow PSR-12 for code formatting
- Use descriptive names that clearly indicate purpose
- Write self-documenting code with clear variable and method names
- Add comments to explain complex logic or business rules
- Handle errors properly with try-catch blocks
- Use dependency injection instead of static calls
- Keep methods short and focused on one task
- Use early returns to reduce nesting
❌ Don'ts
- Don't use short variable names like
$d,$x,$tmp - Don't skip type declarations for parameters or returns
- Don't use abbreviations unless universally understood
- Don't write god classes that do too much
- Don't ignore errors with empty catch blocks
- Don't mix naming conventions in the same codebase
- Don't use magic numbers - define constants instead
- Don't write comments that repeat what code does
- Don't skip strict types declaration
🔧 Code Quality Tools
Enforce these standards automatically with:
bash
# PHP CS Fixer - Auto-fix code style
./vendor/bin/php-cs-fixer fix
# Laravel Pint - Laravel-specific code style fixer
./vendor/bin/pint
# PHPStan - Static analysis
./vendor/bin/phpstan analyse
# Larastan - Laravel-aware static analysis
./vendor/bin/phpstan analyse --memory-limit=2GConfiguration Example
php
// .php-cs-fixer.php
<?php
$finder = PhpCsFixer\Finder::create()
->in(__DIR__)
->exclude(['bootstrap', 'storage', 'vendor'])
->name('*.php')
->notName('*.blade.php');
return (new PhpCsFixer\Config())
->setRules([
'@PSR12' => true,
'strict_param' => true,
'array_syntax' => ['syntax' => 'short'],
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'no_unused_imports' => true,
'not_operator_with_successor_space' => true,
'trailing_comma_in_multiline' => true,
'phpdoc_scalar' => true,
'unary_operator_spaces' => true,
'binary_operator_spaces' => true,
'blank_line_before_statement' => [
'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
],
'phpdoc_single_line_var_spacing' => true,
'phpdoc_var_without_name' => true,
])
->setFinder($finder);📝 Code Standards: These standards are enforced through automated tools in CI/CD. All pull requests must pass code style checks before merging.