Development Standards
When to Use
Every development project using TDD or testing. These standards ensure code quality, maintainability, and team consistency.
Dependency Injection Over Static Calls
Why: Testability. Static calls and global state are hard to mock/stub in tests.
// BAD: Static calls and global state
class UserService {
public function createUser($email, $password) {
$user = new User();
$user->email = $email;
$user->password = Hash::make($password); // Static call - can't mock
DB::table('users')->insert($user); // Global DB - can't use test database
return $user;
}
}
// GOOD: Dependency injection
class UserService {
private $hasher;
private $userRepository;
public function __construct(Hasher $hasher, UserRepository $userRepository) {
$this->hasher = $hasher;
$this->userRepository = $userRepository;
}
public function createUser($email, $password) {
$user = new User();
$user->email = $email;
$user->password = $this->hasher->make($password); // Injected - can mock
$this->userRepository->save($user); // Injected - can use test repo
return $user;
}
}
// Test with mocks
public function testCreateUser() {
$hasher = $this->createMock(Hasher::class);
$hasher->method('make')->willReturn('hashed_password');
$userRepo = new InMemoryUserRepository(); // Fast test double
$service = new UserService($hasher, $userRepo);
$user = $service->createUser('alice@example.com', 'secret123');
$this->assertEquals('hashed_password', $user->password);
$this->assertEquals(1, $userRepo->count());
}
Render Arrays Over Raw HTML
Why: Testability and security. Rendering HTML from strings leads to XSS vulnerabilities and is hard to test.
// BAD: String concatenation (Drupal anti-pattern)
function render_user_profile($user) {
$output = '<div class="user-profile">';
$output .= '<h2>' . $user->name . '</h2>'; // XSS vulnerability
$output .= '<p>' . $user->bio . '</p>';
$output .= '</div>';
return $output;
}
// GOOD: Render array (Drupal best practice)
function render_user_profile($user) {
return [
'#theme' => 'user_profile',
'#user' => $user,
'#cache' => [
'tags' => ['user:' . $user->id()],
],
];
}
// Template file handles rendering (auto-escaped):
// user-profile.html.twig:
// <div class="user-profile">
// <h2>{{ user.name }}</h2>
// <p>{{ user.bio }}</p>
// </div>
// Test render array structure
public function testUserProfileRenderArray() {
$user = User::create(['name' => 'Alice', 'bio' => 'Developer']);
$render = render_user_profile($user);
$this->assertEquals('user_profile', $render['#theme']);
$this->assertEquals($user, $render['#user']);
$this->assertContains('user:' . $user->id(), $render['#cache']['tags']);
}
Proper API Usage
Don't reinvent the wheel: Use framework/language APIs correctly.
# BAD: Reimplementing standard library functions
def my_sort(items):
# 30 lines of bubble sort implementation
pass
def test_my_sort():
# Testing code that shouldn't exist
pass
# GOOD: Use built-in
def process_items(items):
return sorted(items) # Built-in, tested, optimized
def test_process_items():
# Test your logic, not Python's sort
items = [3, 1, 2]
assert process_items(items) == [1, 2, 3]
Single Responsibility Principle
Why: Testability. Classes/functions with one responsibility are easier to test.
// BAD: God class with multiple responsibilities
class UserManager {
validateEmail(email: string): boolean { }
hashPassword(password: string): string { }
sendWelcomeEmail(email: string): void { }
logRegistration(userId: number): void { }
updateMetrics(event: string): void { }
createUser(email: string, password: string): User {
// Uses all of the above
}
}
// Tests are complex because class does too much
// Mocking is difficult because all dependencies in one class
// GOOD: Separate concerns
class EmailValidator {
validate(email: string): boolean { }
}
class PasswordHasher {
hash(password: string): string { }
}
class UserRepository {
save(user: User): User { }
}
class RegistrationService {
constructor(
private validator: EmailValidator,
private hasher: PasswordHasher,
private repository: UserRepository
) {}
register(email: string, password: string): User {
if (!this.validator.validate(email)) {
throw new Error('Invalid email');
}
const user = new User(email, this.hasher.hash(password));
return this.repository.save(user);
}
}
// Tests are simple, focused, fast
test('EmailValidator validates email format', () => {
const validator = new EmailValidator();
expect(validator.validate('alice@example.com')).toBe(true);
expect(validator.validate('invalid')).toBe(false);
});
Coding Standards
Consistency matters more than specific style: Pick a standard, enforce it.
// Use linters and formatters
// .eslintrc.json
{
"extends": "airbnb",
"rules": {
"max-len": ["error", { "code": 100 }],
"no-console": "error"
}
}
// .prettierrc
{
"singleQuote": true,
"trailingComma": "es5"
}
// Run in CI
"scripts": {
"lint": "eslint src/",
"format": "prettier --check src/",
"test": "jest"
}
Testability Checklist
Code is testable when: - [ ] Dependencies are injected, not created internally - [ ] Functions are pure (same input = same output) when possible - [ ] Side effects are isolated and explicit - [ ] Classes have single responsibility - [ ] No hidden global state - [ ] No hard-coded file paths, URLs, dates, random values - [ ] Public API is small and focused
Code is NOT testable when:
- [ ] Uses static methods/singletons extensively
- [ ] Accesses database/filesystem directly (not through abstraction)
- [ ] Has many dependencies created with new inside methods
- [ ] Uses current date/time without abstraction
- [ ] Has conditional logic based on environment (DEV vs PROD)
- [ ] Depends on execution order or global state
Common Mistakes
- Writing code without thinking about tests - Results in untestable code; consider testability during design
- Refactoring to make tests pass instead of code testable - Hides design problems; untestable code is poorly designed code
- Using mocks to work around bad design - Mocks should be for external boundaries, not covering up coupling
- Not enforcing coding standards - Inconsistency wastes time in code review and makes code harder to maintain
- Skipping code review for "small changes" - Small changes can introduce big security/quality issues
See Also
- Previous: Performance Best Practices | Next: Code Reference Map
- Related: Test Doubles (proper use of mocks)
- Related: Refactoring with Confidence
- Reference: PHP-FIG Standards
- Reference: Google Style Guides