DEV Community

Discussion on: How to implement Clean Architecture with Laravel

Collapse
tiagodevweb profile image
Tiago Lopes

Another issue that I find interesting is authentication and authorization, which is more pleasant, a simple if in the interactor or polymorphism of the interactor for each role of the system?

Collapse
bdelespierre profile image
Benjamin Delespierre Author

Can you provide examples for these?

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.