skip to Main Content

Currently, whenever a user opens a URL '/users/new', I use the following route that leads to the controller called 'UsersController':

Route::post('/users/new', [AppHttpControllersApiUserController::class, 'newUser']);

Inside the Controller, the 'newUser' method is responsible for sending the request data (name_user, email_user, password_user) to a Provider called UserProvider:

public function newUser(Request $request){
      $allRequests = $request->all();//get all POST fields
      $resultForm = UserProvider::validateFORM($allRequests);//Validade all POST fields
      
      if($resultForm == null){//If 'null', the validation process is OK
         UserProvider::addNewUser($allRequests);//Send POST to be insert in database
         return json_encode(array('success' => true));//Return Success
      }

      return json_encode($resultForm);//Return the message of validation process
}

In UserProvider is the place where all the data processing logic takes place, from queries to the Models, communication with external APIs, validations and so on.

Regarding this, I would like to know if this is the best way to do this, that is, to put all the processing logic inside a Provider.

If not, what would be the best location?

2

Answers


  1. In my opinion, There is no best way that can be implemented in all projects. On the other hand, It is better to separate the layers of your application because of testing, maintenance, extending, … etc. For example, in your method:

    1- All validations can be placed in a FormRequest,
    So you can validate the incoming data freely and set proper messages or even change some data before entering it into the controller.

    2- Controllers should manage to provide the intended response, not do it themselves. They call other classes to fulfill this purpose. There are two common ways to implement the logic of the endpoint:

    • An action class containing just one public method named handle or execute. For example AddNewUserAction
    • A Service class containing some methods; each method is responsible for implementing the logic. For example UserService with methods like add, delete,.. etc.

    3- The logic is written in the action class or A method of service class (Depending on your choice). It can be done here if you need to write queries or call external Apis. (My personal preference is to do it in separate classes)

    4- Finally, returning the response by ApiResource, you can decorate the data for or use the view method.

    In the end, you can write tests for each layer easily. In addition, each class are reusable;

    public function newUser(NewUserRequest $request, AddNewUserAction $action): UserResource
    {
       $user = $action->handle($request);
          
    
       return UserResource::make($user);
    }
    

    My last recommendation is to use Data Transfer Objects Laravel-data for sending data through the layers instead of using Request object.

    Login or Signup to reply.
  2. So, let me try to help you.

    Let’s first "beautify" the code, not only formatting (that is very oppinionated, so you can ignore that part) but also some lines of code (they could be replaced), I will change lines to what the ideal code would be for Laravel:

    use AppDomainUserUser;
    
    class UserController extends Controller
    {
        public function store(StoreRequest $request)
        {
            User::add($request->validated());
    
            return response()->json(['success' => true]); // This could be just ->emptyResponse() as it will return 200, you don't need success true
        }
    }
    

    So:

    1. I have changed the method name from newUser to store, because you should only have CRUD actions: index, show, create, store, edit, update, delete, forceDelete (if you have soft deletion). Read more about controllers and this in the corresponding section.
    2. I have replaced Request with a Form Request StoreRequest, you could also call it NewUserRequest, but I personally prefer to have AppHttpRequestsUserStoreRequest than having AppHttpRequestsNewUserRequest.
    3. All the code inside the method can be replaced by 2 or 3 lines of code:
      1. Send the already validated data to a Domain class that only has the logic for adding a user (and maybe updating, deleting, in this example you only have addition).
      2. If there is any error/exception, the controller is going to automatically handle it (the framework in reality) and just return the error as JSON (if the request has Accept: application/json), so you should not need to bother about it.
      3. When everything goes well, not exception is thrown, so you can return a JSON with your desired data (in your case you are returning success => true, I would avoid that as it is meaningless because you are already returning 200 HTTP code, that means it worked, you could even return 201 -> ->json(['success' => true], 201); that means Resource Created in API terminology).
        1. You could use return response()->emptyResponse(); and that should return nothing but 200. I cannot remember if it is emptyResponse or emptyContent or something like that.

    Continuing, you shared this route:

    Route::post('/users/new', [AppHttpControllersApiUserController::class, 'newUser']);
    

    Ideally it should be like this:

    Route::post('/users', [AppHttpControllersApiUserController::class, 'store']);
    

    Because Route::post is already going to mean STORE (create), if you wanted to update data it should be Route::put. Then no need to specify /new, as, again, having Route::post already means new resource stored. Read the first link I shared, you have a list of Route::xxxx and URLs, how Laravel interprets them when using Route::resource or Route::apiResource.

    Lastly, if you would like to have User::add or anything similar that takes care of storing a new User or resource, I would try to approach the Domain Driven Design. I do not know much about it, but what I ended up doing every times it creating a Domain folder inside app folder, and then you can separate by concerns, for example:

    • app
      • Domain
        • User
          • Models
          • Services (this is used ONLY to communicate with services that are not on the same Laravel app, like a local microservice or a 3rd party service)
          • Requests (you could also have the requests in here, so it is easier to track them).
          • Events
          • Listeners
          • Jobs
          • etc….

    This last part is very oppinionated and depends on you and your team, there i no "best way" but the way that works for you and your team and the system you are working with. See this approach as a tool, there is no best tool for EVERYTHING, but just tools for every job, choose the best one that fits your workflow.

    One more thing, do not mix Providers and Services, Providers are exclusively for Laravel -> ServiceProvider, Laravel uses that to set binds, abstracts, and way more things (more info here) to aid you in the usage of the Framework, Services should only be read as classes that communicate your app with an external system, it could be a local system on the same machine (but not on the Laravel app code) or it could be external, it could be an API REST, SOAP, GraphQL, etc. Do not use Services as a "domain" logic class, it is going just to be a naming mess and confuse everyone, that is why I use AppDomain.

    Of course there is more to know but you will have to look for that. I can share this similar approach that was shared in a Laracon (do watch them in youtube):

    And, of course, read the documentation, fully read it and see what the framework has: https://laravel.com/docs/10.x/

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search