I'm very sorry for the cryptic title, but I honestly have no idea how to describe it in a short, title-style fashion.
First short version. Simple email confirmation mechanism. One method is sending email with confirmation link. After clicking the link, another controller invokes second method, which verifies token from the URL. Between both actions ConfirmationObject is being stored, with the token and possible other data. After successful confirmation "successHandler" is being used.
Simplified code:
interface SuccessHandlerInterface {
public function success(ConfirmationObjectInterface $object);
}
class EmailTester {
public function try(ConfirmationObjectInterface $object) {
// some code
}
public function confirm($token) {
$confirmationObject = $this->repository->findByToken($token);
$type = $confirmationObject->getType();
$successHandler = $this->handlersRegistry->getSuccessHandler($type);
$successHandler->success($confirmationObject);
}
}
Now we are going to use it this way:
// Firstly let's implement our own success handler.
class UserRegistrationSuccessHandler implements SuccessHandlerInterface {
public function success(ConfirmationObjectInterface $object) {
// Do some stuff on success.
}
}
// Then let's register this success handler to be available in our `handlersRegistry` object.
$handlersRegistry->addType('user_registration', new UserRegistrationSuccessHandler());
// Now we will extend ConfirmationObjectInterface
interface RegistrationConfirmationObjectInterface extends ConfirmationObjectInterface {
public function getSomeDataGivenOnRegistration();
}
// And at the end, let's try our email
$confirmationObject = new RegistrationConfirmationObject(); // Which implements above interface.
// $confirmationObject->getType() === 'user_registration'
$emailTester->try($confirmationObject);
// Now confirmation link with token is being sent to the given email. If user will click it, below method will be invoked.
$emailTester->confirm($token);
The problem now is that I would rather like to have RegistrationConfirmationObjectInterface
in the success handler available, rather than ConfirmationObjectInterface
.
I know I can do:
// Firstly let's implement our own success handler.
class SuccessHandler implements SuccessHandlerInterface {
public function success(ConfirmationObjectInterface $object) {
if ($object instanceof RegistrationConfirmationObjectInterface) {
// Do stuff
}
}
}
But it feels bad. This check is pointless as $object
will always be an instance of RegistrationConfirmationObjectInterface
. How is this design flawed, and how it could be improved?
It is unclear to me why the Confirmation Objects should implement two interfaces. From what I see here, RegistrationConfirmationObjectInterface
only has one method that returns some data structure, and ConfirmationObjectInterface
has no methods at all. Is strict type safety really necessary here, especially if you're certain that your custom SuccessHandler
will receive RegistrationConfirmationObjectInterface
at all times?
If ConfirmationObjectInterface
implementations contain no logic and are just data structures, replace them with associative arrays. Otherwise, I would suggest something like this:
interface ConfirmationObjectInterface
{
/**
* @return array
*/
public function getData();
}
class RegistrationConfirmationObject implements ConfirmationObjectInterface
{
public function getData()
{
return ['data specific to registration here'];
}
}
class SomethingElseConfirmationObject implements ConfirmationObjectInterface
{
public function getData()
{
return ['data specific to something else'];
}
}
Since custom handlers are specific to concrete types, they will know what data to expect from getData()
anyway.