I have User
entity. I would like to have multiple "types" of this entity, with different managers and repositories. All User
entities of all types would share only UserInterface
. Now, I'm looking for a good method of organizing everything. First thing that came to my mind is to create something like this:
interface UserTypeManagerInterface
{
public function addUserType($name, RepositoryInterface $repository, ManagerInterface $manager);
public function hasType($name);
public function getRepository($type);
public function getManager($type);
}
Then in places where I would like to manage multiple types of User
at once, I would inject this, and in places where I would like to manage a specific type of user, I could inject only specific repository and manager objects, for its type.
Seems like a pretty clean approach, but at the same time, when I would like to create a test for a class using UserTypeManager
I would need to mock UserTypeManager
, then this mock would need to return other mocks (repository and manager).
This is doable of course, but it made me thinking if this can be avoided. The only other thing that I can think of, which would allow for avoidance of above complexity during testing, would be something like this:
interface UserTypeManagerInterface {
public function addUserType($name, RepositoryInterface $repository, ManagerInterface $manager);
}
/**
* My class managing multiple types of user.
*/
class ManageMultipleTypesOfUsers implements UserTypeManagerInterface {
// ...
}
So I would just add all repositories and managers to all classes implementing UserTypeManagerInterface
interface. So objects would use directly what was given to them.
This way testing would be much cleaner, because I would need to mock only the one manager and one repository to test class ManageMultipleTypesOfUsers
, but this feels sooo much like over-engineering. ;)
Is any middle-ground here even possible?
PHP Design patterns is an Object-Oriented Programming (OOP) concept that is now also used in Drupal 9 projects. With Drupal's adoption of modern PHP and OOP concepts since version 8, design patterns can be leveraged for cleaner and more robust programming.
Factory method is a creational design pattern which solves the problem of creating product objects without specifying their concrete classes. The Factory Method defines a method, which should be used for creating objects instead of using a direct constructor call ( new operator).
Facade is a structural design pattern that provides a simplified (but limited) interface to a complex system of classes, library or framework. While Facade decreases the overall complexity of the application, it also helps to move unwanted dependencies to one place.
This design pattern can be applied whenever a system needs to support many entities of same or similar type. The Manager object is designed to keep track of all the entities. In many cases, the Manager will also route messages to individual entities.
I can not give a definitive answer on this as it is a trade-off.
As far as I see User is a pure value object? That's a good start. That means it's trivial to manipulate without side-effects.
Now, consider these variables which shall influence your design:
delete(User $user)
- only Admins are allowed, other implementations just throw, basic permission checks) or use vastly different code?So, how do these variable influence our decision?
As I do not know what the requirements will be, I cannot give you an ideal solution. The (second) solution you proposed is fine for the "over-engineered" case.
A more moderate solution would be the functional approach:
function addAdminUser($name, RepositoryInterface $repository, ManagerInterface $manager) { /* ... */ }
function addNormalUser($name, RepositoryInterface $repository, ManagerInterface $manager) { /* ... */ }
// you even can pass this around as a callable ($cb = "addAdminUser"), or (pre-binding):
$cb = function($name) use ($repo, $mgr) { addAdminUser($name, $repo, $mgr); };
Or radically (if normal user is a subset of admin user addition) [as long as the function itself is side-effect free and its callers not going to be too hard to test]:
function addUser($name, $type, RepositoryInterface $repository, ManagerInterface $manager) {
/* ... common logic ... */
if ($type == IS_ADMIN_USER) { /* ... */ }
}
If that's too radical, also possible to inject a callback into addUser:
function addUser($name, $cb, RepositoryInterface $repository, ManagerInterface $manager) {
$user = new User;
/* ... common logic ... */
$cb($user, $repository, $manager);
}
Yes, the functional approach is maybe not as testable (i.e. you won't be able to mock the function if you call it directly (without passing as callable)), but it may bring you a huge gain in readability. Saving levels of trivial indirection may make your code easier to analyze. I'm emphasizing the may, as removing too much indirection may achieve the opposite effect. Find the middle ground as applicable to your application.
TL;DR: PHP provides you with a more functional approach, but less testable. Sometimes that's a fine trade-off, sometimes not. It depends on your concrete code where the middle ground lies.
P.s.: The only thing which really smells a bit to me in your second proposal, is having RepositoryInterface $repository, ManagerInterface $manager
in the addUserType method signature. Are you sure it's not part of the constructor?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With