The idea of the factory pattern is to decouple and encapsulate the logic required to create an instance of an object, from the object itself. This separates concerns.
You currently have a static method on your User class that is responsible for fetching the user information from the database. This couples your User object to your database. It's also very rigid.
- What if your users are stored in a different type of database?
- What if your users are stored in an XML file?
- What if your users are stored in a key/value store?
- What if you want to create a mock repository of users for writing tests?
The factory pattern allows you to abstract these concerns away, and provide alternate implementations of a factory depending on your needs (assuming you had a common interface for each implementation). Maybe the way you create a user changes, but you don't want to also change everywhere you've needed to create a user - just how the user is created. Isolating this code into a factory allows for this.
A factory might take an instance to your DAL as well, if you've abstracted your DAL. (I'll be using IDataAccessLayer as a placeholder for your preferred DAL)
class UserFactory {
private IDataAccessLayer $dataAccessLayer;
public function __constructor($dataAccessLayer) {
$this->dataAccessLayer = $dataAccessLayer;
}
public function createUser($userId) {
$userDataSet = $this->dataAccessLayer->someQuery($userId);
switch ($userDataSet->type) {
case UserType::Administrator:
return createAdministrator($userDataSet);
case UserType::Moderator:
return createModerator($userDataSet);
case UserType::Ordinary:
return createOrdinaryUser($userDataSet);
}
}
private function createAdministrator($userDataSet) { /* Create and return an administrator */ }
private function createModerator($userDataSet) { /* Create and return a moderator */ }
private function createOrdinaryUser($userDataSet) { /* Create and return an ordinary user */ }
}
You'd then use this like:
$factory = new UserFactory($dataAccessLayer);
$factory->createUser(1);
(Bear with me, I haven't coded PHP in some years, so some of my syntax may be off, but the idea is the same)
Now, personally, it looks like you need to get even more granular here. I'd create a UserRepository following the Repository pattern. This is because the factory shouldn't really be accessing the database in my opinion. It should just use the data from the database to create the user. The repository pattern should be responsible for getting the data from the database, and then feeding that data to the factory pattern to get an object.
class UserRepository {
private IDataAccessLayer $dataAccessLayer;
public function __constructor($dataAccessLayer) {
$this->dataAccessLayer = $dataAccessLayer;
}
public function getUserById($id) {
$userData = $this->dataAccessLayer->fetch(someQuery);
$factory = new UserFactory();
return $factory->createUser($userData);
}
}
Then you'd have a simpler factory:
class UserFactory {
public function createUser($userData) {
switch ($userData->type) {
case UserType::Administrator:
return createAdministrator($userData);
case UserType::Moderator:
return createModerator($userData);
case UserType::Ordinary:
return createOrdinaryUser($userData);
}
}
private function createAdministrator($userDataSet) { /* Create and return an administrator */ }
private function createModerator($userDataSet) { /* Create and return a moderator */ }
private function createOrdinaryUser($userDataSet) { /* Create and return an ordinary user */ }
}
And from your code, you'd have something like:
$userRepository = new UserRepository($dataAccessLayer); //This is probably done somewhere higher in your code.
$userRepository->getUserById(1);
You'll probably want to go ahead and create the following interfaces and implement these as well. This will allow you to enforce your contract, as well as set yourself up to be able to swap out implementations if you need.
public interface IUserRepository {
function getUserById($userId);
}
and
public interface IUserFactory {
function createUser($userData);
}
getUser()is a good name for a method of aRepositoryclass. The names of the methods of aFactoryclass usually start withcreateto better express what aFactorydoes: it creates new objects.