The Single Responsibility Principle states that a class should have only one purpose to serve which means we can't define a class that do multiple jobs and when it needs to be update or change there will be one only reason behind this.
If a class serves multiple jobs then it becomes more dificult to maintian and if we change one aspect of that class, it may effect or break other functionality.
Lets see the bellow code:
class UserManager {
private $dbConnection;
public function __construct($dbConnection) {
$this->dbConnection = $dbConnection;
}
// Handles user creation and saving user to the database
public function createUser($name, $email) {
// Business logic for creating a user
if (empty($name) || empty($email)) {
throw new Exception("Name and email are required");
}
// Saving user data to the database
$query = "INSERT INTO users (name, email) VALUES ('$name', '$email')";
if ($this->dbConnection->query($query) === TRUE) {
echo "New user created successfully";
} else {
echo "Error: " . $this->dbConnection->error;
}
}
// Fetching user data
public function getUser($id) {
$query = "SELECT * FROM users WHERE id = '$id'";
$result = $this->dbConnection->query($query);
return $result->fetch_assoc();
}
}
The code above doesn't comply with the Single Responsibility Principle (SRP) because it serves multiple purposes. The UserManager class is responsible for both handling user data logic and database connection, as well as fetching data from it.
In order to modify user data logic, we must also modify database-related code. This may have an effect on other code that is inheriting from this codebase. It could become more complicated than it should be.
Let's refactor this code with Single Responsibility Principle
// UserValidator: Responsible for validating user data
class UserValidator {
public function validate($name, $email) {
if (empty($name) || empty($email)) {
throw new Exception("Name and email are required");
}
}
}
// UserRepository: Responsible for interacting with the database
class UserRepository {
private $dbConnection;
public function __construct($dbConnection) {
$this->dbConnection = $dbConnection;
}
// Saves the user to the database
public function save($name, $email) {
$query = "INSERT INTO users (name, email) VALUES ('$name', '$email')";
if ($this->dbConnection->query($query) !== TRUE) {
throw new Exception("Error: " . $this->dbConnection->error);
}
}
// Retrieves user data from the database
public function getById($id) {
$query = "SELECT * FROM users WHERE id = '$id'";
$result = $this->dbConnection->query($query);
return $result->fetch_assoc();
}
}
// UserManager: Responsible for user-related business logic (no persistence here)
class UserManager {
private $validator;
private $repository;
public function __construct(UserValidator $validator, UserRepository $repository) {
$this->validator = $validator;
$this->repository = $repository;
}
// Handles user creation
public function createUser($name, $email) {
// Validate user data
$this->validator->validate($name, $email);
// Save user to the database
$this->repository->save($name, $email);
echo "New user created successfully";
}
// Fetching user data
public function getUser($id) {
return $this->repository->getById($id);
}
}
What makes the refactored code comply with SRP standards.
What Makes it SRP standards:
- UserValidator's responsibility is limited to validating user data (business logic).
- Database interactions (persistence logic) are the sole responsibility of UserRepository.
- The UserManager handles user-related business logic without having to deal with validation or persistence.
What is the benigits of having Single Responsibility:
- It is easier to maintain and extend when each class has only one responsibility.
- The UserValidator class will be the only one affected by any changes to the validation logic.
- The UserRepository class is the only thing that needs to be modified if the database interaction needs to be changed (e.g., switching to a different database or changing the schema).
Easy To Test Every Functionality:
Unit testing becomes easier because every class has a single responsibility. The validation logic, repository methods, and user management logic can be tested separately.
Flexibility & Maintainable:
The UserRepository or UserValidator can be swapped out with different implementations without any impact on the other parts of the code. As an example, you have the option to substitute the UserRepository with one that utilizes an API instead of a database.