I have a Laravel User model which has a unique validation rule on username and email. In my Repository, when I update the model, I revalidate the fields, so as to not have a problem with required rule validation:
public function update($id, $data) {
$user = $this->findById($id);
$user->fill($data);
$this->validate($user->toArray());
$user->save();
return $user;
}
This fails in testing with:
ValidationException: {"username":["The username has already been
taken."],"email":["The email has already been taken."]}
Is there a way of fixing this elegantly?
Append the id of the instance currently being updated to the validator.
Pass the id of your instance to ignore the unique validator.
In the validator, use a parameter to detect if you are updating or creating the resource.
If updating, force the unique rule to ignore a given id:
//rules
'email' => 'unique:users,email_address,' . $userId,
If creating, proceed as usual:
//rules
'email' => 'unique:users,email_address',
Another elegant way...
In your model, create a static function:
public static function rules ($id=0, $merge=[]) {
return array_merge(
[
'username' => 'required|min:3|max:12|unique:users,username' . ($id ? ",$id" : ''),
'email' => 'required|email|unique:member'. ($id ? ",id,$id" : ''),
'firstname' => 'required|min:2',
'lastname' => 'required|min:2',
...
],
$merge);
}
Validation on create:
$validator = Validator::make($input, User::rules());
Validation on update:
$validator = Validator::make($input, User::rules($id));
Validation on update, with some additional rules:
$extend_rules = [
'password' => 'required|min:6|same:password_again',
'password_again' => 'required'
];
$validator = Validator::make($input, User::rules($id, $extend_rules));
Nice.
Working within my question:
public function update($id, $data) {
$user = $this->findById($id);
$user->fill($data);
$this->validate($user->toArray(), $id);
$user->save();
return $user;
}
public function validate($data, $id=null) {
$rules = User::$rules;
if ($id !== null) {
$rules['username'] .= ",$id";
$rules['email'] .= ",$id";
}
$validation = Validator::make($data, $rules);
if ($validation->fails()) {
throw new ValidationException($validation);
}
return true;
}
is what I did, based on the accepted answer above.
EDIT: With Form Requests, everything is made simpler:
<?php namespace App\Http\Requests;
class UpdateUserRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required|unique:users,username,'.$this->id,
'email' => 'required|unique:users,email,'.$this->id,
];
}
}
You just need to pass the UpdateUserRequest to your update method, and be sure to POST the model id.
Unique Validation With Different Column ID In Laravel
'UserEmail'=>"required|email|unique:users,UserEmail,$userID,UserID"
or what you could do in your Form Request is (for Laravel 5.3+)
public function rules()
{
return [
'email' => 'required|email|unique:users,email,'. $this->user
//here user is users/{user} from resource's route url
];
}
i've done it in Laravel 5.6 and it worked.
'email' => [
'required',
Rule::exists('staff')->where(function ($query) {
$query->where('account_id', 1);
}),
],
'email' => [
'required',
Rule::unique('users')->ignore($user->id)->where(function ($query) {
$query->where('account_id', 1);
})
],
Laravel 5 compatible and generic way:
I just had the same problem and solved it in a generic way. If you create an item it uses the default rules, if you update an item it will check your rules for :unique and insert an exclude automatically (if needed).
Create a BaseModel class and let all your models inherit from it:
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model {
/**
* The validation rules for this model
*
* #var array
*/
protected static $rules = [];
/**
* Return model validation rules
*
* #return array
*/
public static function getRules() {
return static::$rules;
}
/**
* Return model validation rules for an update
* Add exception to :unique validations where necessary
* That means: enforce unique if a unique field changed.
* But relax unique if a unique field did not change
*
* #return array;
*/
public function getUpdateRules() {
$updateRules = [];
foreach(self::getRules() as $field => $rule) {
$newRule = [];
// Split rule up into parts
$ruleParts = explode('|',$rule);
// Check each part for unique
foreach($ruleParts as $part) {
if(strpos($part,'unique:') === 0) {
// Check if field was unchanged
if ( ! $this->isDirty($field)) {
// Field did not change, make exception for this model
$part = $part . ',' . $field . ',' . $this->getAttribute($field) . ',' . $field;
}
}
// All other go directly back to the newRule Array
$newRule[] = $part;
}
// Add newRule to updateRules
$updateRules[$field] = join('|', $newRule);
}
return $updateRules;
}
}
You now define your rules in your model like you are used to:
protected static $rules = [
'name' => 'required|alpha|unique:roles',
'displayName' => 'required|alpha_dash',
'permissions' => 'array',
];
And validate them in your Controller. If the model does not validate, it will automatically redirect back to the form with the corresponding validation errors. If no validation errors occurred it will continue to execute the code after it.
public function postCreate(Request $request)
{
// Validate
$this->validate($request, Role::getRules());
// Validation successful -> create role
Role::create($request->all());
return redirect()->route('admin.role.index');
}
public function postEdit(Request $request, Role $role)
{
// Validate
$this->validate($request, $role->getUpdateRules());
// Validation successful -> update role
$role->update($request->input());
return redirect()->route('admin.role.index');
}
That's it! :) Note that on creation we call Role::getRules() and on edit we call $role->getUpdateRules().
I have BaseModel class, so I needed something more generic.
//app/BaseModel.php
public function rules()
{
return $rules = [];
}
public function isValid($id = '')
{
$validation = Validator::make($this->attributes, $this->rules($id));
if($validation->passes()) return true;
$this->errors = $validation->messages();
return false;
}
In user class let's suppose I need only email and name to be validated:
//app/User.php
//User extends BaseModel
public function rules($id = '')
{
$rules = [
'name' => 'required|min:3',
'email' => 'required|email|unique:users,email',
'password' => 'required|alpha_num|between:6,12',
'password_confirmation' => 'same:password|required|alpha_num|between:6,12',
];
if(!empty($id))
{
$rules['email'].= ",$id";
unset($rules['password']);
unset($rules['password_confirmation']);
}
return $rules;
}
I tested this with phpunit and works fine.
//tests/models/UserTest.php
public function testUpdateExistingUser()
{
$user = User::find(1);
$result = $user->id;
$this->assertEquals(true, $result);
$user->name = 'test update';
$user->email = 'ddd#test.si';
$user->save();
$this->assertTrue($user->isValid($user->id), 'Expected to pass');
}
I hope will help someone, even if for getting a better idea. Thanks for sharing yours as well.
(tested on Laravel 5.0)
A simple example for roles update
// model/User.php
class User extends Eloquent
{
public static function rolesUpdate($id)
{
return array(
'username' => 'required|alpha_dash|unique:users,username,' . $id,
'email' => 'required|email|unique:users,email,'. $id,
'password' => 'between:4,11',
);
}
}
.
// controllers/UsersControllers.php
class UsersController extends Controller
{
public function update($id)
{
$user = User::find($id);
$validation = Validator::make($input, User::rolesUpdate($user->id));
if ($validation->passes())
{
$user->update($input);
return Redirect::route('admin.user.show', $id);
}
return Redirect::route('admin.user.edit', $id)->withInput()->withErrors($validation);
}
}
If you have another column which is being used as foreign key or index then you have to specify that as well in the rule like this.
'phone' => [
"required",
"phone",
Rule::unique('shops')->ignore($shopId, 'id')->where(function ($query) {
$query->where('user_id', Auth::id());
}),
],
I am calling different validation classes for Store and Update. In my case I don't want to update every fields, so I have baseRules for common fields for Create and Edit. Add extra validation classes for each. I hope my example is helpful. I am using Laravel 4.
Model:
public static $baseRules = array(
'first_name' => 'required',
'last_name' => 'required',
'description' => 'required',
'description2' => 'required',
'phone' => 'required | numeric',
'video_link' => 'required | url',
'video_title' => 'required | max:87',
'video_description' => 'required',
'sex' => 'in:M,F,B',
'title' => 'required'
);
public static function validate($data)
{
$createRule = static::$baseRules;
$createRule['email'] = 'required | email | unique:musicians';
$createRule['band'] = 'required | unique:musicians';
$createRule['style'] = 'required';
$createRule['instrument'] = 'required';
$createRule['myFile'] = 'required | image';
return Validator::make($data, $createRule);
}
public static function validateUpdate($data, $id)
{
$updateRule = static::$baseRules;
$updateRule['email'] = 'required | email | unique:musicians,email,' . $id;
$updateRule['band'] = 'required | unique:musicians,band,' . $id;
return Validator::make($data, $updateRule);
}
Controller:
Store method:
public function store()
{
$myInput = Input::all();
$validation = Musician::validate($myInput);
if($validation->fails())
{
$key = "errorMusician";
return Redirect::to('musician/create')
->withErrors($validation, 'musicain')
->withInput();
}
}
Update method:
public function update($id)
{
$myInput = Input::all();
$validation = Musician::validateUpdate($myInput, $id);
if($validation->fails())
{
$key = "error";
$message = $validation->messages();
return Redirect::to('musician/' . $id)
->withErrors($validation, 'musicain')
->withInput();
}
}
public static function custom_validation()
{
$rules = array('title' => 'required ','description' => 'required','status' => 'required',);
$messages = array('title.required' => 'The Title must be required','status.required' => 'The Status must be required','description.required' => 'The Description must be required',);
$validation = Validator::make(Input::all(), $rules, $messages);
return $validation;
}
I had the same problem.
What I've done: add in my view hidden field with id of a model and in validator check the unique, only if I've get some id from view.
$this->validate(
$request,
[
'index' => implode('|', ['required', $request->input('id') ? '' : 'unique:members']),
'name' => 'required',
'surname' => 'required',
]
);
You can trying code bellow
return [
'email' => 'required|email|max:255|unique:users,email,' .$this->get('id'),
'username' => 'required|alpha_dash|max:50|unique:users,username,'.$this->get('id'),
'password' => 'required|min:6',
'confirm-password' => 'required|same:password',
];
Laravel 5.8 simple and easy
you can do this all in a form request with quite nicely. . .
first make a field by which you can pass the id (invisible) in the normal edit form. i.e.,
<div class="form-group d-none">
<input class="form-control" name="id" type="text" value="{{ $example->id }}" >
</div>
...
Then be sure to add the Rule class to your form request like so:
use Illuminate\Validation\Rule;
... Add the Unique rule ignoring the current id like so:
public function rules()
{
return [
'example_field_1' => ['required', Rule::unique('example_table')->ignore($this->id)],
'example_field_2' => 'required',
];
... Finally type hint the form request in the update method the same as you would the store method, like so:
public function update(ExampleValidation $request, Examle $example)
{
$example->example_field_1 = $request->example_field_1;
...
$example->save();
$message = "The aircraft was successully updated";
return back()->with('status', $message);
}
This way you won't repeat code unnecessarily :-)
public function rules()
{
if ($this->method() == 'PUT') {
$post_id = $this->segment(3);
$rules = [
'post_title' => 'required|unique:posts,post_title,' . $post_id
];
} else {
$rules = [
'post_title' => 'required|unique:posts,post_title'
];
}
return $rules;
}
For a custom FormRequest and Laravel 5.7+ you can get the id of your updated model like this:
public function rules()
{
return [
'name' => 'required|min:5|max:255|unique:schools,name,'.\Request::instance()->id
];
}
For anyone using a Form request
In my case i tried all of the following none of them worked:
$this->id, $this->user->id, $this->user.
It was because i could not access the model $id nor the $id directly.
So i got the $id from a query using the same unique field i am trying to validate:
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
$id = YourModel::where('unique_field',$this->request->get('unique_field'))->value('id');
return [
'unique_field' => ['rule1','rule2',Rule::unique('yourTable')->ignore($id)],
];
}
It will work 100%
I have both case implement like One case is same form field in database table products and other is products_name is form field and in table, it's name is name, how we can validate and ignore that id while updating. I have encrypted that so i'm decrypted id, if you are encrypt then you will decrypt otherwise pass it as it's coming from the form.
$request->validate([
'product_code' => 'required|unique:products,product_code,'.decrypt($request->hiddenProductId),
'products_name' => 'required|unique:products,name,'.decrypt($request->hiddenProductId),
]);
there is detailed and straightforward answer to this question, I was looking for too
https://laravel.com/docs/9.x/validation#rule-unique
I learning about laravel framework, I don't know how to access property in my controller.
public function uuid(Request $request)
{
if($request->get('uuid') == null) return abort(404);
$uuid = $request->get('uuid');
$users = DB::table('users')->select('*')->where('uuid', $uuid)->get();
$result = array([
'id' => $users['id'],
'username' => $users['username'],
'uuid' => $users['uuid'],
]);
return view ('dashboard')->with('username', $result['username']);
}
in my dashboard.blade.php
{{$username}}
When i come to dashboard it show error like this
ErrorException (E_NOTICE)
Undefined index: username
Use First() instead of get() you'll get object so access data like.
$users = DB::table('users')->select('*')->where('uuid', $uuid)->first();
$result = array([
'id' => $users->id,
'username' => $users->username,
'uuid' => $users->uuid,
]);
return view ('dashboard')->with('username', $result['username']);
Now sort way to do it.
$user = DB::table('users')->select('*')->where('uuid', $uuid)->first();
$username = '';
if(!empty($user)){
$username = $user->username
}
return view ('dashboard',compact('username'));
$users is a collection of users. So you cannot access a property of a user like $users['id'];
If you want to get one user object from the database, you need to call first() instead of get()
Possible Solution
$user = DB::table('users')->select('*')->where('uuid', $uuid)->first();
You can use firstOrFail();
$users = DB::table('users')->select('*')->where('uuid', $uuid)->firstOrFail();
$result = array([
'id' => $users->id,
'username' => $users->username,
'uuid' => $users->uuid,
]);
return view ('dashboard')->with('username', compact('username'));
A possible short version of your solution may be like following
public function uuid(Request $request)
{
$user = User::select('username')->where('uuid', $request->uuid)->firstOrFail();
return view ('dashboard')->with('username', $user->username);
}
I have table which have multiple reference to ohter tables like
user
id name email
categories
id title
user_categories
user_id category_id
Here a user will have multiple category associated with him/her
I am able to save these successfully with new records like following
View File:
echo $form->field($package_categories, 'category_id')->dropDownList( ArrayHelper::map(
StudyMaterialCategories::find()->all(), 'id', 'title'),
['multiple' => true]
);
Save New record:
$model = new Packages();
$package_categories = new PackageCategories();
$request = Yii::$app->request;
if ($request->isPost) {
$transaction = Yii::$app->db->beginTransaction();
try {
$post = $request->post();
$model->load($post);
$model->save();
foreach ($post['PackageCategories']['category_id'] as $key => $value) {
$package_categories = new PackageCategories();
$package_categories->category_id = $value;
$package_categories->package_id = $model->id;
$package_categories->save();
}
$transaction->commit();
return $this->redirect(['view', 'id' => $model->id]);
} catch (Exception $ex) {
$transaction->rolback();
Yii::$app->session->setFlash("error", $ex->getMessage());
}
}
Till now It's running successfully.
But I'm stuck when going to update the table. The problem part is dropdown list. How to set multiple selected option as per database if I'm coming with array of object.
Have a look on the following code
$package_categories = PackageCategories::find()
->where('package_id=:package_id', ['package_id' => $id])->all();
if (count($package_categories) < 1) {
$package_categories = new PackageCategories();
}
$request = Yii::$app->request;
if ($request->isPost) {
$transaction = Yii::$app->db->beginTransaction();
try {
$post = $request->post();
$model->load($post);
$model->save();
$package_categories = new PackageCategories();
$package_categories->deleteAll(
"package_id=:package_id",
[':package_id' => $model->id]
);
foreach ($post['PackageCategories']['category_id'] as $key => $value) {
$package_categories = new PackageCategories();
$package_categories->category_id = $value;
$package_categories->package_id = $model->id;
$package_categories->save();
}
$transaction->commit();
return $this->redirect(['view', 'id' => $model->id]);
} catch (Exception $ex) {
$transaction->rolback();
Yii::$app->session->setFlash("error", $ex->getMessage());
}
}
if I try to get first object of the array $package_categories of only able to set selected one option
This is an example code of a model class Permit which has a many to many relationship with Activity through PermitActivity (pivot table model).
Model Class Activity
public class Permit extends \yii\db\ActiveRecord {
public $activities_ids;
...
public function rules() {
return [
...
[['activities_ids'], 'safe'],
...
];
}
...
// Method called after record is saved, be it insert or update.
public function afterSave($insert, $changedAttributes) {
// If this is not a new record, unlink all records related through relationship 'activities'
if(!$this->isNewRecord) {
// We unlink all related records from the 'activities' relationship.
$this->unlinkAll('activities', true);
// NOTE: because this is a many to many relationship, we send 'true' as second parameter
// so the records in the pivot table are deleted. However on a one to many relationship
// if we send true, this method will delete the records on the related table. Because of this,
// send false on one to many relationships if you don't want the related records deleted.
}
foreach($this->activities_ids as $activity_id) {
// Find and link every model from the array of ids we got from the user.
$activity = Activity::findOne($activity_id);
$this->link('activities', $activity);
}
parent::afterSave($insert, $changedAttributes);
}
...
// Declare relationship with Activity through the pivot table permitActivity
public function getActivities(){
return $this->hasMany(Activitiy::className(), ['id' => 'activity_id'])
->viaTable('permitActivity',['permit_id' => 'id']);
}
...
public function afterFind(){
parent::afterFind();
$this->activities_id = ArrayHelper::getColumn($this->activities, 'id');
}
}
This way the model class is the one responsible for creating and updating the relationship using the pivot table.
The most important thing is to have the relationship method declared correctly.
Edit
This is an example of the view using kartikv\widgets\Select2. I don't really know if dropDownList supports multiple select, however Select2 has so many useful features i usually use it over other options.
echo $form->field($model, 'activities')->widget(Select2::classname(), [
'data' => $data,
'options' => [
'placeholder' => '...'
],
'pluginOptions' => [
'allowClear' => true,
'multiple' => true,
],
]);
I followed a serie on creating a shopping cart on codecourse - and it was just what I was looking for.
In the series the payment happens on creating the order - but now my client says that they don't want the payment on creating order - first when they have processed the order - they want to send an email with payment or an link to a payment site.
I thought no big deal - just move the BraintreePayment par out of the create class - create a new class called payment and thats that - but no - so now I am stuck.
I am using swiftmailer to send the mail with the link and that works fine - but the payment part fails.
I am new to this MVC / Slim thing - so please can someone help me in the right direction.
The error it throws say:
Type: TypeError Message: Argument 1 passed to
Cart\Events\OrderWasCreated::__construct() must be an instance of
Cart\Models\Order, integer given, called in
/Applications/AMPPS/www/testshop.dev/cart/app/Controllers/OrderController.php
on line 160 File:
/Applications/AMPPS/www/testshop.dev/cart/app/Events/OrderWasCreated.php
Line: 17
My ordercontroller with the payment class - looks like this:
<?php
namespace Cart\Controllers;
use Slim\Router;
use Slim\Views\Twig;
use Cart\Basket\Basket;
use Cart\Models\Order;
use Cart\Models\Product;
use Cart\Models\Address;
use Cart\Models\Delivery;
use Cart\Models\Customer;
use Cart\Controllers\MailController;
use Cart\Validation\Contracts\ValidatorInterface;
use Cart\Validation\Forms\OrderForm;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Braintree_transaction;
class OrderController
{
protected $basket;
protected $mailcontroller;
protected $router;
protected $validator;
public function __construct(Basket $basket, Mailcontroller $mailcontroller, Router $router, ValidatorInterface $validator)
{
$this->basket = $basket;
$this->mailcontroller = $mailcontroller;
$this->router = $router;
$this->validator = $validator;
}
public function index(Request $request, Response $response, Twig $view)
{
$this->basket->refresh();
if (!$this->basket->subTotal()) {
return $response->withRedirect($this->router->pathFor('cart.index'));
}
return $view->render($response, 'order/index.twig');
}
public function show($hash, Request $request, Response $response, twig $view, Order $order)
{
$order = $order->with('address', 'products')->where('hash', $hash)->first();
$this->mailcontroller->mailLisbeth($hash);
if (!$order) {
return $response->withRedirect($this->router->pathFor('home'));
}
return $view->render($response, 'order/show.twig', [
'order' => $order,
]);
}
public function create(Request $request, Response $response, Customer $customer, Address $address, Delivery $delivery, Order $order)
{
$this->basket->refresh();
$validation = $this->validator->validate($request, OrderForm::rules());
if (!$this->basket->subTotal()) {
return $response->withRedirect($this->router->pathFor('cart.index'));
}
if ($validation->fails()) {
return $response->withRedirect($this->router->pathFor('order.index'));
}
$hash = bin2hex(random_bytes(32));
$customer = $customer->firstOrCreate([
'email' => $request->getParam('email'),
'name' => $request->getParam('name'),
]);
$delivery = $delivery->firstOrCreate([
'delivery' => $request->getParam('delivery'),
'deliverydate' => $request->getParam('deliverydate'),
'deliverytime' => $request->getParam('deliverytime'),
]);
$address = $address->firstOrCreate([
'address1' => $request->getParam('address1'),
'address2' => $request->getParam('address2'),
'city' => $request->getParam('city'),
'postal_code' => $request->getParam('postal_code'),
]);
$order = $customer->orders()->create([
'hash' => $hash,
'accepted' => "2",
'paid' => false,
'total' => $this->basket->subTotal() + 150,
]);
$address->order()->save($order);
$delivery->order()->save($order);
$allItems = $this->basket->all();
$order->products()->saveMany(
$allItems,
$this->getQuantities($allItems)
);
$event = new \Cart\Events\OrderWasCreated($order, $this->basket);
$event->attach([
// new \Cart\Handlers\MarkOrderPaid,
// new \Cart\Handlers\RecordSuccessfulPayment($result->transaction->id),
new \Cart\Handlers\UpdateStock,
new \Cart\Handlers\Emptybasket,
]);
$event->dispatch();
return $view->render($response, 'order/show.twig', [
'order' => $order,
]);
}
//
public function payment($slug, Request $request, Response $response, twig $view, Customer $customer, Address $address, Delivery $delivery, Order $order)
{
$order = $order->with('address', 'products')->where('id', $slug)->first();
// var_dump($order);
// die();
// if (!$request->getParam('payment_method_nonce')) {
// return $response->withRedirect($this->router->pathFor('order.index'));
// }
$order = $customer->orders()->update([
'paid' => true,
]);
$result = Braintree_Transaction::sale([
'amount' => $this->basket->subTotal() + 150,
'paymentMethodNonce' => $request->getParam('payment_method_nonce'),
'options' => [
'submitForSettlement' => true,
]
]);
$event = new \Cart\Events\OrderWasCreated($order, $this->basket);
if (!$result->success) {
$event->attach(new \Cart\Handlers\RecordFailedPayment);
$event->dispatch();
return $response->withRedirect($this->router->pathFor('order.index'));
}
$event->attach([
new \Cart\Handlers\MarkOrderPaid,
new \Cart\Handlers\RecordSuccessfulPayment($result->transaction->id),
// new \Cart\Handlers\UpdateStock,
// new \Cart\Handlers\Emptybasket,
]);
}
protected function getQuantities($items)
{
$quantities = [];
foreach ($items as $item) {
$quantities[] = ['quantity' => $item->quantity];
}
return $quantities;
}
}
The error occures on line 160:
$event = new \Cart\Events\OrderWasCreated($order, $this->basket);
because you invoke Cart\Events\OrderWasCreated constructor, which requires instance of Cart\Models\Order class as first argument. Instead your first argument ($order) is integer.
Now latest assignment of $order is on line 147:
$order = $customer->orders()->update([
'paid' => true,
]);
I'll have a guess and say that this is an operation that returns integer as result (be that a simple 1/0 success/fauilure or the id of the order being updated). Are you sure that you need that? Asking because:
You are assigning $order a value on line 138:
$order = $order->with('address', 'products')->where('id', $slug)->first();
And you're passing $order as an argument to the OrderController::payment.
So, my guess is: you want to remove lines 138 and 147.