I want to validate a form which holds a dropdown menu and a text input field.
The user can choose a project from the dropdown menu. If he wants to create a new project he can use the text input field next to the dropdown menu.
Here is my upload type:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setAction('upload')
->setMethod('POST')
// project name dropdown menu
->add('projectname', 'choice' , array(
'label' => 'upload_project_label',
'choices' => $this->projects,
'attr' => array(
'class' => 'form-control some',
'required' => 'true'
)
))
// newprojectname text input
->add('newprojectname', 'text', array(
'label' => false,
'attr' => array(
'class' => 'form-control',
'required' => false,
'placeholder' => 'upload_newprojectname_placeholder'
)
)
)
...
And this is a snippet from my upload entity:
/**
* #ORM\Column(type="text")
*
* #var string $projectname
* #Assert\NotBlank()
*/
protected $projectname;
/**
* #ORM\Column(type="text")
*
* #var string $newprojectname
* #Assert\Length(
* min = 3,
* max = 7,
* minMessage = "min message",
* maxMessage = "max message"
* )
*/
protected $newprojectname;
My question is is there a possibility of a query to check if the field newproject is set (i.e. a string is entered)? And if so let the Assert annotation do its job.
This can be done a number of ways, all of which will likely satisfy your requirements.
Use a custom callback - this is the quickest and most straightforward
Use an expression validator - a lot of people have issues with embedding a meta-language within PHP which is perfectly valid but this is another quick-ish way of doing things
Use group sequences, specifically the group sequence provider functionality
Which one you choose is up to you but callbacks are a quick and easy starting point that you can build on if your validation constraints become more complex.
This is the code block for advised solution as custom callback validation.
I had to add another function in my upload entity which looks like this:
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* Function holds custom validation for fields in import/upload form
* #Assert\Callback
* #param ExecutionContextInterface $context
*/
public function validate(ExecutionContextInterface $context)
{
// check new project name
$newProjectName = $this->getNewprojectname();
if(!empty($newProjectName)) {
// letters only
$pattern = '/[a-zA-Z]/';
if($this->isPatternWrong($newProjectName, $pattern)) {
$context
->buildViolation('Please use letters only.')
->atPath('newprojectname')
->addViolation();
}
// text max. 7 digits
$maxlength = 7;
if($this->isStringTooLong($newProjectName, $maxlength)) {
$context
->buildViolation('Max. length 7 digits.')
->atPath('newprojectname')
->addViolation();
}
}
}
private function isPatternWrong($string, $pattern)
{
$result = preg_match($pattern, $string);
if($result === 0) {
return true;
}
return false;
}
private function isStringTooLong($string, $length)
{
if(strlen($string) > $length) {
return true;
}
return false;
}
Related
<?php
namespace frontend\controllers;
use Yii;
use common\models\Subscriber;
use common\models\SubscriberSearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
/**
* SubscriberController implements the CRUD actions for Subscriber model.
*/
class SubscriberController extends Controller
{
/**
* Creates a new Subscriber model.
* If creation is successful, the browser will be redirected to the 'view' page.
* #return mixed
*/
public function actionSubscribe()
{
$model = new Subscriber();
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
if ($model->sendEmail()) {
Yii::$app->session->setFlash('success', 'You have successfully subscribed My-Blog. You will get notification whenever New post is published');
return $this->goHome();
} else {
Yii::$app->session->setFlash('error', 'Sorry, we are unable to subscribe for the provided email address.');
}
}
return $this->render('create', [
'model' => $model,
]);
}
/**
* Finds the Subscriber model based on its primary key value.
* If the model is not found, a 404 HTTP exception will be thrown.
* #param integer $id
* #return Subscriber the loaded model
* #throws NotFoundHttpException if the model cannot be found
*/`enter code here`
}
using following model :
<?php
namespace common\models;
use Yii;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\db\Expression;
/**
* This is the model class for table "subscriber".
*
* #property int $id
* #property string $email
* #property string $token
* #property int $status
* #property int $created_at
* #property int $updated_at
*/
class Subscriber extends \yii\db\ActiveRecord
{
const STATUS_DEACTIVE = 0;
const STATUS_ACTIVE = 1;
/**
* #inheritdoc
*/
public static function tableName()
{
return 'subscriber';
}
public function behaviors()
{
return [
'timestamp' => [
'class' => TimestampBehavior::className(),
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
],
'value' => new Expression('NOW()'),
],
];
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['email'], 'required'],
[['status', 'created_at', 'updated_at'], 'integer'],
[['email'], 'string', 'max' => 60],
[['token'], 'string', 'max' => 255],
[['token'], 'unique'],
[['email'], 'unique', 'targetClass' => '\common\models\Subscriber', 'message' => 'This email has already subscribed our blog.','filter' => ['!=','status' ,0]],
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'email' => 'Email',
'token' => 'Token',
'status' => 'Status',
'created_at' => 'Created At',
'updated_at' => 'Updated At',
];
}
/**
* Generates subscriber token
*/
public function generateSubscriberToken()
{
return $this->token = Yii::$app->security->generateRandomString() . '_' . time();
}
/**
* Send Email when successfully subscribe
*/
public function sendEmail()
{
$subscribers = Subscriber::find()->where(['email' => $this->email, 'status' => 0,])->one();
if(!$subscribers)
{
$this->generateSubscriberToken();
if(!$this->save())
{
return false;
}
return Yii::$app->mailer
->compose()
->setFrom(['noreply#my-blog.com' => Yii::$app->name . ' robot'])
->setTo('piyush#localhost')
->setSubject('Subscription : ' . Yii::$app->name)
->setHtmlBody('Thank you '.$this->email.' for subscribing '.Yii::$app->name.'<br /><br /> You will receive notification whenever new trick or post is published to website')
->send();
}
$subscribers->generateSubscriberToken();
$subscribers->status = 1;
if(!$subscribers->save())
{
return false;
}
return Yii::$app->mailer
->compose()
->setFrom(['noreply#my-blog.com' => Yii::$app->name . ' robot'])
->setTo('piyush#localhost')
->setSubject('Subscription : ' . Yii::$app->name)
->setHtmlBody('Welcome back '.$this->email.'Thank you for subscribing '.Yii::$app->name.'<br /><br /> You will receive notification whenever new trick or post is published to website')
->send();
}
}
This controller and model are being used to make subscribe activity using email. I want that IF a user has unsubscribed and after some time again want to subscribe then update status = 1 and regenerate token. Above sendEmail is working fine if it's a new subscriber but if it is an old subscriber with status 0 then not working.
Above all, you need to replace the lines
$subscribers->generateSubscriberToken();
$subscribers->status = 1;
with
$subscriber->token =$this->generateSubscriberToken();
$subscribers->status = 1;
as in your function you are setting $this->token and returning it and to update the record you need to set the $subcribers->token filed with the value.
And you should not search the table for the email with status 0 just query the email and check in PHP if status ==0 because a new record should only be entered if the email does not exist, without caring what the status field has so in your case if the email exists but with status =1 your query won't fetch the record and it will try to insert a record instead of doing nothing.
To understand you can try using var_dump(!$subscribers) in both cases an see what it returns.
Moreover, you are repeating things like sending email and token generation you should change your function to the below.
public function sendEmail()
{
$subscribers = self::find()->where(['email' => $this->email])->one();
//set flag for sending email
$sendMail = false;
//email subject
$subject = '';
//generate token
$token = $this->generateSubscriberToken();
//if email found in subscribers
if ($subscribers !== null) {
//check if inactive
if ($subscribers->status !== self::STATUS_ACTIVE) {
//assign token
$subscribers->token = $token;
//set status to active
$subscribers->status = self::STATUS_ACTIVE;
//update the recrod
if (!$subscribers->save()) {
return false;
}
//set subject
$subject = 'Welcome back ' . $this->email . 'Thank you for subscribing ' . Yii::$app->name . '<br /><br /> You will receive notification whenever new trick or post is published to website';
$sendMail = true;
}
} else { //if email does not exist only then insert a new record
$this->status = 1;
if (!$this->save()) {
return false;
}
$subject = 'Thank you ' . $this->email . ' for subscribing ' . Yii::$app->name . '<br /><br /> You will receive notification whenever new trick or post is published to website';
$sendMail = true;
}
//check if send mail flag set
if ($sendMail) {
return Yii::$app->mailer
->compose()
->setFrom(['noreply#my-blog.com' => Yii::$app->name . ' robot'])
->setTo('piyush#localhost')
->setSubject('Subscription : ' . Yii::$app->name)
->setHtmlBody($subject)
->send();
}
}
In my Admin I'v defined this:
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
/* ... */
->add('camps', 'sonata_type_model', array(
'btn_add' => false, //Specify a custom label
'choices' => $this->tema_service->getCampsDefinicio($this->getSubject()),
'sortable' => true,
'multiple' => true,
), array(
'placeholder' => 'No selected'
))
;
/* ... */
}
Function getCampsDefinicio returns an array of items ordered. Inspite of it, the options appear sorted according its id.
What can I do to get it respect the order given?
It does not care if I have to override any template. In that case which files I have to look for/override?
Finally I solved this.
I was a little confused.
The order in the choises array is respected only for selectionable items.
The order for the SELECTED items is defined by te ArrayCollection provided in the entity function
/**
* Get camps
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCamps()
{
return $this->camps;
}
I, i have to add an Assert to an atribute when other atribute is equal than something. Like this:
/**
* #Assert\Callback(methods={"isChildMinor",)
*/
class PatientData
{
/**
* #Assert\Date()
*/
public $birthday;
public $role;
public function isChildMinor(ExecutionContext $context)
{
if ($this->role == 3 && check #assert\isMinor() to $birtday) {
=>add violation
}
}
so, i want check if the patient is minor (with assert or somethings else) if the role is equal than 3. How do this?
There are several ways to do, what you want.
1) You could make it right in the form. Like that:
use Symfony\Component\Validator\Constraints as Assert;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$yourEntity = $builder->getData();
//here you start the field, you want to validate
$fieldOptions = [
'label' => 'Field Name',
'required' => true,
];
if ($yourEntity->getYourProperty != 'bla-bla-bla') {
$fieldOptions[] = 'constraints' => [
new Assert\NotBlank([
'message' => 'This is unforgivable! Fill the field with "bla-bla-bla" right now!',
]),
],
}
$builder->add('myField', TextType::class, $fieldOptions);
2) Other way - is to make your custom validation callback in your Entity and play with direct asserts there. It's possible, I think.
3) But the optimal way, from my point of view - is to use several asserts with validation groups. You need to specify Assert\isMinor(groups={"myCustomGroup"}) on birthday field. And then, in your form:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'validation_groups' => function (FormInterface $form) {
$yourEntity = $form->getData();
if ($yourEntity->role !== 3) {
return ['Default', 'myCustomGroup'];
}
return ['Default'];
},
Hope this'll be helpful for you.
There is a complex form with a lot of nested fieldsets. Some fields need to be validated depending on field(-s) in another fieldset. So I cannot define all the validation rules directly in the getInputFilterSpecification() of the Fieldset, since there I cannot access other fieldsets (only the sub-fieldsets). The only way to do this is to extend the Form validation. Right? If so, how to do this?
MyForm extends Form
{
public function isValid()
{
$isFormValid = parent::isValid();
$isFieldXyzValid = // my additional validation logic
if(! $isFieldXyzValid) {
$fieldXyz->setMessages(...);
}
return $isFormValid && $isFieldXyzValid;
}
}
Like this? Or is a cleaner way to solve this problem?
I've already developed something similar in my previous project.
For doing this i used a service which take my form and set dynamic fieldset, and obviously custom validation rules.
In your controller, get your form (via the dependancy injection formManager (polyfilled or not).
$form = $this->formManager->get('{your form}');
Call your service and give it your form.
And in your service you can do anything you want like :
get your stuff (from DB or others) to determine wich fields are mandatory
Foreach on your form
Add or remove fieldset
Add or remove validationGroup fields
Add or remove filters
Add or remove validators
I performed those via (sample) in a foreach where $stuff is an element of doctrine collection
$nameFieldset = 'my_fieldset-'.$stuff->getId();
$globalValidator = new GlobalValidator();
$globalValidator->setGlobalValue($gloablValue);
$uoValidator = new UcValidator();
$uoValidator->setOptions(array(
'idToValidate' => $stuff->getId(),
'translator' => $this->translator
));
$globalValidator->setOptions(array(
'idToValidate' => $stuff->getId(),
'translator' => $this->translator
));
$name = 'qty-'.$stuff->getId();
$form = $this->setFilters($form, $name, $nameFieldset);
$globalValidator->setData($data);
$form = $this->setValidators($form, $name, $globalValidator, $uoValidator, $nameFieldset);
Where setFilters and setValidators are custom methods wich add filters and validator to my fields (also custom)
/**
* #param myForm $form
* #param $name
* #param string $nameFieldset
* #return myForm
*/
public function setFilters($form, $name, $nameFieldset)
{
$form->getInputFilter()->get('items')->get($nameFieldset)
->get($name)
->getFilterChain()
->getFilters()
->insert(new StripTags())
->insert(new StringTrim());
return $form;
}
/**
* #param myForm $form
* #param $name
* #param $globalValidator
* #param $uoValidator
* #param $nameFieldset
* #return myForm
*/
public function setValidators($form, $name, $globalValidator, $uoValidator, $nameFieldset)
{
$optionsSpace = [
'translator' => $this->translator,
'type' => NotEmpty::SPACE
];
$optionsString = [
'translator' => $this->translator,
'type' => NotEmpty::STRING
];
$optionsDigits = [
'translator' => $this->translator,
];
$form->getInputFilter()->get('items')
->get($nameFieldset)
->get($name)
->setRequired(true)
->getValidatorChain()
->attach($uoValidator, true, 1)
->attach($globalValidator, true, 1)
// We authorize zéro but not space nor strings
->attach(new NotEmpty($optionsSpace), true, 2)
->attach(new NotEmpty($optionsString), true, 2)
->attach(new Digits($optionsDigits), true, 2);
return $form;
}
What is the best way to validate a number range based on the value of another form element? If the user selects "percentage" as a discount type, the discount amount should be between 0 and 100, and not 140! The problem seems to be passing in another form element value.
Also, I've viewed other resources, one dealing with a similar topic, but perhaps not all the way relevant.
How to validate a field of Zend_Form based on the value of another field?
Form.php
$isValid = new Application_Model_Validate();
$discount = $this->createElement('text', 'discount')
->setLabel('Discount Amount')
->setDescription("Enter an amount in the format \"200.00\" ")
->setRequired(true)
->setDecorators(array('Description', 'ViewHelper', 'Errors',
array('HTMLTag', array('tag' => 'dd')),
array('Label', array('tag' => 'dt'))));
$discount->addValidators(array(array('Float')), $isValid->isValid(new Zend_Validate_Between(array('min' => '0', 'max' => '100')), $discountType));
$this->addElement($discount);
Application_Model_Validate.php
Require_once 'Zend/Validate/Abstract.php';
class Application_Model_Validate extends Zend_Validate_Abstract
{
/*
* Validation failure message key
*/
const INVALID_PERCENTAGE = 'InvalidPercentage';
/*
* Validation failure message template definitions
*/
protected $_messageTemplates = array(
self::INVALID_PERCENTAGE => 'Please enter a percentage greater than 0 and up to 100.'
);
protected $_percentageOption;
protected $_percentageValue;
/*
* Defined by Zend_Validate_Interface
* Validate the percentage parameters
*/
public function isValid($value, $context = null)
{
$this->_setValue($value);
/*
* If context key is valid, return true
*/
if(is_array($context))
{
if (isset($context['percentage']) && ($value))
{
return true;
}
}
$this->_error(self::INVALID_PERCENTAGE);
return false;
}
If you need anymore information, just say.
I modified your code a bit and added a drop down box:
Form:
$this->addElement('select', 'discounttype');
$this->getElement('discounttype')
->addMultiOptions(
array('percentage' => 'percentage', 'other' => 'other')
);
$discount = $this->createElement('text', 'discount')
->setLabel('Discount Amount')
->setRequired(true);
$discount->addValidators(
array('Float', new Application_Model_Validate(0, 140))
);
$this->addElement($discount);
Validator:
<?php
class Application_Model_Validate extends Zend_Validate_Between
{
public function isValid($value, $context = null)
{
$this->_setValue($value);
if ($context['discounttype'] == 'percentage') {
$this->setMax(100);
}
return parent::isValid($value, $context);
}
}
Now, if you validate the form in your controller using $form->isValid($this->getRequest()->getParams()), it will take an input between 0 and 100 if in the drop down box 'percentage' is selected and an input between 0 and 140 otherwise.
An alternative solution:
//controller validation
$discountValidate = new Application_Model_Validate();
$discountValidate->_checkDiscount($data['discountType'], $data['discount']);
if ($discountValidate->isValid())
{
//do
}
else
{
$element = $form->getElement('discount');
$element->addError($discountValidate->getError());
}