N
N
nepster-web2016-09-27 16:01:50
Software design
nepster-web, 2016-09-27 16:01:50

How to properly organize data processing in a controller action?

I wondered about simple and flexible actions in controllers. Actually, using my approach to design, I ran into problems, for the solution of which I want to use your advice.
Examples on laravel5
Controller:

<?php

namespace App\Http\Controllers;

use App\Http\Requests\TestRequest;
use App\Services\TestService;
use Exception;
use Session;
use DB;

class TestController extends Controller
{
    protected $testService;


    public function __construct(TestService $testService)
    {
        $this->testService = $testService;
    }


    public function index(TestRequest $request)
    {
        DB::beginTransaction();

        try {

            $result = $this->testService->process($request->all());

            if ($result) {
                DB::commit();

                // Данные сохранены успешно

            } else {
                DB::rollBack();

                // Ошибка сохранения данных

            }
        } catch (Exception $e) {
            DB::rollBack();

            // Ошибка обработки данных
        }

        //return redirect || view;
    }

}

Service
<?php

namespace App\Services;

use ErrorException;

class TestService
{
    public function process(array $data)
    {
        // Логика и аналитика для получания переменной $var
        $var = 7;

        if ($var > $data['count']) {
            throw new ErrorException('Возникла критическая ошибка, ...');
        }

        return true;
    }
}

Request
<?php

namespace App\Http\Requests;

use HttpResponseException;
use Validator;
use Response;

class TestRequest extends Request
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        $this->sanitize();

        return [
            // rules
        ];
    }

    public function messages()
    {
        return [];
    }

    public function sanitize()
    {
        $input = $this->all();
        $input['var1'] = filter_var(isset($input['var1']) ? $input['var1'] : null, FILTER_SANITIZE_STRING);
        $input['var2'] = filter_var(isset($input['var2']) ? $input['var2'] : null, FILTER_SANITIZE_STRING);
        $input['var3'] = filter_var(isset($input['var3']) ? $input['var3'] : null, FILTER_SANITIZE_STRING);
        $this->replace($input);
    }

}

There was a question / problem with data validation.
For good validation, a request should do it, but quite often there are situations in which I need to check if there is a record in the table and immediately work with it. That is, 2 requests come out, 1 - through the framework validators, and 2 - when we get the same entry in the controller or service for further work.
In addition to this, sometimes the logic can be quite convoluted, so in order to get some data for validation, you need to do a number of logical actions. I don’t really like the idea of ​​putting pieces of logic into a request (even if you pull services into a request) in order to do validation, and then almost the same thing in order to continue working with this matter in the service.
Accordingly, it turns out to be an inconvenient situation when part of the validation needs to be placed in the request, and part in the service. There is still the problem of rendering errors, when we get two types of validation, request and validation using exceptions from services.
Please tell me (preferably using pseudocode examples) how to arrange it beautifully?
I would be very grateful for detailed answers on this issue.

Answer the question

In order to leave comments, you need to log in

2 answer(s)
I
index0h, 2016-09-28
@nepster-web

For good validation, a request should be made

Nope. Request is just a collection of data.
You are trying to find the only correct place to check. On good checks should be in all methods, even in private ones. Something is wrong - throw an exception. This practice is at first glance creepy, but it will protect you from a huge number of errors.
Anticipating your question: "Well, how many checks will you have to duplicate?". Yes, a lot, but believe me, it's worth it.
If you are trying to capture multiple contexts at one point, sure. If you do the necessary checks everywhere, this difficulty will not arise.
The thought is sound.
Don't violate the SRP.
<?php

namespace Vendor\Project\AppBundle\Controller;

use KoKoKo\assert\Assert;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Vendor\Project\Path\Authorization\UserNotFoundException;
use Vendor\Project\Path\Authorization\InvalidCredentialsException;

class LoginController extends Controller
{
    /**
     * @Route("/login", name="login")
     * @Method({"POST"})
     * @param Request $request
     * @return JsonResponse
     */
    public function loginAction(Request $request) : JsonResponse
    {
        try {
            $login = $request->request->get('login');
            $pass  = $request->request->get('pass');

            Assert::assert($login, 'login')->string()->notEmpty()->match('/^[a-z\d]{3,32}$/i');
            Assert::assert($pass, 'pass')->string()->notEmpty()->lengthBetween(6, 32);
        } catch (\Throwable $exception) {
            return new JsonResponse($exception->getMessage(), JsonResponse::HTTP_BAD_REQUEST);
        }

        try {
            $user = $this->get('UserAuthorizator')->authorize($login, $pass);

            return new JsonResponse($user->getId());
        } catch (UserNotFoundException $exception) {
            return new JsonResponse('User not found', JsonResponse::HTTP_BAD_REQUEST);
        } catch (InvalidCredentialsException $exception) {
            return new JsonResponse('Invalid login or path', JsonResponse::HTTP_BAD_REQUEST);
        } catch (\Throwable $exception) {
            $this->get('logger')->error($exception->getMessage(), ['exception' => $exception]);

            return new JsonResponse('Something went wrnog :((', JsonResponse::HTTP_INTERNAL_SERVER_ERROR);
        }
    }
}

A
Alex Wells, 2016-09-28
@Alex_Wells

Unfortunately not. Check this: https://github.com/illuminate/validation/blob/mast...
Requests to the database go directly, so it is impossible to pass the finished model to the validator. Also have a look at this: https://github.com/illuminate/validation/blob/mast...
Also hardcoding. Laravel is not a magic wand and is far from perfect. It becomes very difficult with it when performing tasks more difficult than CRUD. But there are always options:
1) You can create your own validator class that will inherit the usual one, rewrite the verification methods from the database there, and also rewrite the DatabasePresenceVerifier so that it uses the passed model. But all this is clearly not going to be an elegant and casual solution, purely as an option.
2) Use third party validators.
3) And the easiest one is to just get the model, and then pass $model->toArray() to the validator. Thus, you can fully control what and where it comes from, as well as do different types of validations that go beyond the usual validator. But this approach also has disadvantages - some of the usual validator methods can be thrown out, such as unique and so on.
If option 3 is not suitable, which is most likely the case, then you need to look for absolutely custom verifiers.
There are no other options. Use the built-in tools Stalls - hell. I now store the UUID in binary form, so I need to unearth half of the framework here in order to implement it normally. Symfony clearly wins in this regard.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question