DEV Community

Discussion on: How to implement Clean Architecture with Laravel

Collapse
tiagodevweb profile image
Tiago Lopes • Edited on

Yes,
In a post creation case where the authenticated user can be a simple author or a moderator, where should publish permission be checked?

With a conditional in the interactor:

 <?php

 class CreatePostInteractor implements CreatePostInputPort
 {
       public function __construct(
          private CreatePostOutputPort $output,
          private PostRepository $repository,
          private PostFactory $factory,
          private IAM $iam
        ) {}

      public function createPost(CreatePostRequestModel $request): ViewModel
      {
          /* @var PostEntity */
          $post = $this->factory->make([
             'title' => $request->getTitle(),
             'body' => $request->getBody(),
             'published' => null
          ]);

         if ($this->iam->hasRole($request->getUserAuth(), 'moderator')) {
             $post->publish();
         }

         if ($this->repository->titleExists($request->getTitle())) {
             return $this->output->titleAlreadyExists(
                 new CreatePostResponseModel($post)
            );
        }

        try {
             $user = $this->repository->create($post);
        } catch (\Exception $e) {
             return $this->output->unableToCreatePost(
                 new CreatePostResponseModel($user), $e
             );
        }

         return $this->output->postCreated(
              new CreatePostResponseModel($user)
         );
     }
 }
Enter fullscreen mode Exit fullscreen mode

With derivations for each type of user:

 <?php

 abstract class AbstractCreatePostInteractor
 {
       public function __construct(
           private CreatePostOutputPort $output,
           private PostRepository $repository,
           private PostFactory $factory
        ) { }

       abstract protected function makePost(CreatePostRequestModel $request): PostEntity;

       public function createPost(CreatePostRequestModel $request): ViewModel
       {
             /* @var PostEntity */
             $post = $this->makePost($request);

            if ($this->repository->titleExists($request->getTitle())) {
                 return $this->output->titleAlreadyExists(
                     new CreatePostResponseModel($post)
                 );
            }

            try {
                  $user = $this->repository->create($post);
            } catch (\Exception $e) {
                  return $this->output->unableToCreatePost(
                       new CreatePostResponseModel($user), $e
                  );
            }

            return $this->output->postCreated(
                  new CreatePostResponseModel($user)
            );
       }
  }

  class CreatePostInteractor extends AbstractCreatePostInteractor implements CreatePostInputPort
  {

        protected function makePost(CreatePostRequestModel $request): PostEntity
        {
              return $this->factory->make([
                 'title' => $request->getTitle(),
                 'body' => $request->getBody(),
                 'published' => null
         ]);
     }
  }

 class CreatePostInteractorAsModerator extends AbstractCreatePostInteractor implements CreatePostInputPort
 {

      protected function makePost(CreatePostRequestModel $request): PostEntity
      {
              return $this->factory->make([
                 'title' => $request->getTitle(),
                 'body' => $request->getBody(),
                 'published' => new \DateTimeImmutable()
      ]);
   }
}
Enter fullscreen mode Exit fullscreen mode
Thread Thread
bdelespierre profile image
Benjamin Delespierre Author • Edited on

Both approaches could work just fine IMHO. The second one is better suited if the two roles have a lot of differences in their use-cases, justifying the existence of two distinct use-cases.

I also believe the keyword interactor should be a suffix of the class like CreatePostAsModeratorInteractor.

Also note that the interactor returning the entity directly is not a recommendation of the Clean Architecture as it will inevitably introduce coupling between the interactor and its callers.