【问题标题】:Laravel best strategy to serve requests from API and form at the same timeLaravel 同时服务来自 API 和表单的请求的最佳策略
【发布时间】:2020-10-19 08:08:51
【问题描述】:

使用 Laravel 7.*,我的任务是创建一个简单的应用程序来发送支付请求,用户填写表格并发送数据,然后我验证用户输入并创建一个新的支付实例。

然后用户被重定向回同一页面。 (当然还有其他要求列出所有付款和更新付款):

   //In PaymentController.php

   public function store()
    {
        $inputData = $this->validateRequest();

        $person = $this->personRepository->findOneByAttribute('id_number', request('id_number'));

        if ($person instanceof Person) {
            $this->paymentRepository->create($inputData, $person);
            return back()->with('successMessage', 'Your payment request registered successfully.');
        } else {
            return back()->with('failureMessage', 'Shoot! Cannot find a peron with the given Identification Number.')->withInput();
        }
    }

一切都很好,但是我需要实现一个 Restful API 来执行相同的请求并获得有效的 json 响应,假设没有前端 JavaScript 框架,实现这个目标的最佳方法是什么?

我应该创建一个单独的控制器吗?或者只是检查请求是从传统表单还是从 API 客户端发送的?我是否缺少设计模式?

【问题讨论】:

    标签: php laravel design-patterns laravel-7


    【解决方案1】:

    简单的方法是检查您应该发回什么样的响应:

       //In PaymentController.php
    
       public function store()
        {
            $inputData = $this->validateRequest();
    
            $person = $this->personRepository->findOneByAttribute('id_number', request('id_number'));
    
            if ($person instanceof Person) {
                $this->paymentRepository->create($inputData, $person);
                if (request()->expectsJson()) {
                    return response('', 201); // Just a successfully created response 
                }
                return back()->with('successMessage', 'Your payment request registered successfully.');
            } else {
                if (request()->expectsJson()) {
                    return response()->json([ 'error' => 'Not found' ], 404); // You can change the error code and message (or remove the message) as needed
                }
                return back()->with('failureMessage', 'Shoot! Cannot find a person with the given Identification Number.')->withInput();
            }
        }
    

    你当然可以选择把它封装在一个实现Responsible接口的类中

    class PaymentResponse implements Responsible {
         private $success;
         public function __construct($success) {
               $this->success = $success;
         }
    
         public function toResponse($request) {
             if ($this->success) {
                if (request()->expectsJson()) {
                    return response()->json([ 'error' => 'Not found' ], 404); // You can change the error code and message (or remove the message) as needed
                }
                return back()->with('failureMessage', 'Shoot! Cannot find a person with the given Identification Number.')->withInput();
              } 
              if (request()->expectsJson()) {
                  return response()->json([ 'error' => 'Not found' ], 404); // You can change the error code and message (or remove the message) as needed
              }
              return back()->with('failureMessage', 'Shoot! Cannot find a person with the given Identification Number.')->withInput();
         }
      
    }
    

    那么您的代码将是:

    //In PaymentController.php
    
       public function store()
        {
            $inputData = $this->validateRequest();
    
            $person = $this->personRepository->findOneByAttribute('id_number', request('id_number'));
    
            if ($person instanceof Person) {
                $this->paymentRepository->create($inputData, $person);
                return new PaymentResponse(true);
            } else {
                return new PaymentResponse(false);
            }
        }
    

    当然,您也可以将控制器逻辑提取到单独的库中,然后拥有两个单独的控制器方法,如果您愿意,仍然可以使用负责的对象。这实际上取决于您的用例以及最适合您的方法

    【讨论】:

    • 感谢您的及时回复,我会尝试测试您的第二个建议。
    • 您的解决方案适用于我们重定向回用户的情况,如果用户想要查看所有付款并且我们返回视图怎么办?
    • 在这种情况下,我认为您可能需要一个单独的控制器操作。将控制器逻辑提取到单独的库中可能会很好,这样您就可以在需要时重新使用它,但因为响应会有所不同。我的意思是您可以将参数传递给同一个控制器操作并将该参数传递给 Repsonsible 对象以确定要发送什么样的响应,但这会使控制器操作承担太多责任 IMO
    【解决方案2】:

    我认为另一种方法是利用服务存储库模式。您将应用程序服务包装在一个单独的类中。服务是控制器和存储库之间的交互器。流程看起来像[request] -> [controller] -> [service] -> [repository]

    通过利用这种模式,您可以在应用程序的不同区域重复使用您的服务。例如,通过返回 JSON 数据,您可以拥有一个特定于服务传统 Web 应用程序的控制器和一个服务于 SPA 的控制器,但服务于相同的业务流程。

    例如:

    付款响应:

    class PaymentStoreResponse
    {
        protected $message;
        protected $code;
        protected $extraData;
    
        public function __construct($message, $code, $extraData = "")
        {
            $this->message = $message;
            $this->code = $code;
            $this->extraData = $extraData;
        }
    
        public function getMessage()
        {
            return $this->message;
        }
    
        public function getCode()
        {
            return $this->code;
        }
        
        public function getExtraData()
        {
            return $this->extraData;
        }
    }
    

    支付服务:

    function store($data)
    {
        $person = $this->personRepository->findOneByAttribute('id_number', $data('id_number'));
    
        if ($person instanceof Person) {
            $this->paymentRepository->create($inputData, $person);
            return new PaymentResponse("paymentSuccess", 201, "successMessage");
        } else {
            return new PaymentResponse("notFound", 404, "failureMessage");
        }
    }
    

    控制器:

    // App\Controllers\Web\PaymentController.php
    function store(Request $request)
    {
        $inputData = $this->validateRequest();
        $response = $this->paymentService->store($inputData);
        
        return back()->with($response->getExtraData(), $response->getMessage()) 
    }
    
    
    // App\Controllers\Api\PaymentController.php
    function store(Request $request)
    {
        // validation might be different because
        // api might need to authenticate with jwt etc.
        $inputData = $this->validateRequest();
        $response = $this->paymentService->store($inputData);
    
        return response()->json(['message' => $response->getMessage()], $response->getCode());
    }
    

    这将产生一个更简洁的控制器,因为控制器将只处理请求验证和响应,同时将业务流程委托给服务类(支付服务)。业务逻辑也集中在服务层,这意味着如果业务发生变化,它将应用到 API 控制器和 Web 控制器。因此,它将使您免于重构噩梦。

    您可以探索不同的架构,例如 Clean Architecture + DDD。它将使您的开发体验更好,实现集中的领域逻辑,并通过依赖抽象实现层之间的低耦合。

    【讨论】:

      猜你喜欢
      • 2015-12-11
      • 2012-09-15
      • 2018-11-05
      • 1970-01-01
      • 2011-08-20
      • 1970-01-01
      • 1970-01-01
      • 2012-10-30
      • 2016-01-30
      相关资源
      最近更新 更多