Page tree

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Expand
title2. Coding guidelines

2.1 Dependency Injection (DI):

  • All class dependencies must be explicit and provided through Dependency Injection.
  • Avoid creating dependencies within the class or using global state (e.g., global variables or singleton patterns).
  • Use constructor injection or method injection for better testability and maintainability.


Code Block
languagephp
titleExample
collapsetrue
class OrderProcessor {
    private PaymentGatewayInterface $paymentGateway;

    public function __construct(PaymentGatewayInterface $paymentGateway) {
        $this->paymentGateway = $paymentGateway;
    }
}


2.2 New Code Placement:

  • All new code must be placed in the /src folder.
  • Code must adhere to PSR standards, including:
    • Proper use of namespaces to reflect the directory structure.
    • Avoidance of PHP 4-style class names (no underscores for class name simulation).


2.3 SOLID Principles:

  • All new code must follow the SOLID principles:
    • Single Responsibility Principle: A class should have one and only one reason to change.
    • Open/Closed Principle: Code should be open for extension but closed for modification.
    • Liskov Substitution Principle: Subtypes must be substitutable for their base types.
    • Interface Segregation Principle: Classes should not be forced to implement unnecessary interfaces.
    • Dependency Inversion Principle: Depend on abstractions, not on concrete implementations.


2.4 Unit Tests:

  • All new classes must have corresponding unit tests.
  • Tests should cover the main functionality and edge cases.
  • Follow the AAA structure for tests (Arrange, Act, Assert).


Code Block
languagephp
titleExample
collapsetrue
use PHPUnit\Framework\TestCase;

class PaymentServiceTest extends TestCase {
    public function testProcessPayment() {
        // Arrange
        $mockGateway = $this->createMock(PaymentGatewayInterface::class);
        $service = new PaymentService($mockGateway);

        // Act
        $result = $service->processPayment(100);

        // Assert
        $this->assertTrue($result);
    }
}


2.5 Encapsulation Over Inheritance:

  • Prefer encapsulation over extending classes.
  • Use inheritance only when a clear “is-a” relationship exists.

Problem with Inheritance:

Using inheritance might seem natural when trying to extend functionality, but it can lead to tightly coupled designs and rigid hierarchies.

Code Block
languagephp
titleExample
collapsetrue
class FileStorage {
    public function save(string $fileName, string $data): void {
        // Save the file to disk
        echo "Saving to disk: $fileName\n";
    }
}

class LoggingFileStorage extends FileStorage {
    public function save(string $fileName, string $data): void {
        // Log the action before saving
        echo "Logging: Saving $fileName\n";
        parent::save($fileName, $data);
    }
}

Issues:

  • The LoggingFileStorage class is tightly coupled to the FileStorage implementation.
  • If the base class changes, LoggingFileStorage might break or require updates.
  • Adding more variations (e.g., caching, encrypting) will create a complex and rigid inheritance hierarchy.


Better with Encapsulation:

Encapsulation allows us to compose functionality dynamically and avoid the pitfalls of deep inheritance.

Code Block
languagephp
titleExample
collapsetrue
interface StorageInterface {
    public function save(string $fileName, string $data): void;
}

class FileStorage implements StorageInterface {
    public function save(string $fileName, string $data): void {
        // Save the file to disk
        echo "Saving to disk: $fileName\n";
    }
}

class LoggingStorage implements StorageInterface {
    public function __construct(private readonly StorageInterface $storage) {
        $this->storage = $storage;
    }

    public function save(string $fileName, string $data): void {
        // Log the action before saving
        echo "Logging: Saving $fileName\n";
        $this->storage->save($fileName, $data);
    }
}

class CachingStorage implements StorageInterface {
    public function __construct(private readonly StorageInterface $storage) {
        $this->storage = $storage;
    }

    public function save(string $fileName, string $data): void {
        // Save to cache before saving to storage
        echo "Caching: $fileName\n";
        $this->storage->save($fileName, $data);
    }
}

Advantages of Encapsulation Over Inheritance:

  1. Flexibility: Functionality can be composed dynamically without changing the underlying classes.
  2. Reusability: Decorators can be reused across different storage implementations (e.g., database, cloud storage).
  3. Scalability: Adding new behavior (e.g., encryption) doesn’t require modifying existing classes or creating a rigid hierarchy.

This approach adheres to the Open/Closed Principle (code is open for extension but closed for modification) and results in more maintainable, testable, and decoupled code.


2.6 Interface Usage:

  • Always program to interfaces, not implementations.
  • Use interfaces to define contracts and ensure flexibility for future changes.


2.7 Small, Focused Methods:

  • Keep methods small and focused on a single task.
  • If a method grows beyond 15–20 lines, consider refactoring into smaller, reusable methods.


2.8 Avoid Hardcoding:

  • Avoid hardcoding values or logic directly into your classes.
  • Use configuration files, environment variables, or constants for flexibility.


Code Block
languagephp
titleExample
collapsetrue
class RetryHandler {
    private const MAX_RETRY_COUNT = 5;

    public function handle() {
        for ($i = 0; $i < self::MAX_RETRY_COUNT; $i++) {
            // Retry logic
        }
    }
}


2.9 Readable Code:

  • Prioritize readability over clever or overly complex code.
  • Use meaningful variable and method names, and write inline comments for non-obvious logic. Important here is not to write comments that describe what the code is doing because it is obvious from the code itself, but to write comments describing the logic behind, WHY it is doing this.


2.10 Avoid Side Effects:

  • Functions and methods should avoid unexpected side effects.
  • For example, avoid modifying global state or altering input parameters.
  • Functions that are designed to do some checks should not mutate or create something and vice versa




...