As a variation of this, each action that can be performed on a user can be encapsulated by an object and an interface that wraps up the "is supported" check and the execution of the action. Couple that with a factory that implements a generic interface and you end up with a safer, object oriented way of doing this.
First the interface for the "action factory":
interface IUserActionFactory {
function getUser() : IGetUserAction;
function changePassword() : IChangePasswordAction;
function deleteUser() : IDeleteUserAction;
}
class LdapUserActionFactory implements IUserActionFactory {
function getUser() {
return new LdapGetUserAction();
}
function changePassword() {
return new LdapChangePasswordAction();
}
function deleteUser() {
return new LdapDeleteUserAction();
}
}
Nothing spectacular. These methods return objects supporting an interface. Without further adu, the interfaces:
interface IChangePasswordAction {
function isSupported();
function execute($username, $oldPassword, $newPassword);
}
interface IGetUserAction {
function isSupported();
function execute($username);
}
interface IDeleteUserAction {
function isSupported();
function execute($username);
}
Now all you need to do is use them (more on the implementation later):
class UserService {
private $actionFactory;
public function __construct(IUserActionFactory $actionFactory) {
$this->actionFactory = $actionFactory;
}
public function getUser($username) {
$getUserAction = $this->actionFactory->getUser();
if ($getUserAction->isSupported()) {
return $getUserAction->execute($username);
}
else {
return null;
}
}
public function changePassword($username, $oldPassword, $newPassword) {
$changePasswordAction = $this->actionFactory->changePassword();
if ($changePasswordAction->isSupported()) {
return $changePasswordAction->execute($username, $oldPassword, $newPassword);
}
else {
return false;
}
}
public function deleteUser($username) {
$deleteUserAction = $this->actionFactory->deleteUser();
if ($deleteUserAction->isSupported()) {
return $deleteUserAction->execute($username);
}
else {
return false;
}
}
}
The UserService just needs an IUserActionFactory object as a constructor argument. Then performing each action becomes a predictable check and call methodology.
In the classes that implement each action you can specialize in the particular user service, for instance LDAP:
// Generic exception for when an action cannot be executed
class ActionNotSupportedException extends Exception { }
class LdapGetUserAction implements IGetUserAction {
public function isSupported() {
return true;
}
public function execute($username) {
$user = // get from LDAP
return $user;
}
}
class LdapChangePasswordAction implements IChangePasswordAction {
function isSupported() {
return true;
}
function execute($username, $oldPassword, $newPassword) {
if (/* password gets changed */) {
return true;
}
else {
return false;
}
}
}
class LdapDeleteUserAction implements IDeleteUserAction {
public function isSupported() {
return false;
}
public function execute($username) {
throw new ActionNotSupportedException("Cannot delete users via LDAP");
}
}
I suppose you could name the "execute" method something specific, like "executeGetUser" or "getUser". With unique method names you could have 1 class that implements all of the interfaces.
Now if an exception gets thrown it should be because, say, LDAP goes down and the server is unreachable, or you try to change a password for a user that doesn't exist, instead of trying to interpret exceptions as "this thing is not supported."
And to use it:
$userService = new UserService(new LdapUserActionFactory());
if ($userService->deleteUser($POST['username'])) {
echo "User deleted";
}
else {
echo "You cannot delete this user";
}
Switching your user management is easy once you have the implementing classes:
$userService = new UserService(new ActiveDirectoryUserActionFactory());
if ($userService->deleteUser($POST['username'])) {
echo "User deleted";
}
else {
echo "You cannot delete this user";
}
If you call a method on the $userService
and it throws an ActionNotSupportedException then you know you have a defect to fix in UserFactory. It shouldn't be throwing that exception.