在Laravel中从另一个控制器调用控制器是一种好的实践吗?

6
我成功实现了一个PaypalController,其中包含一个可重用的postPayment()方法,该方法接受商品和价格信息,创建一个Paypal支付,并将用户重定向到Paypal支付页面。
class PaypalController extends Controller {

    private static $_api_context;

    private static function initialize() {
        //initialize api context
    }

    public static function postPayment($items, $currency, $description) {
        self::initialize();

        //create item list, transaction, payment objects, etc

        $payment->create(PaypalController::$_api_context);
        ...
        return redirect()->away($redirect_url); // redirect to paypal
    }
}

PaypalController 是其他控制器静态调用的。例如,AuthController 可以在用户注册后立即调用它来请求用户支付:

class AuthController extends Controller {
    public function postRegister(Request $request) {
        return PaypalController::postPayment($items, 'JPY', 'description');
    }
}

基本上,PaypalController 返回一个 RedirectAuthController,后者也返回它,以执行重定向到 Paypal 支付页面。
我想知道这是否是一个好的设计 - 一个控制器调用另一个控制器,是吗?
如果不是,有什么更好的方法可以做到这一点吗?也许将我的代码从 PaypalController 移动到自定义服务提供程序、自定义帮助程序或其他什么地方?我非常新手 Laravel,我需要一些指导。
3个回答

3
不,这不是一个好的实践方法。您应该将业务逻辑抽象到服务/存储库类中。例如:
创建一个接口作为契约:
namespace App\Services\Paypal;

interface PaypalInterface {

     public function PostRegister(Array $array, /*More $params if necessary*/); 
}

然后实现合约:

namespace App\Services\Paypal;

class PaypalService implements PaypalInterface {

    // Must match the method signature declared in the interface
    public function PostRegister(Array $array, /*$More $params if necessary*/) {

        // Do the process here
    }
}

然后将合同/接口用作依赖项。因此,在您的PaypalController或任何其他控制器中,您可以重新使用它,例如:

namespace App\Http\Controllers;

use App\Http\Request;
use App\Services\Paypal\PaypalInterface;

class AuthController extends Controller {
    public function postPayment(Request $request, PaypalInterface $paypalService) {
        return $paypalService->postRegister($request->all());
    }
}

在这种情况下,需要在服务提供程序(基本上是在AppServiceProvider中)中注册绑定(接口到实现)。这就是基本的工作流程。为什么使用接口,因为控制器(客户端/消费者类)应该与契约/接口交互,而不是具体的实现。 我的这篇文章可能会对你有所帮助,但请记住,这并不是100%解耦的,它仍然与Laravel框架相耦合,您甚至可以进一步解耦服务。
注:这是最佳实践,但不要盲目地为每个项目/问题遵循此方法,只有在明智选择时才应该这样做,这真的取决于上下文,但不要死板地遵循它。当前的上下文适合这样做。

为什么我要使用接口?我担心在这种特定情况下使用接口会过度设计。 - Obay
2
这是解耦业务逻辑的正确/最佳实践,也就是说:按接口编程,而不是具体实现。换句话说,对抽象类型(接口)的依赖使双方不会紧密耦合,并且可以在不触及应用程序逻辑的情况下更改依赖关系。但是像我说的那样,做你认为更好的事情,不要遵循任何规则,直到你意识到需要并理解它。无论如何,这是一个引发讨论的话题。 - The Alpha
此外,请查看此链接 - The Alpha

1
你的postPayment是一个静态方法,这让我感到不适,因为它告诉我“不在控制器内”。
  • 正如你所说,我认为服务是更好的选择,如果你想的话,可以看一下Omnipay

  • 你也可以将PaypalController设为抽象类,AuthController会继承它(只有在多个控制器中使用postPayment时才需要)。

  • 你可以做一个PaypalTrait,并在你的AuthController中使用它(只有在多个控制器中使用postPayment时才需要)。

  • 当然,如果对你来说有意义,你也可以将第一种解决方案与其他解决方案结合起来。

对于这个问题,有很多答案,我认为没有完美的答案,它真的取决于你正在构建什么和你的需求。


其他人建议使用服务提供者,但我喜欢Trait的简单性。我对这两个概念都很陌生,但在我的情况下,Trait是否合适?如果我理解正确,Trait将用于实现多重继承,而Paypal处理不应该被“继承”。你怎么看? - Obay
你可以创建一个 CanProcessPayment Trait 并在每个需要处理 PayPal 的控制器中使用它。但不要将其设置为静态的,并重命名你的 initialize 方法以确保命名不冲突... 如果你查看 Laravel 的 BaseController,它使用多个 Traits 为每个控制器添加功能。抽象的 PaypalController 也不是一个坏的解决方案,它就像 Trait 一样工作,你只需要继承你的控制器即可。 - Elie Faës

1
这不是正确的做法。记住,控制器应该只接收请求并将其分派到正确的服务,而不是自己处理业务逻辑。
这个规则适用:当你需要从另一个控制器调用控制器方法时,它会让代码变得糟糕。
相反,使用服务提供者和服务类来处理您的PayPal逻辑。服务类将处理PayPal逻辑,并在以后需要在控制器中使用时使用。
//SERVICE CLASS
class PayPalService
{
    public function processPayment(){ //... }
}

服务提供者用于在应用程序中注册服务类: 你告诉Laravel, 当你需要一个 PayPalService 时,它会为你构建并返回它。

//SERVICE PROVIDER: binds the creation of the service in the ioc container
class PayPalServiceProvider extends ServiceProvider
{
    public function register()
    {
        //use singleton or bind to bind the service in the ioc container
        $this->app->singleton( PayPalService::class, function()
        {
            return new PayPalService();
        });
    }
}

当您需要使用PayPalService类时,应该让Laravel自动将服务注入到您的控制器中:

class AuthController extends Controller 
{   
    public function postRegister(Request $request, PayPalService $paypal) {
        return $paypal->processPayment();
    }
}

如果您愿意,您可以通过为服务类使用接口来进一步改进此设计。

在您的最后一个例子中,您是不是想说:return $paypal->processPayment(); - Obay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接