(laravel) how to use unique validation request on update - php

I want to use unique validation in Lavavel 8 but the problem is that it doesn't allow me to update when I don't change the name field
my update code on my TemplateController
public function updateTemplate($templateId, TemplateRequest $templateRequest)
{
$thumbnailUrl = $this->templateService->updateThumbnail($templateRequest);
$this->templateRepository->updateTemplateInfo($templateRequest, $templateId, $thumbnailUrl);
return redirect()->route('templates.list.show', [$templateId])
->with(["message" => __('templates.edit.success')]);
}
this is my UpdateTemplateInfo at TemplateRepository
public function updateTemplateInfo($request, $templateId, $thumbnail)
{
$template = $this->getTemplate($templateId);
$template->name = $request->name;
$template->thumbnail = $thumbnail;
$template->business_type_id = $request->business_type;
$template->update();
}
and this is my TemplateRequest
public function rules()
{
return [
'name' => 'required|unique:templates',
'business_type' => 'required'
];
}
this is the method in web.php
Route::patch('/list/{templateId}/update', 'TemplateController#updateTemplate')->name('templates.list.update');
When I try to update without change the name field, the validator fails

If you don't want to create a separate request for post and put you can do something like this...
public function rules() {
$rules = [
'name' => 'required|unique:templates,name',
'business_type' => 'required'
];
if ($this->method() === 'PUT') {
$rules['name'] .= ',' . $this->route('templateId');
}
return $rules;
}

you should create a new request class for the update case
public function rules()
{
return [
'name' => "required|unique:templates,name,{$this->name}",
'business_type' => 'required'
];
}
or you can edit the current request class to this:
public function rules()
{
$rules = [
'business_type' => 'required'
];
if (request()->method == 'PUT') {
$rules['name'] = "required|unique:templates,name,{$this->name}";
} else {
$rules['name'] = 'required|unique:templates';
}
return $rules;
}

Related

CodeIgniter 4 redirect()->to() not working on IE

I am getting error from IE when I redirect to "dashboard" controller after settings session values in "login" function ( return redirect()->to(base_url('dashboard'));). I have this working on Chrome, Firefox, Edge, and Opera.
I am using public $sessionDriver = 'CodeIgniter\Session\Handlers\DatabaseHandler'; for session storage. this works well with other borwsers.
<?php
namespace App\Controllers;
use App\Controllers\BaseController;
use App\Models\UserModel;
class User extends BaseController
{
public function login()
{
$data = [];
if ($this->request->getMethod() == 'post') {
$rules = [
'email' => 'required|min_length[6]|max_length[50]|valid_email',
'password' => 'required|min_length[8]|max_length[255]|validateUser[email,password]',
];
$errors = [
'password' => [
'validateUser' => "Email or Password don't match",
],
];
if (!$this->validate($rules, $errors)) {
return view('login', [
"validation" => $this->validator,
]);
} else {
$model = new UserModel();
$user = $model->where('email', $this->request->getVar('email'))
->first();
// Stroing session values
$this->setUserSession($user);
// Redirecting to dashboard after login
return redirect()->to(base_url('dashboard'));
}
}
return view('login');
}
private function setUserSession($user)
{
$data = [
'id' => $user['id'],
'name' => $user['name'],
'phone_no' => $user['phone_no'],
'email' => $user['email'],
'isLoggedIn' => true,
];
session()->set($data);
return true;
}
public function register()
{
$data = [];
if ($this->request->getMethod() == 'post') {
//let's do the validation here
$rules = [
'name' => 'required|min_length[3]|max_length[20]',
'phone_no' => 'required|min_length[9]|max_length[20]',
'email' => 'required|min_length[6]|max_length[50]|valid_email|is_unique[tbl_users.email]',
'password' => 'required|min_length[8]|max_length[255]',
'password_confirm' => 'matches[password]',
];
if (!$this->validate($rules)) {
return view('register', [
"validation" => $this->validator,
]);
} else {
$model = new UserModel();
$newData = [
'name' => $this->request->getVar('name'),
'phone_no' => $this->request->getVar('phone_no'),
'email' => $this->request->getVar('email'),
'password' => $this->request->getVar('password'),
];
$model->save($newData);
$session = session();
$session->setFlashdata('success', 'Successful Registration');
return redirect()->to(base_url('login'));
}
}
return view('register');
}
public function profile()
{
$data = [];
$model = new UserModel();
$data['user'] = $model->where('id', session()->get('id'))->first();
return view('profile', $data);
}
public function logout()
{
session()->destroy();
return redirect()->to('login');
}
}
CodeIgniter4 has its "user agent class" this should help you to be able to validate if you are using IE, I share the documentation and I hope it helps you.
You can validate using that class and redirect with another method.
https://codeigniter.com/user_guide/libraries/user_agent.html

Required option of the filter deactivates validation in ZF3

In ZF3 I created a form with two fields: text and url. Only one of them may be filled out by user and at least one must be filled out.
Imagine: one can put the contents of the site or the url of the site. The form may be used to grab certain data from the site or text.
I prepared two validator classes. One for each input. The classes were getting the input value of the other one from context parameter. The StringLength validator was used for both fields.
This worked almost fine but the bad issue was coming when both fields were submitted empty. Then the data did pass the validation while it should no.
At the case of this issue the fields have required turned to false.
When I switched them to true both of fields got required but I wanted only one to be required.
So the goal is that when both fields were empty the validation result would get false. Then the only one message should appear. I mean the message more or less like this: One of fields must be filled out. Not the 'required' message.
Here you are the form class and both validator classes.
<?php
namespace Application\Filter;
use Application\Form\Test as Form;
use Application\Validator\Text;
use Application\Validator\Url;
use Zend\InputFilter\InputFilter;
class Test extends InputFilter
{
public function init()
{
$this->add([
'name' => Form::TEXT,
'required' => false,
'validators' => [
['name' => Text::class],
],
]);
$this->add([
'name' => Form::URL,
'required' => false,
'validators' => [
['name' => Url::class],
],
]);
}
}
<?php
namespace Application\Validator;
use Zend\Validator\StringLength;
use Zend\Validator\ValidatorInterface;
class Text implements ValidatorInterface
{
protected $stringLength;
protected $messages = [];
public function __construct()
{
$this->stringLengthValidator = new StringLength();
}
public function isValid($value, $context = null)
{
if (empty($context['url'])) {
$this->stringLengthValidator->setMin(3);
$this->stringLengthValidator->setMax(5000);
if ($this->stringLengthValidator->isValid($value)) {
return true;
}
$this->messages = $this->stringLengthValidator->getMessages();
return false;
}
if (!empty($value)) return false;
}
public function getMessages()
{
return $this->messages;
}
}
<?php
namespace Application\Validator;
use Zend\Validator\StringLength;
use Zend\Validator\ValidatorInterface;
class Url implements ValidatorInterface
{
const ERROR_NOT_ALLOWED_STRING = 'string-not-allowed';
protected $stringLength;
protected $messages = [
self::ERROR_NOT_ALLOWED_STRING => 'Only one of text and url field may by filled.',
];
public function __construct()
{
$this->stringLengthValidator = new StringLength();
}
public function isValid($value, $context = null)
{
if (empty($context['text'])) {
$this->stringLengthValidator->setMin(3);
$this->stringLengthValidator->setMax(500);
if ($this->stringLengthValidator->isValid($value)) {
return true;
}
$this->messages = $this->stringLengthValidator->getMessages();
return false;
}
if (!empty($value)) return false;
}
public function getMessages()
{
return $this->messages;
}
}
Update
I used advises from #Crisp and had to do some correction in the code. Added returns and message handling. The working code is below:
<?php
namespace Application\Filter;
use Application\Form\Test as Form;
use Application\Validator\Text;
use Application\Validator\Url;
use Zend\InputFilter\InputFilter;
class Test extends InputFilter
{
public function init()
{
$this->add([
'name' => Form::TEXT,
'required' => false,
'allow_empty' => true,
'continue_if_empty' => true,
'validators' => [
['name' => Text::class],
],
]);
$this->add([
'name' => Form::URL,
'required' => false,
'allow_empty' => true,
'continue_if_empty' => true,
'validators' => [
['name' => Url::class],
],
]);
}
}
<?php
namespace Application\Validator;
use Zend\Validator\StringLength;
use Zend\Validator\ValidatorInterface;
class Text implements ValidatorInterface
{
protected $stringLength;
protected $messages = [];
public function __construct()
{
$this->stringLengthValidator = new StringLength();
}
public function isValid($value, $context = null)
{
if (empty($context['url'])) {
if (empty($value)) return false;
$this->stringLengthValidator->setMin(3);
$this->stringLengthValidator->setMax(5000);
if ($this->stringLengthValidator->isValid($value)) {
return true;
}
$this->messages = $this->stringLengthValidator->getMessages();
return false;
}
if (!empty($value)) return false;
return true;
}
public function getMessages()
{
return $this->messages;
}
}
<?php
namespace Application\Validator;
use Zend\Validator\StringLength;
use Zend\Validator\ValidatorInterface;
class Url implements ValidatorInterface
{
const ERROR_NOT_ALLOWED_STRING = 'string-not-allowed';
const ERROR_EMPTY_FIELDS = 'empty-fields';
protected $stringLength;
protected $messages = [
self::ERROR_NOT_ALLOWED_STRING => 'Only one of text and url field may be filled out.',
];
public function __construct()
{
$this->stringLengthValidator = new StringLength();
}
public function isValid($value, $context = null)
{
if (empty($context['text'])) {
if (empty($value)) {
$this->messages = [
self::ERROR_EMPTY_FIELDS => 'One of the fields must be filled out.',
];
return false;
}
$this->stringLengthValidator->setMin(3);
$this->stringLengthValidator->setMax(500);
if ($this->stringLengthValidator->isValid($value)) {
return true;
}
$this->messages = $this->stringLengthValidator->getMessages();
return false;
}
if (!empty($value)) return false;
return true;
}
public function getMessages()
{
return $this->messages;
}
}
To ensure your validators always run, even for an empty value, you need to add the allow_empty and continue_if_empty options to your input specs. Otherwise validation is skipped for any value that isn't required.
The following combination should work
class Test extends InputFilter
{
public function init()
{
$this->add([
'name' => Form::TEXT,
'required' => false,
'allow_empty' => true,
'continue_if_empty' => true,
'validators' => [
['name' => Text::class],
],
]);
$this->add([
'name' => Form::URL,
'required' => false,
'allow_empty' => true,
'continue_if_empty' => true,
'validators' => [
['name' => Url::class],
],
]);
}
}
That combination should ensure your validators are applied when empty values are encountered.
Rob Allen (#akrabat) wrote a useful blog post detailing the combinations which is worth bookmarking akrabat.com/zend-input-empty-values/

php - Call to a member function getClientOriginalName() on null and remove alert laravel

I am newbie in Laravel so i really need some help. I want to ask when
I commented the part 'photo' => required why if I update without entering the photo it shows some error like call to a member function getClientOriginalName() on null. So the real question is I want to update without entering photo and it should still to be updated.
This is my code in Controller to upload photo
public function update($id, UpdateBannerRequest $request)
{
$input = $request->all();
//get original file name
$filename = Input::file('photo')->getClientOriginalName();
$input['photo'] = $filename;
Input::file('photo')->move($this->path, $filename);
$banner = $this->BannerRepository->findWithoutFail($id);
if (empty($banner)) {
Flash::error('Banner not found');
return redirect(route('banner.index'));
}
$banner = $this->BannerRepository->update($input, $id);
Flash::success('Banner updated successfully.');
return redirect(route('banner.index'));
}
This is the code on my model
<?php
namespace App\Models;
use Eloquent as Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Banner extends Model
{
use SoftDeletes;
public $table = 'banners';
protected $dates = ['deleted_at'];
public $fillable = [
'title',
'description',
'photo',
'status'
];
protected $casts = [
'title' => 'string',
'description' => 'string',
'photo' => 'string',
'status' => 'integer'
];
public static $rules = [
'title' => 'required',
'description' => 'required',
//'photo' => 'required',
'status' => 'required'
];
}
$validator = Validator::make(
$request->all(),
array(
'photo' => 'required',
),
array(
'photo' => 'Please choose file',
)
);
If Photo is not mandatory directly use this
if(!empty($request->photo)){
//do something
}
else{
Flash::error('Banner not provided');
return redirect(route('banner.index'));
}
Hope this will help.. let me know if any issue..Thank you
your update function would look like
public function update($id, UpdateBannerRequest $request)
{
$input = $request->all();
$banner = $this->BannerRepository->findWithoutFail($id);
if(!empty($request->photo)){
//do something for saving the name of file in database and other value respectively using
// $filename = Input::file('photo')->getClientOriginalName();
// $banner->photo = $filename;
}
else{
Flash::error('Banner not provided');
return redirect(route('banner.index'));
}
$banner->save();
Flash::success('Banner updated successfully.');
return redirect(route('banner.index'));
}
The simplest validation required would be to test if Input::hasFile('photo'), this should be placed before you call Input::file('photo')->getClientOriginalName()
if( Input::hasFile('photo') == false )
{
Flash::error('Banner not provided');
return redirect(route('banner.index'));
}
https://laravel.com/docs/4.2/requests#files
You should check bellow code.
if(isset(Input::file('photo'))
Before work with it.

Update `create_at` model attribute only if it is null or blank

I am using YII2 advanced application template with yii2-user.
public function behaviors()
{
return [
TimestampBehavior::className(),
];
}
This will set the current timestamp value in my user model. But I want to add this only if it's null; it should not be overwritten if I set the value in my controller.
You can create your TimestampBehavior with custom logic:
<?php
namespace app\behaviors;
use yii\db\ActiveRecord;
use yii\base\Behavior;
use yii\db\Expression;
class ARTimestampBehavior extends Behavior
{
public function events()
{
return [
ActiveRecord::EVENT_BEFORE_INSERT => 'beforeInsert',
ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeUpdate',
];
}
public function beforeInsert($event)
{
$model = $event->sender;
if ($model->hasAttribute('created_at') && is_null($model->created_at)) {
$model->created_at = new Expression('NOW()');
}
if ($model->hasAttribute('updated_at')) {
$model->updated_at = new Expression('NOW()');
}
}
public function beforeUpdate($event)
{
$model = $event->sender;
if ($model->hasAttribute('updated_at')) {
$model->updated_at = new Expression('NOW()');
}
}
}
And then use it in your model:
public function behaviors()
{
return [
ARTimestampBehavior::className(),
];
}
I don't think there is an easy way to do this. The closest one can get without much coding it to specify a custom value to be set.
public function behaviors()
{
return [
[
'class' => TimestampBehavior::className(),
'value' => function($event) {
return (/* some condition */)
? your_custom_function_returning_the_time()
: time();
],
];
}
That being said, I see this as a potential misuse of TimestampBehavior. One might be better off defining a new column for your custom creation timestamp.
If you are set on using the current column, then ditch TimestampBehavior and overwrite the beforeSave method of your model:
public function beforeSave($insert)
{
if (! parent::beforeSave($insert)) {
return false;
}
if ($insert && this->create_at === null) {
$this->create_at = time();
}
$this->update_at = time();
return true;
}
Just use default TimestampBehavior like this:
/**
* #inheritdoc
* #return array mixed
*/
public function behaviors()
{
return [
'timestamp' => [
'class' => TimestampBehavior::class,
'createdAtAttribute' => 'created_at',
'updatedAtAttribute' => false,
'value' => function($event) {return $event->sender->created_at ?? new \yii\db\Expression('NOW()');},
],
];
}
Yii does have built-in support for preserving already filled values from v2.0.13:
public function behaviors()
{
return [
'timestamp' => [
'class' => \yii\behaviors\TimestampBehavior::class,
'preserveNonEmptyValues' => true,
],
];
}

Unit Testing REST Update with Laravel and Mockery

I can't seem to figure out how I unit test the update of my controller. i'm getting the following error:
method update() from Mockery_0_App.... Should be called exactly 1 times but called 0 times.
After I remove the if statement in the update (after checking if the allergy exists), I get the following error on the line where I add the id the the unique validation rule:
Trying to get property of on object
My Code:
Controller:
class AllergyController extends \App\Controllers\BaseController
{
public function __construct(IAllergyRepository $allergy){
$this->allergy = $allergy;
}
...other methods (index,show,destroy) ...
public function update($id)
{
$allergy = $this->allergy->find($id);
//if ($allergy != null) {
//define validation rules
$rules = array(
'name' => Config::get('Patient::validation.allergy.edit.name') . $allergy->name
);
//execute validation rules
$validator = Validator::make(Input::all(), $rules);
$validator->setAttributeNames(Config::get('Patient::validation.allergy.messages'));
if ($validator->fails()) {
return Response::json(array('status' => false, 'data' => $validator->messages()));
} else {
$allergy = $this->allergy->update($allergy, Input::all());
if ($allergy) {
return Response::json(array('status' => true, 'data' => $allergy));
} else {
$messages = new \Illuminate\Support\MessageBag;
$messages->add('error', 'Create failed! Please contact the site administrator or try again!');
return Response::json(array('status' => false, 'data' => $messages));
}
}
//}
$messages = new \Illuminate\Support\MessageBag;
$messages->add('error', 'Cannot update the allergy!');
return Response::json(array('status' => false, 'data' => $messages));
}
}
TestCase:
class AllergyControllerTest extends TestCase
{
public function setUp()
{
parent::setUp();
$this->allergy = $this->mock('App\Modules\Patient\Repositories\IAllergyRepository');
}
public function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
public function tearDown()
{
parent::tearDown();
Mockery::close();
}
public function testIndex()
{
$this->allergy->shouldReceive('all')->once();
$this->call('GET', 'api/allergy');
$this->assertResponseOk();
}
...Other tests for Index and Show ...
public function testUpdate()
{
$validator = Mockery::mock('stdClass');
Validator::swap($validator);
$input = array('name' => 'bar');
$this->allergy->shouldReceive('find')->with(1)->once();
$validator->shouldReceive('make')->once()->andReturn($validator);
$validator->shouldReceive('setAttributeNames')->once();
$validator->shouldReceive('fails')->once()->andReturn(false);;
$this->allergy->shouldReceive('update')->once();
$this->call('PUT', 'api/allergy/1', $input);
$this->assertResponseOk();
}
}
Config validation rules file:
return array(
'allergy' => array(
'add' => array(
'name' => 'required|unique:Allergy'
),
'edit' => array(
'name' => 'required|unique:Allergy,name,'
),
'messages' => array(
'name' => 'Name'
)
)
);
Is there a way to actually mock the value provided into the validation rule? Or what is the best way to solve this?
I changed my code to this and now it works! :)
$validator = Mockery::mock('stdClass');
Validator::swap($validator);
$allergyObj = Mockery::mock('stdClass');
$allergyObj->name = 1;
$input = array('name' => 'bar');
$this->allergyRepo->shouldReceive('find')->with(1)->once()->andReturn($allergyObj);
$validator->shouldReceive('make')->once()->andReturn($validator);
$validator->shouldReceive('setAttributeNames')->once();
$validator->shouldReceive('fails')->once()->andReturn(false);;
$this->allergyRepo->shouldReceive('update')->once();
$this->call('PUT', 'api/allergy/1', $input);
$this->assertResponseOk();

Categories