Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validating Unique Values on Update #6

Closed
adunk opened this issue May 19, 2014 · 17 comments
Closed

Validating Unique Values on Update #6

adunk opened this issue May 19, 2014 · 17 comments

Comments

@adunk
Copy link

adunk commented May 19, 2014

For example, on updating user account information. In the docs it looks like you can add a id to a unique column to ignore validation like so:

'email' => 'unique:users,email_address,10'

Does this package support this functionality? If so how would one implement this?

Thanks!

@carvefx
Copy link

carvefx commented May 20, 2014

we do it somewhat simplistically, but it works.

First of all you have two options: 1 class per action i.e. UpdateUserForm and CreateUserForm, or something similar to what we did below, where the rules var is set when calling a certain method.

if you look at the validateUpdate($id, array $params) method, you will notice that we simply append the id to the $update_rules and then send it off to @JeffreyWay package ;)

class UserForm extends FormValidator{
  /**
   * @var array
   */
  protected $create_rules =
    [
      'name'                  => 'Required|Min:3|Max:255|Regex:/^[a-z-.]+( [a-z-.]+)*$/i',
      'email'                 => 'Required|Between:3,255|Email|unique:users',
      'password'              => 'Required|Between:8,32|Confirmed|Regex:/^([a-z0-9!@#$€£%^&*_+-])+$/i',
      'password_confirmation' => 'Required|Between:8,32'
    ];

  /**
   * @var array
   */
  protected $update_rules =
    [
      'name'                  => 'Required|Min:3|Max:255|Regex:/^[a-z-.]+( [a-z-.]+)*$/i',
      'email'                 => 'Required|Between:3,255|Email|unique:users',
      'password'              => 'Between:8,32|Confirmed|Regex:/^([a-z0-9!@#$€£%^&*_+-])+$/i',
      'password_confirmation' => 'Required_With:password|Between:8,32'
    ];

  /**
   * @var array
   */
  protected $rules = [];

  /**
   * @param array $input
   * @return mixed
   */
  public function validateCreate(array $input)
  {
    $this->rules = $this->create_rules;
    return $this->validate($input);
  }

  /**
   * @param int $id
   * @param array $input
   * @return mixed
   */
  public function validateUpdate($id, array $input)
  {
    $this->update_rules['email'] = "Required|Between:3,255|Email|unique:users,email,$id";
    $this->rules = $this->update_rules;
    return $this->validate($input);
  }
} 

@drehimself
Copy link

Thanks @carvefx your code works great :)

@carvefx
Copy link

carvefx commented May 21, 2014

If anybody finds a more elegant solution, I'd love to see it in action, I feel dirty about appending strings directly, seems so low-level :P

@adunk
Copy link
Author

adunk commented May 21, 2014

Thanks! This helped me a lot. Glad to also see someone else was looking for this functionality.

@adunk adunk closed this as completed May 21, 2014
@matyhaty
Copy link

matyhaty commented Aug 2, 2014

Is this really closed.

Seems messy to me, and obviously is over riding methods which really should be able to cope with this....

@JeffreyWay is this something you could look at and maybe address is Laracasts (Which is excellent)

Could the validator function accept a second param, array which has key (name of form element which has unique requirements) => $id.

From here, the $rules could be editted???

@matyhaty
Copy link

matyhaty commented Aug 4, 2014

Maybe a slight improvement..

<?php

namespace Teamleaf\Depots\Forms;

use Laracasts\Validation\FormValidator;

class DepotForm extends FormValidator
{
    protected $rules = [];
    /*
     * Validation rules for the status form
     * @var Array
     */
    protected $create_rules = [
        'title'         => 'required|unique:depots',
        'code'         => 'required|unique:depots',
        'address'       => 'required'
    ];

    protected $update_rules = [
        'title'         => 'required|unique:depots',
        'code'         => 'required|unique:depots',
        'address'       => 'required'
    ];

    /**
     * @param array $input
     * @return mixed
     */
    public function validateCreate(array $input)
    {
        $this->rules = $this->create_rules;
        return $this->validate($input);
    }

    /**
     * @param int $id
     * @param array $input
     * @return mixed
     */
    public function validateUpdate(array $input, $id)
    {
        $this->update_rules['title'] .= ',title,'.$id;
        $this->update_rules['code'] .= ',code,'.$id;
        $this->rules = $this->update_rules;
        return $this->validate($input);
    }
} 

and

public function update($id)
    {
        $inputs = Input::get();
        $inputs['userId'] = Auth::id();
        $inputs['id'] = $id;

        $this->depotForm->validateUpdate($inputs, $id);

        $depot = $this->execute(UpdateDepotCommand::class, $inputs);

        Flash::message("Your depot has been updated!");

        return Redirect::to('/depots');
    }

Realistically the passed $id needs to be $overrides which should be an array for multiple options, e.g. two unique ids in different tables etc.

@laracasts
Copy link
Collaborator

Hey, guys -

Firstly, I very quickly scanned this thread, so if I missed something, let me know. (Sorry, in a hurry.)

I'm not sure if the package needs to provide a solution for this. You can add a helper method to your form object to set an excludes on the appropriate column. Something like:

public function excludeUserId($id)
{
    $this->rules['username'] = "required|unique:users,username,$id"

    return $this;
}

Then, you can do $this->updateAccountForm->excludeUserId(Auth::id())->validate();

@schulzefelix
Copy link

Hi,
at first I definitely would love to see that implemented.

I extend your FormValidator class like this (maybe not the best way to do the replacement)

public function setCurrent($id)
    {
        $this->currentId = $id;
        return $this;
    }

    public function validate($formData)
    {
        $formData = $this->normalizeFormData($formData);

        if( is_numeric($this->currentId))
        {
            $rules = $this->getValidationRules();
            foreach($rules as $key => $value){
                $rules[$key] = str_replace(':current', $this->currentId, $rules[$key]);
            }
            $this->rules = $rules;

        } else
        {
            $this->rules = $this->getValidationRules();
        }

        $this->validation = $this->validator->make(
            $formData,
            $this->rules,
            $this->getValidationMessages()
        );

[...]

Then you can use in your rules

'email' => 'required|email|unique:users,email,:current',

and in your Controller:

$this->updateUserForm->setCurrent($id)->validate($input);

I hope you will include something like that in one update, also if its only with one id at the moment.

Felix

@ShaneG
Copy link

ShaneG commented Aug 7, 2014

This project has the functionality and seems to work pretty neatly, albeit doing a few too many tasks in a single class:

https://github.com/philipbrown/magniloquent

@huglester
Copy link

@JeffreyWay I think it would be good to implement something like this into the package.

for example me personnaly, can't remember any project, where this unique thing would not be required...

for now I just created something like this

<?php namespace App\Forms;

use Laracasts\Validation\FormValidator;

class CarColorForm extends FormValidator {

    protected $rules = [
        'name' => 'required|unique:car_colors,name,{id}',
        'hex'  => 'required|unique:car_colors,hex,{id}',
    ];

    public function validateUpdate($id, array $formData)
    {
        $search = [',{id}'];
        $replace = [''];

        if ($id)
        {
            $replace = [','.$id];
        }

        foreach ($this->rules as $k => $rules)
        {
            $this->rules[$k] = str_replace($search, $replace, $rules);
        }

        return parent::validate($formData);
    }

}

and I just call $this->$this->carColorForm->validateUpdate($id, $input);

but probably funtionality like this out of the box would be perfect :)

Thanks

@gravitano
Copy link

I think the $rules property would be great if replaced with the rules method. As same as the Form Request class in laravel 5.

If you define the validation rules in a method. You can do something like this.

public function rules()
{
     return [
         'username' => 'required|unique:users,username,' . Auth::id()
     ];
}

This is only a suggestion. But, this is a great package.

@leothelocust
Copy link

@JeffreyWay how would you now do this with Laravel 5? Since the "YourCustomFormRequest" has no knowledge of the input data, how might you pass an $id to the Request?

Particularly, when it's not something you can conveniently grab like @gravitano suggested above.

What am I missing?

@leothelocust
Copy link

I mean, this works:

public function rules()
    {
        $id = Request::segment(2);
        return [
            'name' => 'required',
            'slug' => 'required|unique:departments,slug,'.$id.'|alpha_dash'
        ];
    }

but to me that just seems hacky and probably not the best way of doing it.

Other suggestions are greatly appreciated!

@TomCawthorn
Copy link

It doesn't sound that hack to me! To avoid breaking stuff that's already setup (and I haven't got round to updating yet), I use the following in FormValidator:


    /**
     * @return array
     */
    public function getValidationRules()
    {
        if (method_exists($this, 'rules')) {
            return $this->rules();  
        }
        return $this->rules;    
    }

Then I reference the request like @leothelocust

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
                extract(\Request::only('name'));
        $userClubID = \Auth::User()->club->id;
        return [
            'name' => 'required|unique:signups,name,'. $name .',id,club_id,'. $userClubID,
            'description' => 'required',            
        ];
    }

@JacobBennett
Copy link

@leothelocust @TomCawthorn Laravel 5 has no need of this package as it is implementing its own validation using Form Requests which you can read more about over at Matt Staufers blog http://mattstauffer.co/blog/laravel-5.0-form-requests

@JacobBennett
Copy link

Also have my own solution to the multiple sets of rules for a single resource. You can check it out in my fork of the repo over at https://github.com/JacobBennett/Validation.

The idea is essentially that you set nested rules in the rules array like so

$rules = [
   'store' => [
      'user' => 'required',
      'password' => 'required'
   ],
   'update' => [
      'password' => 'required'
   ]
];

Then before validating, you simply set a context on the validator like so

$this->validator->setContext('update')->validate();

Easy solution, and tests are included. Has been working great for me.

@parweb
Copy link

parweb commented Jan 12, 2015

my extends of Laracasts\Validation\FormValidator

namespace Acme\Core;

use Laracasts\Validation\FormValidator as _FormValidator;

class FormValidator extends _FormValidator {

    protected $rule = null;

    public function getValidationRules() {

        // si on passe en arg un array
        if ( is_array( $this->rule ) ) {

            // recupere toutes les values
            $rule_val = current( array_values( $this->rule ) );
// print_r($rule_val);exit;
            // si l'array contien qu'un seul élément et que les values sont un array
            if ( count( $this->rule ) == 1 && is_array( $rule_val ) ) {

                // récupere la premiere key de l'array
                $rule_key = current( array_keys( $this->rule ) );

                // si la premiere key de l'array est présent dans la class Acme/Forms/{$name}Form.php dans $this->rules ( add || edit || whatever || ... ).
                if ( in_array( $rule_key, array_keys( $this->rules ) ) ) {

                    // on fusionne les 2 array
                    return array_merge( $this->rules[$rule_key], $rule_val );

                }

                //si 1er key pas présente on return les nouvelles rules passé en arg en enlevant le parents ( ex: edit => [ $rule1, $rule2, ... ] )
                return $rule_val;
            }

            // si array plus que 1 éléments on return directement les rules
            return $this->rule;
        }

        // si on passe en arg un string
        if ( is_string( $this->rule ) ) {

            // on return la bonne rules
            return $this->rules[$this->rule];

        }

        // sinon rien de tout cela comportement par default
        return $this->rules;

    }

    public function validate ( array $formData, $rule = null ) {

        $this->rule = $rule;

        // dd($this->rules);

        return parent::validate( $formData );

    }

}

example of a FormValidator

namespace Acme\Forms;

use Acme\Core\FormValidator;

class UserForm extends FormValidator {

    protected $rules = [

        'add' => [

            'login' => 'required|alpha_dash|unique:user,login,NULL,id,deleted_at,NULL',
            'email' => 'required|email|unique:user,email,NULL,id,deleted_at,NULL',
            'password' => 'required|confirmed'

        ]

    ];

}

Using in a controller

// ....

public function store( $id = null ) {

    $user = User::firstOrNew(['id' => $id]);

    if ( $id === null ) {
        $this->userForm->validate(Input::all(), 'add');

        // or merging with the existing one ( in Acme\Form\UserForm )
        // if rule "add" doesn't existe juste apply the  [ 'rule1', 'rule2'  ] 
        $this->userForm->validate(Input::all(), [ 'add' => [ 'rule1', 'rule2'  ] ]);

        $user->password = Input::get( 'password' );

        Flash::success("L'utilisateur a été ajouté avec succès !");     
    }
    else {
        $rules = [
            'login' => 'required|alpha_dash|unique:user,login,'.$id.',id,deleted_at,NULL',
            'email' => 'required|email|unique:user,email,'.$id.',id,deleted_at,NULL'
        ];

        $this->userForm->validate( Input::all(), $rules );

        Flash::success("L'utilisateur a été modifié avec succès !");
    }

    $user->login = htmlspecialchars(Input::get( 'login' ));
    $user->email = htmlspecialchars(Input::get( 'email' ));
    $user->firstname = htmlspecialchars(Input::get( 'firstname' ));
    $user->lastname = htmlspecialchars(Input::get( 'lastname' ));

    $user->save();

    return Redirect::to( 'user' );
}

// ....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests