1

I'm in the process of moving validation into model (watson/validating package). But some validation rules can't be moved. For instance, consider user registration form. You want to enforce passwords to be no less then 6 characters. You can't do it in the model, since in the model you've got hash of the password.

But then, if your password fails to pass controller validation rules, only errors from controller validation are reported. I'd obviously like user to see results of both controller and model validation.

x-yuri
  • 16,722
  • 15
  • 114
  • 161
  • In what situation would you need both? The idea seems redundant to me. – Erik Berkun-Drevnig Jan 07 '17 at 20:57
  • @ErikBerkun-Drevnig Haven't I described it? Generally, you'd like to have validation in the model, to not duplicate it every time you're creating a model. But minimum length password validation can't be performed in the model, since you've got hash in the model, not the password itself. – x-yuri Jan 07 '17 at 21:18
  • What I mean is that usually the data you put into your model is coming from the controller so request validation should be sufficient. In what situation would you validate the model when the values themselves are already validated? – Erik Berkun-Drevnig Jan 07 '17 at 21:20
  • Like I said, to not duplicate validation rules. For example, you've got one form to create an object with basic set of fields. Later, user can press edit and change basic + some extra fields. Besides, testing model generally faster/easier than testing controller. – x-yuri Jan 07 '17 at 21:37
  • Yeah so in that case you could create a form request object https://laravel.com/docs/5.3/validation#form-request-validation and use it for both create and edit forms. – Erik Berkun-Drevnig Jan 07 '17 at 22:58
  • Well, I don't have strong arguments for now. But your suggestion seems to work out *most* of the time, not *every* time. Occasionally I'll most likely be faced with a situation, where I'm supposed to decide whether to move validation rules into models or do some workaround. And then there'll be more code, and more difficult to change anything. By the way, how do you test form requests? – x-yuri Jan 07 '17 at 23:19
  • To test you would just instantiate a form request object in your test then pass in some sample data and assert the result. Someone wrote an example here: http://stackoverflow.com/a/34404621/3239436 – Erik Berkun-Drevnig Jan 08 '17 at 00:54
  • @ErikBerkun-Drevnig Anyway, thanks for idea. Consider making it into answer. And don't take me wrong. I consider your suggestion viable and reasonable, and probably tempting. I just already have things set up, and fear not everything might fit well into your scheme. By the way, have you seen form requests (in terms of Laravel) in any other framework? – x-yuri Jan 10 '17 at 02:32

2 Answers2

1

I would suggest a combination of Form Request objects and database constraints. This way you can extract the form validation out of your controllers and reuse them for multiple requests if the fields are similar.

This will prevent users from getting incorrect data into your database however unlike validations on the model like you are trying to do, it will not prevent other developers from inserting data into the database if they neglect to use the correct validation object. Database constraints can remedy some of this but what you are describing is not part of the framework.

Good luck!

Erik Berkun-Drevnig
  • 2,306
  • 23
  • 38
0

Here's what I came up with:

class RegisterController extends Controller
{
    protected function validator(array $data)
    {
        $rules = merge_validation_rules((new User)->getRules(), [
            'email' => 'required',
            'password' => 'min:6',
        ]);
        return Validator::make($data, $rules);
    }
    ...

merge_validation_rules helper:

function merge_validation_rules($a, $b)
{
    $r = $a;
    foreach ($b as $k => $v)
        if (isset($a[$k])) {
            $r[$k] = array_merge(explode('|', $a[$k]), explode('|', $b[$k]));
            if (in_array('sometimes', $r[$k])
            && in_array('required', $r[$k]))
                $r[$k] = array_diff($r[$k], ['sometimes']);
            $r[$k] = implode('|', $r[$k]);
        } else
            $r[$k] = $b[$k];
    return $r;
}

merge_validation_rules test:

<?php

namespace Tests;

use Tests\TestCase;

class HelpersTest extends TestCase
{
    /**
     * @dataProvider mergeValidationRulesProvider
     */
    function testMergeValidationRules($input, $output)
    {
        $r = call_user_func_array('merge_validation_rules', $input);

        $this->assertEquals($output, $r);
    }
    function mergeValidationRulesProvider() { return [
        [[['a' => 'ra1', 'b' => 'rb1'], ['a' => 'ra2', 'c' => 'rc1']],
            ['a' => 'ra1|ra2', 'b' => 'rb1', 'c' => 'rc1']],
        [[['a' => 'sometimes'], ['a' => 'required']],
            ['a' => 'required']],
    ]; }
}

Here you can see one more issue. I'm using both user/password and social logins. So email is not always present (not every social networks returns it). So in the model I have 'email' => 'sometimes|email', but in registration controller I want it to be required. For now I remove sometimes if required is present. We'll see if it'll work out.

x-yuri
  • 16,722
  • 15
  • 114
  • 161