How to validate >1 field at a time, in a Zend sub-form? - zend-form

I've created a 3 screen "wizard" using the Zend_Form_SubForm example from the online reference documentation.
The requirement I'm having trouble meeting is this:
If fields 1, 2, & 3 of the first screen are already in the database, notify the user that they are trying to add a duplicate record. Each of those fields has their own validators. Somehow I need to add this "group validator".
So, at its most basic level, I'm trying to do:
if($field_1_not_in_db && $field_2_not_in_db && $field_3_not_in_db){
return true;//validation OK
} else {
return false;//invalid data
}
I am coming up against several issues, though:
1) Because it applies to multiple fields, I don't know which field to attach it to. Error messages appear beside the field they are attached to, so this is important... unless I can get these "multi-field validator" errors to appear at the top of the screen, which would be ideal.
2) My validator is only receiving a single value (the value of the field I attach it to, not the values of the multiple fields it is supposed to validate).
3) I provide a link to the original (non-duplicate) record in the error message, but it escapes the link, and I can't figure out how to get around that.
The setup I'm currently using (below) actually executes fine, but NewPlace validator receives $_POST['city_fk'] as $fields, instead of the desired group of posted values.
$city_fk = new Zend_Form_Element_Select('city_fk');
$cities = array();
$city_fk->setMultiOptions($cities)
->setLabel('City')
->setDescription('The city this place is in')
->setRequired(true);
$v = array(
'place_is_unique' => array(
'NewPlace',
'fields' => array('place_name','phone_number','phone_extension','street','post_code_name'),
)
);
$city_fk->addValidators($v);
$addressSubForm->addElement($city_fk);
class My_Validate_NewPlace extends Zend_Validate_Abstract
{
public function isValid($fields)
{
$result = false;
if(!$result)
{
$this->_error('sorry, this is duplicate data. see it here');
return false;
}
return true;
}
}

This won't help you decide which field to attach the validation to, but...
There is a thing called a "validation context" that helps.
When you create your custom validator or form IF you specify a second optional parameter ($context = null), then Zend will auto-populate this with the entire array of posted data, which you can use to incorporate other fields values into your validation. Here's a very basic example:
$city_name = new Zend_Form_Element_Text('city_name');
$place_name = new Zend_Form_Element_Text('place_name');
$place_name->addValidator('NewPlace');
class My_Validate_NewPlace extends Zend_Validate_Abstract
{
public function isValid($value, **$context = null**)
{
if(trim($value)!='' && trim($context['city_name']) != '')
{
return true;
}
return false;
}
}

Related

How do I display only one validation error in form?

I have several input fields in registration form.
For example, there are 5 fields. 'Email' and "Phone Number" fields are wrong, I do not want to display both validation errors. I only want to check "Email" field and display Email error, if it will be correctly written on second try, only then 'Password' error message can appear.
Can I accomplish it with server-side validation only?
Screenshot: Both validation errors are displayed at the same time.
You can dynamically modify the ModelState and check the errors :
if (ModelState.IsValid)
{
....
}
else
{
var flag = false;
foreach (var modelState in ViewData.ModelState.Values)
{
if (flag)
{
modelState.Errors.Clear();
}
if (modelState.Errors.Count >0)
{
flag = true;
}
if (modelState.Errors.Count>1)
{
var firstError = modelState.Errors.First();
modelState.Errors.Clear();
modelState.Errors.Add(firstError);
}
}
}
return View("index", movie);
Set maximum model validation errors to 1 in stratup, the validation process stops when max number is reached (200 by default):
services.AddMvc(options =>
{
options.MaxModelValidationErrors = 1;
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
but in this case it will stop on the first error even if there is more than one validation error on the same property (e.g. password length is not valid, password must contain upper case letter, etc...).
If you need to show all errors at once for each property you will need another solution.
ref: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-2.2

Validate Form on Change ONLY

Because much of our imported data technically has validation errors, users are unable to update fields without first correcting previously entered bad data. This wouldn't be a problem except that many times this user doesn't have the information needed to enter a correct value into that field but we still need to save their update.
Is it possible to disable the validate on submit for a DynamicForm?
Is it possible to disable the validate on submit for a DynamicForm?
there's a disableValidation attribute, it disables client-side validators.
The best solution I could find thus far.
I'm disabling validation and overridding getValues, which is called as part of saveData so I manually parse through any fields and look for errors. If I find an error I remove it from the return value and store it under the valuesManager.invalidatedFields.
If a field had an error it will not be included in the save, but because the server will return the original value I had to override setValues as well to prevent your (bad) change from being overridden.
Also, because getValues is called on initial load it validates on load as well.
isc.ValuesManager.create({
disableValidation: true,
invalidatedFields: {},
setValues: function(values){
console.log("setting values..", this.invalidatedFields);
for (var key in this.invalidatedFields) {
if (this.invalidatedFields.hasOwnProperty(key)) {
values[key] = this.invalidatedFields[key];
}
}
this.Super("setValues", arguments);
},
getValues: function () {
this.invalidatedFields = [];
var data = this.Super("getValues");
for (var key in data) {
if (data.hasOwnProperty(key)) {
var form = this.getMemberForField(key);
if (form && !form.getField(key).validate()) {
console.log(key + " failed validation", data[key]);
this.invalidatedFields[key] = data[key];
delete data[key];
}
}
}
return data;
}
});

How to get Phalcon to not reload the relation each time I want to access it

I am using Phalcon and have a model Order that has a one-to-many relationship with model OrderAddress. I access those addresses through the following function:
public function getAddresses($params = null) {
return $this->getRelated("addresses", array(
"conditions" => "[OrderAddress].active = 'Y'"
));
}
The OrderAddress model has a public property errors that I do not want persisted to the database. The problem I am having is that everytime I access the getAddresses function, it reloads the object from MySQL which completely wipes the values that I set against that property.
I really only want the OrderAddress models to be loaded once, so that each call to getAddresses doesn't make another trip to the DB- it just iterates over the collection that was already loaded.
Is this possible?
I suppose there's no such option in phalcon, so it has to be implemented in your code.
You could create an additional object property for cached addresses, and return it if it's already been initialized:
protected $cachedAddresses = null;
public function getAddresses($params = null) {
if ($this->cachedAddresses === null) {
$this->cachedAddresses = $this->getRelated("addresses", array(
"conditions" => "[OrderAddress].active = 'Y'"
));
}
return $this->cachedAddresses;
}
This could be a quick solution, but it will be painful to repeat it if you have other relations in your code. So to keep it DRY, you could redefine a 'getRelated' method in base model so it would try to return cached relations, if they already were initialized.
It may look like this:
protected $cachedRelations = [];
public function getRelated($name, $params = [], $useCache = true) {
//generate unique cache object id for current arguments,
//so different 'getRelated' calls will return different results, as expected
$cacheId = md5(serialize([$name, $params]));
if (isset($this->cachedRelations[$cacheId]) && $useCache)
return $this->cachedRelations[$cacheId];
else {
$this->cachedRelations[$cacheId] = parent::getRelated($name, $params);
return $this->cachedRelations[$cacheId];
}
}
Then, you can leave 'getAddresses' method as is, and it will perform only one database query. In case you need to update cached value, pass false as a third parameter.
And, this is completely untested, but even if there're any minor errors, the general logic should be clear.

Yii Many to Many Relational Query

Using a many many relational query with users having many clients and clients having many users. Trying to view a record of a particular client for a particular user. And if that client is not associated with that user, redirect to a different page.
// the relation in the client model
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'owners'=>array(self::MANY_MANY, 'User','owner_client(owner_id, client_id)'),
);
}
//the relation in the user model
public function relations()
{
return array(
'clients'=>array(self::MANY_MANY, 'Clients','owner_client(owner_id, client_id)'),
);
}
//determine if user can view this client
//client record
$client_record = Clients::model()->findByPk($id);
//many query to find users
$users = $client_record->owners;
//if user id is not found in array, redirect
if (!in_array(Yii::app()->user->id, $users))
{
$this->redirect(array('/site/dashboard'));
}
The above code redirects, even though I know the client is related to the user logged in
When you call $users = $client_record->owners;, what you're getting back is an array of all your user models that are associated with the current client. As a result, you're comparing integers to objects, which means your in_array() condition will always fail.
What I recommend is that you build a conditional query to do your verification check. Something like this should work:
$model = Clients::model()->with(
array(
'owners'=>array(
'select'=>'owner_id',
'condition'=>'user.id = '.Yii::app()->user->id,
),
)
)->findByPk($id);
if ($model === null) {
$this->redirect(array('/site/dashboard'));
}

Saving data with cakephp won't work

I'm trying to load, edit and save a record with CakePHP 2.0 but I get a generic error during the save method that don't help me to understand where is the problem.
if I try with debug($this->User->invalidFields()); I get an empty array, but I get false from $this->User->save() condition.
Here is the controller action where I get the error:
public function activate ($code = false) {
if (!empty ($code)) {
// if I printr $user I get the right user
$user = $this->User->find('first', array('activation_key' => $code));
if (!empty($user)) {
$this->User->set(array (
'activation_key' => null,
'active' => 1
));
if ($this->User->save()) {
$this->render('activation_successful');
} else {
// I get this error
$this->set('status', 'Save error message');
$this->set('user_data', $user);
$this->render('activation_fail');
}
debug($this->User->invalidFields());
} else {
$this->set('status', 'Account not found for this key');
$this->render('activation_fail');
}
} else {
$this->set('status', 'Empty key');
$this->render('activation_fail');
}
}
When I try the action test.com/users/activate/hashedkey I get the activation_fail template page with Save error message message.
If I printr the $user var I get the right user from cake's find method.
Where I'm wrong?
I think the problem may be in the way you're querying for the User record. When you do this:
$user = $this->User->find('first', array('activation_key' => $code));
The variable $user is populated with the User record as an array. You check to ensure it's not empty, then proceed; but the problem is that $this->User hasn't been populated. I think if you tried debug($this->User->id) it would be empty. The read() method works the way you're thinking.
You could try using the ID from that $user array to set the Model ID first, like so:
if (!empty($user)) {
$this->User->id = $user['User']['id']; // ensure the Model has the ID to use
$this->User->set(array (
'activation_key' => null,
'active' => 1
));
if ($this->User->save()) {
...
Edit: Well another possible approach is to use the $user array instead of modifying the current model. You said that you get back a valid user if you debug($user), so if that's true you can do something like this:
if (!empty($user)) {
$user['User']['activation_key'] = null;
$user['User']['active'] = 1;
if ($this->User->save($user)) {
...
This method works in the same way as receiving form data from $this->request->data, and is described on the Saving Your Data part of the book.
I'm curious though if there's another part of your setup that's getting in the way. Can other parts of your app write to the database properly? You should also check to make sure you aren't having validation errors, like their example:
<?php
if ($this->Recipe->save($this->request->data)) {
// handle the success.
}
debug($this->Recipe->validationErrors);