cult3

Building a Password Reminder Domain Service

Oct 27, 2014

Table of contents:

  1. Is it a Domain Service?
  2. How do Password Reminders work?
  3. Speccing what we need to build
  4. The Reminder Code Value Object
  5. The Reminder Id Value Object
  6. The Reminder Entity
  7. The Reminder Repository Interface
  8. The Reminder Repository Implementation
  9. Reminder Fixtures
  10. The Repository Tests
  11. The Reminder Service
  12. Conclusion

Last week we looked at writing the registration component of Cribbb as a Domain Service. The registration process is going to be a very important part of Cribbb and so I decided to model it as a Domain Service.

Registering new users into a new application is fairly straight forward and fits nicely into the Identity namespace we’re currently working on.

Another important aspect of Identity is the functionality to reset a password. When developing an application it is very easy to forget about implementing the ability for your users to reset their passwords. But if you launch without it, your new users will soon let you know about it with support requests!

In this week’s tutorial we’re going to be looking at implementing the functionality to reset passwords as a Domain Service.

Is it a Domain Service?

In last week’s tutorial we analysed whether the registration functionality should be a Domain Service or not.

I decided that I would write the registration functionality as a Domain Service for the following 3 reasons:

  1. The User object would end up with too much responsibility if it knew about the database and the ability to hash passwords.
  2. The process involves the co-ordination of multiple services
  3. The registration is an important part of the application’s business logic

When it comes to providing password reminder functionality, I think you could argue that the three points above are equally as valid.

Firstly, the functionality does not fit on any existing Domain Object. The User object will require a resetPassword() method, but it should not have the responsibility to query the database or hash a new password. We need to tell the User object about the new password, not expect it to create it itself.

Secondly, resetting a user’s password will require multiple Domain Objects and Services. We will need a Reminder Entity, and the ability to generate a ReminderCode Value Object and assert whether it is valid or not. We need a way of encapsulating this logic or it will leak out into the application.

And thirdly, whilst you could argue that resetting a password should be an Application Service, I tend to prefer to keep this kind of functionality insulated in the Domain, and then allow the Application layer to act as thin layer of co-ordination on top. I’m still figuring out this structure though, so if you don’t feel like it is right for your application, feel free to implement it as an Application Service.

How do Password Reminders work?

Before I jump into the code, first I’ll briefly explain how the process for allowing a user to reset their password will work.

1. Requesting

When a user has forgotten their password, they will be directed to a form where they can submit their registered email address.

At this point we will check to ensure that the email is registered with the application.

If the email is valid we can create a new Reminder that will expire in 60 minutes.

We can then send the user an email with a link to reset their password.

2. Reseting

The user will receive an email with a URL back to Cribbb that contains a unique code.

When the user clicks on the link they will be taken to a page that will allow them to reset the password.

The unique code in the URL will be validated against the current valid Reminder codes to ensure it is valid.

If the code is valid, the user will be able to reset their password.

I’m sure you’ve probably reset your password hundreds of times using web applications, but if you’ve never built a password reminder service, I think it can useful to know what’s going on under the hood.

Speccing what we need to build

So now that we have the theory down, we can start to think about what code we need to write to actually make the functionality work.

Domain Objects

The Domain is the most important part of Domain Driven Design and so naturally the bulk of the logic should be encapsulated in Domain Objects. If the bulk of the logic was captured in Services or Infrastructure we would know that something had gone wrong.

  1. When a user requests to reset their password, we need to create a Reminder. A Reminder is stored in the database and has a lifecycle, so it should be an Entity. The Reminder object should be responsible for determining if it is valid or not.
  2. Each Reminder will need a ReminderCode. The ReminderCode can be a Value Object and so we can model it separately.
  3. We will need a Repository to add, find and delete reminders
  4. We will also need Domain Events so we can send the email to the user on the request and a confirmation email when the password was successfully reset. Using Domain Events means we don’t have to couple the sending of the email to the Reminder Service.
  5. And finally we need to add a resetPassword() method to the existing User class.

Domain Services

  1. We will need a ReminderService that will co-ordinate the various Domain Objects and Services and provide an API to the Application layer of the application

Infrastructure

  1. We will need a Doctrine Repository implementation for the ReminderRepository.

The Reminder Code Value Object

Value Objects are always the easiest bit of developing functionality like this so we’ll start by creating the ReminderCode class:

<?php namespace Cribbb\Domain\Model\Identity;

use RuntimeException;
use Cribbb\Domain\ValueObject;

class ReminderCode implements ValueObject
{
    /**
     * @var string
     */
    private $value;

    /**
     * Create a new ReminderCode
     *
     * @param string $value
     * @return void
     */
    private function __construct($value)
    {
        $this->value = $value;
    }

    /**
     * Generate a new ReminderCode
     *
     * @return ReminderCode
     */
    public static function generate($length = 40)
    {
        $bytes = openssl_random_pseudo_bytes($length * 2);

        if (!$bytes) {
            throw new RuntimeException("Failed to generate token");
        }

        return new static(
            substr(
                str_replace(["+", "=", "/"], "", base64_encode($bytes)),
                0,
                $length
            )
        );
    }

    /**
     * Create a new instance from a native form
     *
     * @param mixed $native
     * @return ValueObject
     */
    public static function fromNative($native)
    {
        return new ReminderCode($native);
    }

    /**
     * Determine equality with another Value Object
     *
     * @param ValueObject $object
     * @return bool
     */
    public function equals(ValueObject $object)
    {
        return $this == $object;
    }

    /**
     * Return the object as a string
     *
     * @return string
     */
    public function toString()
    {
        return $this->value;
    }

    /**
     * Return the object as a string
     *
     * @return string
     */
    public function __toString()
    {
        return $this->value;
    }
}

The ReminderCode requires a single generate() method that can be called statically. We can also make the __construct() method private because the code should be generated internally.

This class should also implement the ValueObject interface and we can provide a fromNative() method so we can rehydrate data from the database.

The tests for this class are basically the same as the other Value Object tests:

<?php namespace Cribbb\Tests\Domain\Model\Identity;

use Cribbb\Domain\Model\Identity\ReminderCode;

class ReminderCodeTest extends \PHPUnit_Framework_TestCase
{
    /** @test */
    public function should_generate_new_code()
    {
        $code = ReminderCode::generate();

        $this->assertInstanceOf(
            "Cribbb\Domain\Model\Identity\ReminderCode",
            $code
        );
    }

    /** @test */
    public function should_throw_exception_if_code_generation_fails()
    {
        $this->setExpectedException("RuntimeException");

        $code = ReminderCode::generate(null);
    }

    /** @test */
    public function should_create_a_code_from_a_string()
    {
        $code = ReminderCode::fromNative(
            "D1zcA5ncaEHzmjvCGjJIt3Kd8sGxTTtE7DkathqB"
        );

        $this->assertInstanceOf(
            "Cribbb\Domain\Model\Identity\ReminderCode",
            $code
        );
    }

    /** @test */
    public function should_test_equality()
    {
        $one = ReminderCode::fromNative(
            "D1zcA5ncaEHzmjvCGjJIt3Kd8sGxTTtE7DkathqB"
        );
        $two = ReminderCode::fromNative(
            "D1zcA5ncaEHzmjvCGjJIt3Kd8sGxTTtE7DkathqB"
        );
        $three = ReminderCode::fromNative(
            "sdffpweofpojwepfnpowefopwfpowejfpopopfep"
        );

        $this->assertTrue($one->equals($two));
        $this->assertFalse($one->equals($three));
    }

    /** @test */
    public function should_return_as_string()
    {
        $code = ReminderCode::fromNative(
            "D1zcA5ncaEHzmjvCGjJIt3Kd8sGxTTtE7DkathqB"
        );

        $this->assertEquals(
            "D1zcA5ncaEHzmjvCGjJIt3Kd8sGxTTtE7DkathqB",
            $code->toString()
        );
        $this->assertEquals(
            "D1zcA5ncaEHzmjvCGjJIt3Kd8sGxTTtE7DkathqB",
            (string) $code
        );
    }
}

These tests are asserting that we can create a new ReminderCode instance and the equals() method can correctly identify equality.

The Reminder Id Value Object

The Reminder class will be an Entity and so it will require a unique Id. In this project I’m using UUIDs as unique identifiers instead of auto-incrementing database ids:

<?php namespace Cribbb\Domain\Model\Identity;

use Rhumsaa\Uuid\Uuid;
use Cribbb\Domain\Identifier;
use Cribbb\Domain\UuidIdentifier;

class ReminderId extends UuidIdentifier implements Identifier
{
    /**
     * @var Uuid
     */
    protected $value;

    /**
     * Create a new ReminderId
     *
     * @return void
     */
    public function __construct(Uuid $value)
    {
        $this->value = $value;
    }
}

This class is basically exactly the same as the UserId class from a couple of weeks ago.

The tests are also essentially the same as the UserId tests as well as the ReminderCode tests:

<?php namespace Cribbb\Tests\Domain\Model\Identity;

use Rhumsaa\Uuid\Uuid;
use Cribbb\Domain\Model\Identity\ReminderId;

class ReminderIdTest extends \PHPUnit_Framework_TestCase
{
    /** @test */
    public function should_require_instance_of_uuid()
    {
        $this->setExpectedException("Exception");

        $id = new ReminderId();
    }

    /** @test */
    public function should_create_new_reminder_id()
    {
        $id = new ReminderId(Uuid::uuid4());

        $this->assertInstanceOf("Cribbb\Domain\Model\Identity\ReminderId", $id);
    }

    /** @test */
    public function should_generate_new_reminder_id()
    {
        $id = ReminderId::generate();

        $this->assertInstanceOf("Cribbb\Domain\Model\Identity\ReminderId", $id);
    }

    /** @test */
    public function should_create_reminder_id_from_string()
    {
        $id = ReminderId::fromString("d16f9fe7-e947-460e-99f6-2d64d65f46bc");

        $this->assertInstanceOf("Cribbb\Domain\Model\Identity\ReminderId", $id);
    }

    /** @test */
    public function should_test_equality()
    {
        $one = ReminderId::fromString("d16f9fe7-e947-460e-99f6-2d64d65f46bc");
        $two = ReminderId::fromString("d16f9fe7-e947-460e-99f6-2d64d65f46bc");
        $three = ReminderId::generate();

        $this->assertTrue($one->equals($two));
        $this->assertFalse($one->equals($three));
    }

    /** @test */
    public function should_return_reminder_id_as_string()
    {
        $id = ReminderId::fromString("d16f9fe7-e947-460e-99f6-2d64d65f46bc");

        $this->assertEquals(
            "d16f9fe7-e947-460e-99f6-2d64d65f46bc",
            $id->toString()
        );
        $this->assertEquals(
            "d16f9fe7-e947-460e-99f6-2d64d65f46bc",
            (string) $id
        );
    }
}

The Reminder Entity

The Reminder Entity is fairly similar to the User Entity that we created a couple of weeks ago.

However I’ll walk through it step-by-step because it’s quite a big class.

Firstly, the Entity should implement the AggregateRoot interface and it should use the RecordsEvents trait (I renamed HasEvents to RecordsEvents):

<?php namespace Cribbb\Domain\Model\Identity;

use Carbon\Carbon;
use Cribbb\Domain\RecordsEvents;
use Doctrine\ORM\Mapping as ORM;
use Cribbb\Domain\AggregateRoot;
use Cribbb\Domain\Model\Identity\Events\ReminderWasCreated;

/**
 * @ORM\Entity
 * @ORM\Table(name="reminders")
 */
class Reminder implements AggregateRoot
{
    use RecordsEvents;
}

Note: I’m using Doctrine annotations because this is an Entity. If you are unfamiliar with Doctrine annotations, take a look at this tutorial.

Next we can define the class properties:

/**
 * @ORM\Id
 * @ORM\Column(type="string")
 */
private $id;

/**
 * @ORM\Column(type="string")
 */
private $email;

/**
 * @ORM\Column(type="string")
 */
private $code;

/**
 * @ORM\Column(type="string")
 */
private $created_at;

We don’t need to associate a Reminder code to a user because that would be an unnecessary association. Instead we can simply record the email address.

Next we can implement the __construct() method:

/**
 * Create a new Reminder
 *
 * @param ReminderId $reminderId
 * @param Email $email
 * @param ReminderCode $code
 * @return void
 */
public function __construct(ReminderId $reminderId, Email $email, ReminderCode $code)
{
    $this->setId($reminderId);
    $this->setEmail($email);
    $this->setCode($code);
    $this->setCreatedAt(Carbon::now());

    $this->record(new ReminderWasCreated);
}

Notice how I’m passing in the ReminderId, Email and ReminderCode and then I’m using the class’ internal set() methods.

I’m also recording a ReminderWasCreated Domain Event that we can use to send the email to the user.

Next we can implement the getter and setter methods:

/**
 * Get the Reminder id
 *
 * @return ReminderId
 */
public function id()
{
    return ReminderId::fromString($this->id);
}

/**
 * Set the Reminder id
 *
 * @param ReminderId $id
 * @return void
 */
private function setId(ReminderId $id)
{
    $this->id = $id->toString();
}

/**
 * Get the Reminder email
 *
 * @return Email
 */
public function email()
{
    return Email::fromNative($this->email);
}

/**
 * Set the Reminder email
 *
 * @param Email $email
 * @return void
 */
private function setEmail(Email $email)
{
    $this->email = $email->toString();
}

/**
 * Get the Reminder code
 *
 * @return ReminderCode
 */
public function code()
{
    return ReminderCode::fromNative($this->code);
}

/**
 * Set the Reminder Code
 *
 * @param ReminderCode $code
 * @return void
 */
private function setCode(ReminderCode $code)
{
    $this->code = $code->toString();
}

/**
 * Get the Created At timestamp
 *
 * @return Carbon
 */
public function createdAt()
{
    return Carbon::createFromFormat('Y-m-d H:i:s', $this->created_at);
}

/**
 * Set the Created At timestamp
 *
 * @param Carbon $timestamp
 * @return void
 */
private function setCreatedAt(Carbon $timestamp)
{
    $this->created_at = $timestamp->toDateTimeString();
}

I prefer to not prefix my getters with get and to have all setters as private unless they are explicitly required by the Domain. This is because I find $reminder->code() cleaner to write, and I want to control how data is set on an Entity.

You will also notice that I’m using the Carbon package for my timestamps. A lot of people will tell you not to rely on third-party libraries as part of your Domain code. For the most part I would agree with that statement, however I don’t want to have to mess with date and time logic, so I’m fine with using a fantastic third-party library like Carbon in this situation.

Finally I can implement the isValid() method:

/**
 * Check to see if the Reminder is valid
 *
 * @return bool
 */
public function isValid()
{
    return $this->createdAt()->addHour()->isFuture();
}

This method ensures that the logic behind whether a reminder is valid or not is encapsulated inside the Reminder Entity.

The Reminder Repository Interface

The next thing we need to do is to define the ReminderRepository interface. As we saw a couple of weeks ago, we should define Repository interfaces as part of the Domain of our application, and leave the implementation up to the Infrastructure layer of the application.

The first method we will need will be to return a new instance of ReminderId:

<?php namespace Cribbb\Domain\Model\Identity;

interface ReminderRepository
{
    /**
     * Return the next identity
     *
     * @return ReminderId
     */
    public function nextIdentity();
}

As I mentioned a couple of weeks ago, the repository should be conceptionally responsible for managing the unique id’s of the collection of Entities.

Next we need an add() method:

/**
 * Add a new Reminder
 *
 * @param Reminder $reminder
 * @return void
 */
public function add(Reminder $reminder);

As with the UserRepository, this method should accept a whole instance of Reminder. The add() method’s responsibility is simply to add the entity to the collection.

Next we will need a method to search for a reminder by an Email and a ReminderCode:

/**
 * Find a Reminder by Email and ReminderCode
 *
 * @param Email $email
 * @param ReminderCode $code
 * @return Reminder
 */
public function findReminderByEmailAndCode(Email $email, ReminderCode $code);

This will be used when the user clicks on their email link. We need to ensure that the reminder code exists and that it is associated with the correct email address.

And finally we have three methods to delete existing reminders:

/**
 * Delete a Reminder by it's ReminderCode
 *
 * @param RemindeCode $code
 * @return void
 */
public function deleteReminderByCode(ReminderCode $code);

/**
 * Delete existing Reminders for Email
 *
 * @param Email $email
 * @return void
 */
public function deleteExistingRemindersForEmail(Email $email);

/**
 * Delete all expired Reminders
 *
 * @return void
 */
public function deleteExpiredReminders();

The functionality of these methods should be fairly self explanatory from the method names, but I’ll explain each one as we implement it in the Doctrine Repository and the ReminderService.

The Reminder Repository Implementation

Next we need to create the Doctrine implementation of the Reminder Repository. This will be a new file called ReminderDoctrineORMRepository under the Repositories namespace in the Infrastructure directory:

<?php namespace Cribbb\Infrastructure\Repositories;

use Carbon\Carbon;
use Doctrine\ORM\EntityManager;
use Cribbb\Domain\Model\Identity\Email;
use Cribbb\Domain\Model\Identity\Reminder;
use Cribbb\Domain\Model\Identity\ReminderId;
use Cribbb\Domain\Model\Identity\ReminderCode;
use Cribbb\Domain\Model\Identity\ReminderRepository;

class ReminderDoctrineORMRepository implements ReminderRepository
{
}

As with the Repository implementation from last week, here I’m using a plain PHP object that does not inherit from Doctrine’s EntityRepository class.

The first thing I will do will be to inject the EntityManager through the __construct() method:

/**
 * @var EntityManager
 */
private $em;

/**
 * Create a new ReminderDoctrineORMRepository
 *
 * @param EntityManager $em
 * @return void
 */
public function __construct(EntityManager $em)
{
    $this->em = $em;
}

The EntityManager is the real power behind Doctrine, and so we need an instance of it to talk to the database.

Next we can define the nextIdentity() method:

/**
 * Return the next identity
 *
 * @return ReminderId
 */
public function nextIdentity()
{
    return ReminderId::generate();
}

In this method we need to return a new instance of ReminderId and so we can use the static generate() method to do just that.

Next we can define the method to find a reminder by an email and reminder code:

/**
 * Find a Reminder by Email and ReminderCode
 *
 * @param Email $email
 * @param ReminderCode $code
 * @return Reminder
 */
public function findReminderByEmailAndCode(Email $email, ReminderCode $code)
{
    $query = $this->em->createQuery('
        SELECT r FROM Cribbb\Domain\Model\Identity\Reminder r
        WHERE r.code = :code
        AND r.email = :email
    ');

    $query->setParameters([
        'code' => $code->toString(),
        'email' => $email->toString()
    ]);

    return $query->getOneOrNullResult();
}

In this method I’m using Doctrine’s DQL. If you are unfamiliar with DQL you should read my previous tutorial Using Doctrine Query Language.

If you are familiar with SQL, the code above should look fairly familiar.

The one thing to notice is I’m using the getOneOrNullResult() method. This means if nothing is found by the query, a null response is returned.

Next we can define the add() method:

/**
 * Add a new Reminder
 *
 * @param Reminder $reminder
 * @return void
 */
public function add(Reminder $reminder)
{
    $this->em->persist($reminder);
    $this->em->flush();
}

If you read last week’s posts, the code above should look familiar. Here we are passing a whole Reminder instance into the Repository for it to be persisted.

Next we can define the first of the three delete methods:

/**
 * Delete a Reminder by it's ReminderCode
 *
 * @param RemindeCode $code
 * @return void
 */
public function deleteReminderByCode(ReminderCode $code)
{
    $query = $this->em->createQuery(
        'DELETE Cribbb\Domain\Model\Identity\Reminder r WHERE r.code = :code');

    $query->setParameters(['code' => $code->toString()]);

    $query->execute();
}

In this method we have created a way to delete a reminder by it’s code. This will be useful for cleaning up after a reminder has successfully been used to reset a password.

Next we can define a method to clean up multiple requests:

/**
 * Delete existing Reminders for Email
 *
 * @param Email $email
 * @return void
 */
public function deleteExistingRemindersForEmail(Email $email)
{
    $query = $this->em->createQuery(
        'DELETE Cribbb\Domain\Model\Identity\Reminder r WHERE r.email = :email');

    $query->setParameters(['email' => $email->toString()]);

    $query->execute();
}

If the user doesn’t receive their password reset email straight away, they might request another email. This method will clean up an previously requested reminders from the database so we don’t have multiple reminder codes hanging around.

And finally a method for deleting expired reminders:

/**
 * Delete all expired Reminders
 *
 * @return void
 */
public function deleteExpiredReminders()
{
    $query = $this->em->createQuery(
        'DELETE Cribbb\Domain\Model\Identity\Reminder r WHERE r.created_at < :timestamp');

    $query->setParameters(['timestamp' => Carbon::now()]);

    $query->execute();
}

Once a reminder has expired there is no point in keeping it in the database. This method will simply delete any reminders that have expired. This could be run as an action in the Reminder service or perhaps an automated cron job.

Reminder Fixtures

If you read last week’s post you will remember that we introduced the concept of Doctrine fixtures. Fixtures allow you to seed the database with data so you can run your database tests with existing data. This saves you from having to write code in each test to set up the database.

Create a new class under the Fixtures namespace and copy the following class:

<?php namespace Cribbb\Tests\Infrastructure\Repositories\Fixtures;

use Carbon\Carbon;
use Cribbb\Domain\Model\Identity\Email;
use Cribbb\Domain\Model\Identity\Reminder;
use Cribbb\Domain\Model\Identity\ReminderId;
use Cribbb\Domain\Model\Identity\ReminderCode;
use Doctrine\Common\Persistence\ObjectManager;
use Cribbb\Domain\Model\Identity\HashedPassword;
use Doctrine\Common\DataFixtures\FixtureInterface;

class ReminderFixtures implements FixtureInterface
{
    /**
     * Load the User fixtures
     *
     * @param ObjectManager $manager
     * @return void
     */
    public function load(ObjectManager $manager)
    {
        // Create valid Reminder
        $id = ReminderId::generate();
        $email = new Email("first@domain.com");
        $code = ReminderCode::fromNative("code+99");
        $reminder = new Reminder($id, $email, $code);
        $manager->persist($reminder);

        // Create expired Reminder
        Carbon::setTestNow(Carbon::create(2014, 10, 11, 10, 23, 34));
        $id = ReminderId::generate();
        $email = new Email("first@domain.com");
        $code = ReminderCode::fromNative("code+1");
        $reminder = new Reminder($id, $email, $code);
        Carbon::setTestNow();

        $manager->persist($reminder);
        $manager->flush();
    }
}

In this fixture class I’m creating two existing reminders, one that is valid and one that has expired. This will allow us to test each of the Repository methods by querying for actual data.

When it comes to writing fixtures, I prefer to write the absolute minimum. Fixtures can end up getting out of control if you aren’t strict with them, and you don’t want to end up with false positives in your tests because you don’t fully understand what is going on in your database set up.

The Repository Tests

Next we can write the Repository tests. Here is the initial test file set up:

<?php namespace Cribbb\Tests\Infrastructure\Repositories;

use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Artisan;
use Cribbb\Domain\Model\Identity\Email;
use Doctrine\Common\DataFixtures\Loader;
use Cribbb\Domain\Model\Identity\Reminder;
use Cribbb\Domain\Model\Identity\ReminderId;
use Cribbb\Domain\Model\Identity\ReminderCode;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Cribbb\Infrastructure\Repositories\ReminderDoctrineORMRepository;
use Cribbb\Tests\Infrastructure\Repositories\Fixtures\ReminderFixtures;

class ReminderDoctrineORMRepositoryTest extends \TestCase
{
    /** @var ReminderDoctrineORMRepository */
    private $repository;

    /** @var EntityManager */
    private $em;

    /** @var ORMExecutor */
    private $executor;

    /** @var Loader */
    private $loader;

    public function setUp()
    {
        parent::setUp();

        Artisan::call("doctrine:schema:create");

        $this->em = App::make("Doctrine\ORM\EntityManagerInterface");
        $this->repository = new ReminderDoctrineORMRepository($this->em);

        $this->executor = new ORMExecutor($this->em, new ORMPurger());
        $this->loader = new Loader();
        $this->loader->addFixture(new ReminderFixtures());
    }
}

As with the Repository tests from last week, here I’m using a setUp() method to get everything in place for the tests. This includes migrating the database and loading the fixtures.

The first test should simply assert that an instance of ReminderId is returned from the nextIdentity() method:

/** @test */
public function should_return_next_identity()
{
    $this->assertInstanceOf(
        'Cribbb\Domain\Model\Identity\ReminderId', $this->repository->nextIdentity());
}

The next test should find a reminder by it’s email and reminder code:

/** @test */
public function should_find_reminder_by_email_and_code()
{
    $this->executor->execute($this->loader->getFixtures());

    $code = ReminderCode::fromNative('code+99');
    $email = new Email('first@domain.com');
    $reminder = $this->repository->findReminderByEmailAndCode($email, $code);

    $this->assertInstanceOf('Cribbb\Domain\Model\Identity\Reminder', $reminder);
    $this->assertEquals($code, $reminder->code());
    $this->assertEquals($email, $reminder->email());
}

First I run the fixtures so I know the database will be populated with the reminder I’m looking for.

Next I set up the ReminderCode and Email instances and then pass them to the Repository.

Finally I can assert that the returned $reminder is the one I was looking for.

The next test should assert that I can successfully add a reminder to the database:

/** @test */
public function should_add_new_reminder()
{
    $id = ReminderId::generate();
    $code = ReminderCode::fromNative('new');
    $email = new Email('new@domain.com');
    $reminder = new Reminder($id, $email, $code);

    $this->repository->add($reminder);

    $this->em->clear();

    $reminder = $this->repository->findReminderByEmailAndCode($email, $code);

    $this->assertInstanceOf('Cribbb\Domain\Model\Identity\Reminder', $reminder);
    $this->assertEquals($code, $reminder->code());
    $this->assertEquals($email, $reminder->email());
}

In this test I create a new Reminder instance and then pass it to the Repository.

Next I clear the EntityManager and then search for the Reminder by it’s email and code.

Finally I can assert that the reminder was found and the correct instance was returned.

In the next test I want to ensure that I can delete a reminder by it’s code:

/** @test */
public function should_delete_reminder_by_code()
{
    $this->executor->execute($this->loader->getFixtures());

    $code = ReminderCode::fromNative('code+99');
    $email = new Email('new@domain.com');

    $this->repository->deleteReminderByCode($code);

    $reminder = $this->repository->findReminderByEmailAndCode($email, $code);

    $this->assertEquals(null, $reminder);
}

First we run the fixtures to make sure the database is seeded with reminders.

Next we can create the ReminderCode and Email Value Objects we need to pass to the deleteReminderByCode().

Next we call the method and then attempt to find the reminder we just deleted.

Finally we can assert that the returned value from the Repository is null.

The next test is fairly similar to the previous test, but this time we are deleting by just the email, rather than the code:

/** @test */
public function should_delete_existing_reminders_by_email()
{
    $this->executor->execute($this->loader->getFixtures());

    $code = ReminderCode::fromNative('code+99');
    $email = new Email('new@domain.com');

    $this->repository->deleteExistingRemindersForEmail($email);

    $reminder = $this->repository->findReminderByEmailAndCode($email, $code);

    $this->assertEquals(null, $reminder);
}

And finally we can test that the Repository will delete the expired reminders whenever the deleteExpiredReminders() is called:

/** @test */
public function should_delete_expired_reminders()
{
    $this->executor->execute($this->loader->getFixtures());

    $code = ReminderCode::fromNative('code+1');
    $email = new Email('new@domain.com');

    $this->repository->deleteExpiredReminders();

    $reminder = $this->repository->findReminderByEmailAndCode($email, $code);

    $this->assertEquals(null, $reminder);
}

This should delete the expired fixture that we created earlier.

The Reminder Service

Now that we have the Entities, Value Objects and Repositories in places, we can look at using these individual components to build the ReminderService.

Here is the basic class definition:

<?php namespace Cribbb\Domain\Services\Identity;

use Cribbb\Domain\Model\Identity\Email;
use Cribbb\Domain\Model\Identity\Reminder;
use Cribbb\Domain\Model\Identity\Password;
use Cribbb\Domain\Model\Identity\ReminderCode;
use Cribbb\Domain\Model\InvalidValueException;
use Cribbb\Domain\Model\ValueNotFoundException;
use Cribbb\Domain\Model\Identity\UserRepository;
use Cribbb\Domain\Model\Identity\ReminderRepository;

class ReminderService
{
}

We can also create the test file. As with the RegisterUserService from last week, there is no need to hit the database during these tests. However, we will need to create some in-memory fixtures so we don’t have to repeat the same set up code for each test.

Here is what the test class set up looks like:

<?php namespace Cribbb\Tests\Domain\Services;

use Mockery as m;
use Carbon\Carbon;
use Cribbb\Domain\Model\Identity\User;
use Cribbb\Domain\Model\Identity\Email;
use Cribbb\Domain\Model\Identity\UserId;
use Cribbb\Domain\Model\Identity\Username;
use Cribbb\Domain\Model\Identity\Reminder;
use Cribbb\Domain\Model\Identity\ReminderId;
use Cribbb\Domain\Model\Identity\ReminderCode;
use Cribbb\Domain\Model\Identity\HashedPassword;
use Cribbb\Domain\Services\Identity\ReminderService;

class ReminderServiceTest extends \PHPUnit_Framework_TestCase
{
    /** @var ReminderRepository */
    private $reminders;

    /** @var UserRepository */
    private $users;

    /** @var ReminderService */
    private $service;

    /** @var User */
    private $user;

    /** @var array */
    private $fixture;

    public function setUp()
    {
        $id = UserId::generate();
        $email = new Email("name@domain.com");
        $username = new Username("username");
        $password = new HashedPassword("qwerty123");
        $this->user = User::register($id, $email, $username, $password);

        $this->fixture = [
            "id" => ReminderId::generate(),
            "code" => ReminderCode::generate(),
            "email" => new Email("name@domain.com"),
        ];

        $this->users = m::mock("Cribbb\Domain\Model\Identity\UserRepository");
        $this->reminders = m::mock(
            "Cribbb\Domain\Model\Identity\ReminderRepository"
        );
        $this->hasher = m::mock(
            "Cribbb\Domain\Services\Identity\HashingService"
        );

        $this->service = new ReminderService(
            $this->reminders,
            $this->users,
            $this->hasher
        );
    }
}

The request method

The first thing to do is define the __construct() method and inject the class dependencies:

/**
 * @var ReminderRepository
 */
private $reminders;

/**
 * @var UserRepository
 */
private $users;

/**
 * @var HashingService
 */
private $hasher;

/**
 * Create a new ReminderService
 *
 * @param ReminderRepository $reminders
 * @param UserRepository $users
 * @return void
 */
public function __construct(ReminderRepository $reminders, UserRepository $users, HashingService $hasher)
{
    $this->reminders = $reminders;
    $this->users = $users;
    $this->hasher = $hasher;
}

We’re going to need the ReminderRepository we just wrote as well as the UserRepository so we can search for existing users and the HashingService so we can hash the user’s new password.

Once again notice how I’m relying on the interface for each of these services and not the concrete implementation.

The first method we need to write is for requesting a reminder email:

/**
 * Request a password reminder Token
 *
 * @param string $email
 * @return Reminder
 */
public function request($email)
{
    $email = new Email($email);

    $this->findUserByEmail($email);

    $this->reminders->deleteExistingRemindersForEmail($email);

    $id = $this->reminders->nextIdentity();

    $reminder = new Reminder($id, $email, ReminderCode::generate());

    $this->reminders->add($reminder);

    return $reminder;
}

When the request comes in, the $email will be a string. The first thing to do will be to turn the $email string into a Email value object. This will ensure that the email passes our internal requirements for a valid email address.

Next we need to check to ensure that the email address is an actual registered email address in our application. This is something we will need to do in multiple methods of this service class and so we can abstract it to a private method:

/**
 * Attempt to find a user by their email address
 *
 * @param Email $email
 * @return Cribbb\Domain\Model\Identity\User
 */
private function findUserByEmail(Email $email)
{
    $user = $this->users->userOfEmail($email);

    if ($user) return $user;

    throw new ValueNotFoundException("$email is not a registered email address");
}

If the email is not found in the database we can just abort the process here by throwing an exception.

Back in the request() method we can delete any existing reminders for this user by calling the deleteExistingRemindersForEmail() method on the ReminderRepository and passing the $email:

$this->reminders->deleteExistingRemindersForEmail($email);

Finally we can create a new Reminder instance, add it to the Repository and then return the object from the method:

$id = $this->reminders->nextIdentity();

$reminder = new Reminder($id, $email, ReminderCode::generate());

$this->reminders->add($reminder);

return $reminder;

The first test for this method is to ensure that an exception is thrown if no user is found:

/** @test */
public function should_throw_exception_when_user_does_not_exist()
{
    $this->setExpectedException('Cribbb\Domain\Model\ValueNotFoundException');

    $this->users->shouldReceive('userOfEmail')->andReturn(null);

    $this->service->request('nope@domain.com');
}

In this method I’m telling the UserRepository mock to return null to simulate that no user was found.

Next we can test to ensure a new Reminder is returned from the request() method when everything goes smoothly:

/** @test */
public function should_request_and_return_new_reminder()
{
    $this->users->shouldReceive('userOfEmail')->andReturn($this->user);
    $this->reminders->shouldReceive('deleteExistingRemindersForEmail');
    $this->reminders->shouldReceive('nextIdentity')->andReturn(ReminderId::generate());
    $this->reminders->shouldReceive('add');

    $reminder = $this->service->request('name@domain.com');
    $this->assertInstanceOf('Cribbb\Domain\Model\Identity\Reminder', $reminder);
}

The check method

Next we can write the code for the check() method. This method will be called when the user clicks on the link in the reminder email. This method should ensure that the reminder code exists, is valid and belongs to the correct email address:

/**
 * Check to see if the email and token combination are valid
 *
 * @param string $email
 * @param string $code
 * @return bool
 */
public function check($email, $code)
{
    $code = ReminderCode::fromNative($code);
    $email = new Email($email);

    $reminder = $this->reminders->findReminderByEmailAndCode($email, $code);

    if ($reminder && $reminder->isValid()) return true;

    return false;
}

First we turn the raw values in Value Objects.

Next we pass the valid objects to the Repository and we save the return value into the $reminder variable.

Next we check to see if the $reminder is not null and if it is valid.

If both conditions pass, we can return true, otherwise we return false.

Notice how we’ve kept the logic around what is deemed “valid” internal to the Reminder object.

The first test for the check() method should return true when a reminder is found and it is valid:

/** @test */
public function should_find_reminder_and_return_true_when_valid()
{
    $reminder = new Reminder(
        $this->fixture['id'],
        $this->fixture['email'],
        $this->fixture['code']
    );

    $this->reminders->shouldReceive('findReminderByEmailAndCode')->andReturn($reminder);

    $this->assertTrue($this->service->check('name@domain.com', 'abc123'));
}

The next test should return false when the reminder code is invalid:

/** @test */
public function should_find_reminder_and_return_false_when_invalid()
{
    Carbon::setTestNow(Carbon::create(2014, 10, 11, 10, 23, 34));

    $reminder = new Reminder(
        $this->fixture['id'],
        $this->fixture['email'],
        $this->fixture['code']
    );

    Carbon::setTestNow();

    $this->reminders->shouldReceive('findReminderByEmailAndCode')->andReturn($reminder);

    $this->assertFalse($this->service->check('name@domain.com', 'abc123'));
}

In this test I’m using the setTestNow() on Carbon to artificially set the current timestamp. This is a really nice feature of Carbon that alone makes it worth using.

The reset method

Finally we can write the method that will accept the request from the user to update their password.

/**
 * Reset a user's password
 *
 * @param string $email
 * @param string $password
 * @param string $code
 * @return User;
 */
public function reset($email, $password, $code)
{
    if ($this->check($email, $code)) {
        $user = $this->findUserByEmail(Email::fromNative($email));

        $password = $this->hasher->hash(new Password($password));

        $user->resetPassword($password);

        $this->users->update($user);

        $this->reminders->deleteReminderByCode(ReminderCode::fromNative($code));

        return $user;
    }

    throw new InvalidValueException("$code is not a valid reminder code");
}

First we pass the $email and $code to the check() method again to ensure nothing has been tampered with. If the check() method returns false we can abort by throwing an exception.

We can test for this behaviour with the following test:

/** @test */
public function should_throw_exception_during_reset_attempt_when_email_or_code_are_invalid()
{
    $this->setExpectedException('Cribbb\Domain\Model\InvalidValueException');

    $this->reminders->shouldReceive('findReminderByEmailAndCode')->andReturn(null);

    $this->service->reset('name@domain.com', 'qwerty123', 'abc123');
}

In this test I’m telling the mocked ReminderRepository to return null when the findReminderByEmailAndCode() method is called.

If the check() method returns true we can reset the user’s password:

$user = $this->findUserByEmail(new Email($email));

$password = $this->hasher->hash(new Password($password));

$user->resetPassword($password);

$this->users->update($user);

$this->reminders->deleteReminderByCode(ReminderCode::fromNative($code));

return $user;

First we attempt to find the user using the findUserByEmail() from earlier. If the user is not found, an exception will be thrown.

Next we can pass the password into the HashingService to receive an instance of HashedPassword in return.

Next we can pass the HashedPassword to the resetPassword() method on the User object. This method looks like this:

/**
 * Reset the User's password
 *
 * @param HashedPassword $password
 * @return void
 */
public function resetPassword(HashedPassword $password)
{
    $this->password = $password;

    $this->record(new PasswordWasReset($this));
}

Notice how we also record a PasswordWasReset Domain Event. This will be used to send a confirmation email to the user that they successfully changed their password.

Next we can pass the user to the UserRepository to persist the user to the database:

$this->users->update($user);

Now that the reminder has been successfully used, we can delete it from the database using the deleteReminderByCode() method:

$this->reminders->deleteReminderByCode(ReminderCode::fromNative($code));

And finally, we can return the $user from the method. The $user object is loaded with the PasswordWasReset Domain Event ready to be fired:

return $user;

To test this method we can assert that each of the dependencies is called correctly and the $user is returned:

/** @test */
public function should_reset_password_and_return_user()
{
    $reminder = new Reminder(
        $this->fixture['id'],
        $this->fixture['email'],
        $this->fixture['code']
    );

    $this->reminders->shouldReceive('findReminderByEmailAndCode')->andReturn($reminder);
    $this->users->shouldReceive('userOfEmail')->andReturn($this->user);
    $this->hasher->shouldReceive('hash')->andReturn(new HashedPassword('qwerty123'));
    $this->users->shouldReceive('update');
    $this->reminders->shouldReceive('deleteReminderByCode');

    $user = $this->service->reset('name@domain.com', 'qwerty123', 'abc123');
    $this->assertInstanceOf('Cribbb\Domain\Model\Identity\User', $user);
}

Conclusion

The functionality to reset passwords is an important part of all consumer web applications. It’s easy to overlook as it’s probably not something you think about pre-launch, but it is something that your users will end up requesting.

In this tutorial I’ve decided to model this functionality as a Domain Service. As with last week, I feel that this functionality is important, but I think creating this as an Application Service would also be a fine choice.

I think an important takeaway from this tutorial was how much of the functionality we built into Entities and Value Objects. If you find yourself in a situation where your services have more responsibility than your Domain Objects, it’s probably a sign that you’re doing something wrong. Domain Objects should hold the responsibility for business logic.

Following on from last week, hopefully this was another good example of using a Repository interface in the Domain layer, and a Repository implementation in the Infrastructure layer. It’s also really important that you test your Repository implementations by allowing them to hit the database.

And finally, I hope this was a clear example of the division of responsibility in a web application. Imagine if we had written this functionality as a single service class? That class would be huge and very difficult to test or modify! Instead we’ve split the responsibility down into individual objects and services. This makes our code easier to understand, test and evolve and it also allows us to create complex functionality by composing these individual objects in different ways.

This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.