So here's the deal. The ActiveForm below is supposed to receive two variables: $tt (radio) and $value (string numeric input) and send them to controller.
$value needs to be validated based on different rules and scenarios, which are defined in InputField model. $tt is radio value, so I'm not defining rules for it as of now.
The trick is, I'd like there to be some sort of input validation for $value, and the rules for it should change depending on what the user set as his $tt value. With this kind of configuration, controller never receives neither $tt, nor $value, I think the reason is conflicting rules for it.
Ideally, I would like my view to send checked radio $tt value (with jQuery or smthng) to controller, and for controller then to set scenario for the model so it has proper validation rules set up, which input field for $value in the view will operate on.
Yii2 experts: 1) can you explain me why my controller fails to receive any value. 2)suggest anything for what I was planning to do?
`
//View fragment
$valueForm = ActiveForm::begin([
'id' => 'input-field',
'action' => ['value-search/index'],
'method' => 'post',
'options' => ['class' => 'form-horizontal'] ]); ?>
<?= $valueForm->field($valueModel, 'tt')
->radioList(['P_sat'=>'Sat. Pressure', 'T_sat'=>'Sat. Temperature'])
->label('Select table type'); ?>
<?= $valueForm->field($valueModel, 'value')
->textInput(['options' => ['type'=>'number', 'name'=>'value','id'=>'value']]); ?>
<?php ActiveForm::end() ?>
//Model
class InputField extends \yii\base\Model
{
const SCENARIO_PSAT = 'P_sat';
const SCENARIO_TSAT = 'T_sat';
public $tt;
public $value;
public function scenarios() {
return [
self::SCENARIO_PSAT => ['tt','value'],
self::SCENARIO_TSAT => ['tt','value'],
];
}
public function rules() {
return [
['tt','safe'],
['value', 'double', 'numberPattern' => '/[0-9]+?(\.[0-9]{0,5})?/', 'min' => 0, 'max' => 22064,
'on' => self::SCENARIO_PSAT],
['value', 'double', 'numberPattern' => '/[0-9]+?(\.[0-9]{0,5})?/', 'min' => 0, 'max' => 373.95,
'on' => self::SCENARIO_TSAT],
];
}
//Operating action from the controller
public function actionIndex()
{
$inputModel = new InputField;
$inputModel->load(\Yii::$app->request->post());
$tt = $inputModel->tt;
switch($tt) {
case 'P_sat': $inputModel->scenario = $inputModel::SCENARIO_PSAT;
break;
case 'T_sat': $inputModel->scenario = $inputModel::SCENARIO_TSAT;
break;
default: throw new UserException('setTableType is not returning shit');
}
if ($inputModel->validate('value')) {
$searchValue = $inputModel->value;
$this->test = $inputModel->tt;
$this->test2 = $inputModel->value;
} else {
throw new UserException('Input is not validated');
}
return $this->render('index', [
//'provider' => $provider,
'array' => $this->test,
'array2' => $this->test2
]);
}
`
Both test and test2, which are supposed to receive $tt and $value from the model, are NULL :(
Scenario has to be set before loading a value
public function actionIndex()
{
$inputModel = new InputField;
$inputModel->load(\Yii::$app->request->post());
$tt = $inputModel->tt;
switch($tt) {
case 'P_sat': $inputModel->scenario = $inputModel::SCENARIO_PSAT;
break;
case 'T_sat': $inputModel->scenario = $inputModel::SCENARIO_TSAT;
break;
default: throw new UserException('setTableType is not returning shit');
}
if ($inputModel->load(\Yii::$app->request->post()) && $inputModel->validate('value')) {
$searchValue = $inputModel->value;
$this->test = $inputModel->tt;
$this->test2 = $inputModel->value;
} else {
throw new UserException('Input is not validated');
}
return $this->render('index', [
//'provider' => $provider,
'array' => $this->test,
'array2' => $this->test2
]);
}
Related
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;
}
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/
I am trying to implement the 2amigos SelectizeDropDownList widget in a form to add new values to a table directly within the dropdown.
I am using the model Book and the Model Author so basically want to be able to add a new author in the book form.
This is the book controller at the update function:
public function actionUpdate($id) {
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['index']);
} else {
return $this->render('update', [
'model' => $model,
'categories' => BookCategory::find()->active()->all(),
'publishers' => Publisher::find()->all(),
'copirights' => Copiright::find()->all(),
'authors' => Author::find()->all(),
]);
}
}
This is the form:
<?=
$form->field($model, 'author_id')->widget(SelectizeDropDownList::className(), [
// calls an action that returns a JSON object with matched
// tags
'loadUrl' => ['author/list'],
'value' => $authors,
'items' => \yii\helpers\ArrayHelper::map(\common\models\author::find()->orderBy('name')->asArray()->all(), 'id', 'name'),
'options' => [
'class' => 'form-control',
'id' => 'id'
],
'clientOptions' => [
'valueField' => 'id',
'labelField' => 'name',
'searchField' => ['name'],
'autosearch' => ['on'],
'create' => true,
'maxItems' => 1,
],
])
?>
And this is the function author controller:
public function actionList($query) {
$models = Author::findAllByName($query);
$items = [];
foreach ($models as $model) {
$items[] = ['id' => $model->id, 'name' => $model->name];
}
Yii::$app->response->format = \Yii::$app->response->format = 'json';
return $items;
}
The form works fine to load, filter, search and add new items.
But it is not inserting the new typed attribute in the author table.
Do I need to add something in the book controller?
How can I check if it is a new value or a change of an existing author?
Thanks a lot
I made it work with the following code, not sure the most elegant because i am checking the if the author_id is a number or a string.
In my case the author won't be a number anyway.
public function actionUpdate($id) {
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post())) {
$x = Yii::$app->request->post('Book');
$new_author = $x['author_id'];
if (!is_numeric($new_author)) {
$author = new Author();
$author->name = $new_author;
$author->save();
$model->author_id = $author->id;
}
if ($model->save()) {
return $this->redirect(['index']);
}
} else {
return $this->render('update', [
'model' => $model,
'categories' => BookCategory::find()->active()->all(),
'publishers' => Publisher::find()->all(),
'copirights' => Copiright::find()->all(),
'authors' => Author::find()->all(),
]);
}
}
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();
On my bootstrap I don't have a class, it's a simple php file:
I have added there:
$loader = Zend_Loader_Autoloader::getInstance ();
$loader->setFallbackAutoloader ( true );
$loader->suppressNotFoundWarnings ( false );
//resource Loader
$resourceLoader = new Zend_Loader_Autoloader_Resource(array(
'basePath' => APPLICATION_PATH,
'namespace' => '',
));
$resourceLoader->addResourceType('validate', 'validators/', 'My_Validate_');
$loader->pushAutoloader($resourceLoader);
Then, in application/validators I have:
class My_Validate_Spam extends Zend_Validate_Abstract {
const SPAM = 'spam';
protected $_messageTemplates = array(
self::SPAM => "Spammer"
);
public function isValid($value, $context=null)
{
$value = (string)$value;
$this->_setValue($value);
if(is_string($value) and $value == ''){
return true;
}
$this->_error(self::SPAM);
return false;
}
}
In my form constructor I have:
$this->addElement(
'text',
'honeypot',
array(
'label' => 'Honeypot',
'required' => false,
'class' => 'honeypot',
'decorators' => array('ViewHelper'),
'validators' => array(
array(
'validator' => 'Spam'
)
)
)
);
And finally on my view I have:
<dt><label for="honeypot">Honeypot Test:</label></dt>
<dd><?php echo $this->form->honeypot;?></dd>
Despite all this, I receive my form data, either by filling or not filling that text field.
What am I missing here ?
Thanks a lot in advance.
Thats expected behaviour. $honeypot is a form-element. Now, let's say you have a form $hp_form where $honeypot is one of the elements assigned.
Now, in your controller simply use something like:
if ($hp_form->isValid($this->getRequest()->getPost())) {
// do something meaningful with your data here
}
Probably you also want to check, if you display the form for the first time or if the user submitted the form:
if ($this->getRequest()->isPost() &&
false !== $this->getRequest()->getPost('submit_button', false)) {
if ($hp_form->isValid($this->getRequest()->getPost())) {
// do something meaningful with your data here
}
}
...assuming that your submit button has the id 'submit_button'.
Hope this helps
Bye,
Christian
replace :
if (is_string($value) and $value == ''){
return true;
}
by :
if (strlen($value) > 0)
{
return true;
}