Closed adunk closed 10 years ago
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);
}
}
Thanks @carvefx your code works great :)
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
Thanks! This helped me a lot. Glad to also see someone else was looking for this functionality.
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???
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.
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();
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
This project has the functionality and seems to work pretty neatly, albeit doing a few too many tasks in a single class:
@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
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.
@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?
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!
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',
];
}
@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
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.
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 );
}
}
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'
]
];
}
// ....
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' );
}
// ....
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!