Laravel控制器 - 模型异常处理结构与数据库事务

时间:2017-05-09 07:11:36

标签: php laravel exception-handling transactions try-catch

关于体系结构,将模型中的异常抛出到控制器时,哪两个是一个很好的做法?

结构A:

UserController.php

public function updateUserInfo(UserInfoRequest $request, UserModel $userModel)
{
    $isError = false;
    $message = 'Success';

    try {
        $message = $userModel->updateUserInfo($request->only(['username', 'password']));
    } catch (SomeCustomException $e) {
        $isError = true;
        $message = $e->getMessage();
    }

    return json_encode([
        'isError' => $isError,
        'message' => $message
    ]);
}

UserModel.php

public function updateUserInfo($request)
{
    $isError = false;
    $message = 'Success';

    $username = $request['username'];
    $password = $request['password'];

    try {
        $this->connect()->beginTransaction();

        $this->connect()->table('users')->where('username', $username)->update(['password' => $password]);

        $this->connect()->commit();
    } catch (\Exception $e) {
        $this->connect()->rollback();
        $isError = true;
        $message = $e->getMessage();        
    }

    return [
        'isError' => $isError,
        'message' => $message
    ];
}

结构B:

UserController.php

public function updateUserInfo(UserInfoRequest $request, UserModel $userModel)
{
    $isError = false;
    $message = 'Success';

    try {
        $userModel->updateUserInfo($request->only(['username', 'password']));
    } catch (SomeCustomException $e) {
        $isError = true;
        $message = $e->getMessage();
    } catch (QueryException $e) {
        $isError = true;
        $message = $e->getMessage();    
    }

    return json_encode([
        'isError' => $isError,
        'message' => $message
    ]);
}

UserModel.php

public function updateUserInfo($request)
{
    $username = $request['username'];
    $password = $request['password'];

    try {
        $this->connect()->beginTransaction();

        $this->connect()->table('users')->where('username', $username)->update(['password' => $password]);

        $this->connect()->commit();
    } catch (\Exception $e) {
        $this->connect()->rollback();
        throw new QueryException();
    }
}

结构A 中,模型捕获任何异常,回滚事务并在控制器发生错误或没有错误时返回。然后控制器返回从模型返回的任何内容。

结构B 中,模型捕获任何异常,回滚事务,然后在发生异常时抛出QueryException。然后,控制器从模型中捕获抛出的QueryException,如果它有错误或没有错误,则返回返回。

结构B 仍有问题的原因是该模型应该是进行回滚的模型。如果我要删除模型上的try-catch和控制器以直接捕获异常,那么回滚将在控制器上处理,我认为这会使控制器的功能变得混乱。

让我知道你的想法。 谢谢!

4 个答案:

答案 0 :(得分:5)

为什么我认为B的方法更好:

  1. 您的模型应该只包括逻辑部分:这包括与数据库的通信(事务和回滚),您要打印给用户的错误消息的格式。

  2. 保持模型清洁:这是MVC结构中最重要的部分。如果搞砸了,就很难找到任何错误。

  3. 外包错误处理:如果你把它放在控制器中你可以选择在那里处理它(也许你想要一些特殊的格式化输出用于这种方法,或者你需要一些其他的函数来调用)或你处理它在App\Exceptions\Handler。在这种情况下,您可以在此处呈现此错误消息,而不必在控制器中执行此操作。

  4. 因此,如果您不需要任何特殊的函数调用并希望使用Laravel的全部功能,我建议您结构C

    UserController.php

    public function updateUserInfo(UserInfoRequest $request, UserModel $userModel)
    {
        $userModel->updateUserInfo($request->only(['username', 'password']));
        return response()->json(['message' => 'updated user.']); 
    }
    

    UserModel.php

    public function updateUserInfo($request)
    {
        $username = $request['username'];
        $password = $request['password'];
        try {
            $this->connect()->beginTransaction();
    
            $this->connect()->table('users')->where('username', $username)->update(['password' => $password]);
    
            $this->connect()->commit();
        } catch (\Exception $e) {
            $this->connect()->rollback();
            throw new QueryException();
        }
    }
    

    应用\例外处理程序\

    public function render($request, Exception $exception)
    {
        //catch everything what you want
        if ($exception instanceof CustomException) {
            return response()->json([
              'message' => $exception->getMessage()
            ], 422);
        }
    
        return parent::render($request, $exception);
    }
    

    您可以清楚地分离数据库内容(模型),演示文稿内容(Controller)和错误处理(Handler)。结构C允许您在其他控制器函数中具有相同情况的其他函数中重用错误处理。

    这是我的意见,但我愿意讨论您认为这种方法不是最佳解决方案的任何情况。

答案 1 :(得分:2)

首先,对于您的示例,您甚至不需要使用Transaction。您只执行一个查询。那为什么你需要回滚?您想要回滚哪个查询?当您需要完全处理一组更改以考虑操作完成且有效时,应使用事务。如果第一个成功,但以下任何一个有任何错误,您可以回滚所有内容,就好像什么也没做过一样。

其次,让我们来看看良好实践或最佳实践。 Laravel建议使用瘦控制器和厚模型。因此,您的所有业务逻辑应该在模型中,甚至在存储库中更好。控制人将充当经纪人。它将从存储库或模型中收集数据并将其传递给查看。

或者,laravel提供了一些非常方便的组织代码的方法。您可以在模型中使用EventObservers进行并发操作。

最佳实践因用户的知识和经验而异。所以谁知道,你问题的最佳答案还未到来。

答案 2 :(得分:1)

我宁愿保持控制器和系统的任何其他部分与模型交互,尽可能不了解模型的内部工作原理。因此,例如,我试图避免在模型之外知道QueryException,而是尽可能将其视为普通的PHP对象。

此外,我会避免使用自定义JSON响应结构并使用HTTP statuses。如果有意义,可能更新用户信息的路由会返回更新的资源,或者200 OK就足够了。

// UserModel.php
public function updateUserInfo($request)
{
    $username = $request['username'];
    $password = $request['password'];

    try {
        $this->connect()->beginTransaction();

        $this->connect()->table('users')->where('username', $username)->update(['password' => $password]);

        $this->connect()->commit();

        return $this->connect()->table('users')->where('username', $username)->first();
        // or just return true;
    } catch (\Exception $e) {
        $this->connect()->rollback();

        return false;
    }
}

// UserController.php    
public function updateUserInfo(UserInfoRequest $request, UserModel $userModel)
{
    $updated = $userModel->updateUserInfo($request->only(['username', 'password']));

    if ($updated) {
        return response($updated);
        // HTTP 200 response. Returns JSON of updated user.
        // Alternatively,
        // return response('');
        // (200 OK response, no content)
    } else {
        return response('optional message', 422);
        // 422 or any other status code that makes more sense in the situation.
    }

(完全偏离主题,我想这是一个例子,但以防万一,提醒不要存储纯文本密码。)

答案 3 :(得分:1)

我不明白,为什么你不看Jeffry课程,但是为了更新用户你不需要试试/捕捉部分。 你控制器方法:

public function update(UpdateUserRequest $request, User $user) : JsonResponse
{
   return response()->json($user->update($request->all()))
}

您请求规则方法:

public function rules(): array
{
    return [
        'username' => 'required|string',
        'password' => 'required|min:6|confirmed',
    ];
}

你的异常处理程序渲染方法:

public function render($request, Exception $exception)
{
    if ($request->ajax() || $request->wantsJson()) {
        $exception = $this->prepareException($exception);

        if ($exception instanceof \Illuminate\Http\Exception\HttpResponseException) {
            return $exception->getResponse();
        } elseif ($exception instanceof \Illuminate\Auth\AuthenticationException) {
            return $this->unauthenticated($request, $exception);
        } elseif ($exception instanceof \Illuminate\Validation\ValidationException) {
            return $this->convertValidationExceptionToResponse($exception, $request);
        }

        // we prepare custom response for other situation such as modelnotfound
        $response = [];
        $response['error'] = $exception->getMessage();

        if (config('app.debug')) {
            $response['trace'] = $exception->getTrace();
            $response['code'] = $exception->getCode();
        }

        // we look for assigned status code if there isn't we assign 500
        $statusCode = method_exists($exception, 'getStatusCode')
            ? $exception->getStatusCode()
            : 500;

        return response()->json($response, $statusCode);
    }
    return parent::render($request, $exception);
}

现在,如果你有Exception,Laravel会在Json中为你提供状态代码!= 200,否则会给你成功结果!

相关问题