Search code examples
phparchitecturedomain-driven-designvisibilityddd-service

PHP & DDD: How to ensure that only a Service can call a method on an entity?


I'm working with a domain model, in which I have a Reservation class:

class Reservation
{
    public function changeStatus($status) { ... }
}

Because the changeStatus() method should only be called in a context where all appropriate notifications are sent (emails, ...) I would like to restrict the call to this method to a ReservationService:

class ReservationService
{
    public function confirmReservation(Reservation $reservation)
    {
        $reservation->changeStatus(Reservation::STATUS_CONFIRMED);
        // commit changes to the db, send notifications, etc.
    }
}

Because I'm working with PHP, there is no such concept as package visibility or friend classes, so my changeStatus() method is just public and therefore callable from anywhere in the application.

The only solution I found to this problem, is to use some kind of double dispatch:

class Reservation
{
    public function changeStatus(ReservationService $service)
    {
        $status = $service->getReservationStatus($this);
        $this->setStatus($status);
    }

    protected function setStatus($status) { ... }
}

The potential drawbacks are:

  • That complicates the design a bit
  • That makes the entity aware of the Service, no sure whether that's actually a drawback or not

Do you guys have any comment on the above solution, or a better design to suggest to restrict access to this changeStatus() method?


Solution

  • One of the things that the Symfony2 and FLOW3 frameworks have adopted is tagging their stable public API with an @api annotation comment.

    While this is not exactly what you're looking for, it comes close. It documents the parts of your API that users can rely on. Plus your entity does not have to know about the service, avoiding the evil circular dependency.

    Example:

    class Request
    {
        /**
         * Gets the Session.
         *
         * @return Session|null The session
         *
         * @api
         */
        public function getSession()
        {
            return $this->session;
        }
    }