Symfony2 form validation based on two fields - forms

I am currently developing a Website in which user may buy gift cards. I am using a three step form using the CraueFormFlow bundle and everything is concerning the steps. I am able to validate every simple Assert (like not blank, email, repeated fields, etc) but I am facing the situation where, user may select 0 gift cards and proceed to the next page.
The users may choose the quantity of giftcards they want to buy using two separate : one for 25$ gift cards and one for 50$ gift cards. So I can't just put a validator saying "value 0 is not allowed". The validator must prevent a user from leaving the quantity "0" in both amount (25$ and 50$).
Does anyone know how to make a custom validation looking for the values in two fields?
Thanks in advance!

You have many solutions for this.
The easiest one is to add a Callback constraint to your model class.
Another way to do it would be to create your custom constraint and its associated validator. You have a cookbook explaining how to create a custom validation constrain.
This is the best approach to do it.
As your constraint does not apply to a property but to a class, you must specify it overriding the the ->getTargets() method of your constraint class:
class MyConstraint extends Constraint
{
// ...
public function getTargets()
{
return Constraint::CLASS_CONSTRAINT;
}
}
So the value passed as $value argument of the ->isValid() method will contain values of the whole class and not only of a single property.

When you don't have a data class attached to your form you can implement dependent constraints in forms like this:
$startRangeCallback = function ($object, ExecutionContextInterface $context) use ($form)
{
$data = $form->getData();
$rangeEnd = $data['range_end'];
if($object && $rangeEnd){
if ($object->getTimestamp() > $rangeEnd->getTimestamp()) {
$context->addViolation('Start date should be before end date!', array(), null);
}
}
};
$form->add('range_start', 'bootstrap_datepicker', array(
'format' => 'dd-MM-yyyy',
'required' => false,
'attr' => array('class' => "col-xs-2"),
'calendar_weeks' => true,
'clear_btn' => true,
'constraints' => array(
new Callback(array($startRangeCallback)),
)
)
);
$form->add('range_end', 'bootstrap_datepicker', array(
'format' => 'dd-MM-yyyy',
'required' => false,
'attr' => array('class' => "col-xs-2"),
'calendar_weeks' => true,
'clear_btn' => true,
)
);

This is how I've done this in my validation constraints, to check credit card validity with expiration month and year properties.
In this class, I check the value of expirationYear property and compare it with value of expirationMonth property got from contextObject.
/**
* Method to validate
*
* #param string $value Property value
* #param \Symfony\Component\Validator\Constraint $constraint All properties
*
* #return boolean
*/
public function validate($value, Constraint $constraint)
{
$date = getdate();
$year = (string) $date['year'];
$month = (string) $date['mon'];
$yearLastDigits = substr($year, 2);
$monthLastDigits = $month;
$otherFieldValue = $this->context->getRoot()->get('expirationMonth')->getData();
if (!empty($otherFieldValue) && ($value <= $yearLastDigits) &&
($otherFieldValue <= $monthLastDigits)) {
$this->context->addViolation(
$constraint->message,
array('%string%' => $value)
);
return false;
}
return true;
}
Of course, you have to authorize class and properties constraints in your getTargets method, form the main constraint file.
/**
* Get class constraints and properties
*
* #return array
*/
public function getTargets()
{
return array(self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT);
}
Further explanations and complete tutorial here: http://creativcoders.wordpress.com/2014/07/19/symfony2-two-fields-comparison-with-custom-validation-constraints/

Use Regular expression inorder to prevent Zero
In your Entity class write down the below override function , and specify your property which you need to validate.
The below example is for validating a pincode ,here in pincode field I admit only numbers 0-9 combinations upto 10 digits .
" ^\d+$ " this is the regular expression I used to prevent other characters.
For overriding this function you must include the below classes
use Symfony\Component\Validator\Mapping\ClassMetadata;// for overriding function loadValidatorMetadata()
use Symfony\Component\Validator\Constraints\NotBlank;// for notblank constrain
use Symfony\Component\Validator\Constraints\Email;//for email constrain
use Symfony\Component\Validator\Constraints\MinLength;// for minimum length
use Symfony\Component\Validator\Constraints\MaxLength; // for maximum length
use Symfony\Component\Validator\Constraints\Choice; // for choice fields
use Symfony\Component\Validator\Constraints\Regex; // for regular expression
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('pincode', new NotBlank(array('message' => 'Does not blank')));
$metadata->addPropertyConstraint('pincode', new Regex(array('pattern'=>'/^\d+$/','message' => 'must be number')));
$metadata->addPropertyConstraint('pincode', new MaxLength(array('limit'=>'6','message' => 'must maximum 6 digits')));
$metadata->addPropertyConstraint('pincode', new MinLength(array('limit'=>'6','message' => 'must minimum 6 digits')));
}
Not forget these all must
included in your Entity class
that you have to validate. So in your case use a proper regular expression which does not permit '0'.
Happy coding

I'd suggest using Expression constraint. This constraint can be applied on form field or (preferably) in entity:
/**
* #var int
* #Assert\Type(type="integer")
*/
private $amountGiftCards25;
/**
* #var int
* #Assert\Type(type="integer")
* #Assert\Expression(expression="this.getAmountGiftCards25() > 0 or value > 0", message="Please choose amount of gift cards.")
*/
private $amountGiftCards50;

Related

Validation for Rest Api in Symfony 4

I'm going to write REST API for my project. I'm using symfony 4. I saw several examples, but non of them fit me.
Validation with Form object. It doesn't work for me, because it's API, there are no forms. I don't want to write dummy classes just to support this functionality.
On this page https://symfony.com/doc/current/validation.html they suggest 4 ways: Annotation, yml, xml, php. This solution doesn't fit me because this validation is related to the entity, API - is much mode wider: it has limit, offset, filters and other fields, that doesn't belong to an entity.
So, I think I need to write validator which has an array of constraints for all possible fields. I just don't know what is the best way to present this. Have you ever seen something similar?
P.S. Before writing this post I used stackoverflow search. I didn't find useful answers.
Looking at your example (example.com/api/categories?limit=20&offset=300&filter=something) I guess your action would look something like this:
public function getCategories(?int $limit, ?int $offset, ?string $filter)
{
//...
}
Collection validation
You can define your constraints as an array (and later abstract it away into its own class), and pass it as the second argument to your validator.
$constraint = new Assert\Collection([
'limit' => [
new Assert\Range(['min' => 0, 'max' => 999]),
new Assert\DivisibleBy(0.5)
],
'offset' => new Assert\Range(['min' => 0, 'max' => 999]),
'filter' => new Assert\Regex("/^\w+/")
]);
$validationResult = $this->validator->validate(
['limit' => $limit, 'offset' => $offset, 'filter' => $filter],
$constraint
);
Documentation link.
Validate one by one
Pass the constraint to the validator as second argument, for every parameter you want to validate.
$offsetValidationResult = $this->validator->validate(
$offset,
new Assert\Range(['min' => 0, 'max' => 999])
);
//...
Documentation link.
Object validation
Create a class with the 3 fields in it.
class FilterParameters
{
public function __construct($limit, $offset, $filter)
{
$this->limit = $limit;
$this->offset = $offset;
$this->filter = $filter;
}
// No getters/setters for brevity
/**
* #Assert\DivisibleBy(0.25)
*/
public $limit;
/**
* #Assert\Range(min = 0, max = 999)
*/
public $offset;
/**
* #Assert\Regex("/^\w+/")
*/
public $filter;
}
Instantiate and validate it.
$validationResult = $this->validator->validate(
new FilterParameters($limit, $offset, $filter)
);
Documentation link.
I think to use forms as usual is the very clean and nice.
https://codereviewvideos.com/course/beginners-guide-back-end-json-api-front-end-2018/video/validating-json-data-symfony
I choose this api, because it was the fastest in my tests.
You do not have to buy the course (but you might if you like the code), just follow the "raw symfony 4" articles in this series (you also dont need the behat part)
"Limit", "offset" and "filter" functionality belongs to your repositories. Same way as you pass the id here to the repository
/**
* Class AlbumController
* #package App\Controller
*/
class AlbumController extends AbstractController
{
// ....
/**
* #Route(
* path = "/api/album/{id}",
* name = "get_album",
* methods = {"GET"},
* requirements = {"id"="\d+"}
* )
* #param int $id
*
* #return JsonResponse
*/
public function get($id)
{
return new JsonResponse($this->findAlbumById($id), JsonResponse::HTTP_OK);
}
/**
* #param $id
*
* #return Album|null
* #throws NotFoundHttpException
*/
private function findAlbumById($id)
{
$album = $this->albumRepository->find($id);
if ($album === null) {
throw new NotFoundHttpException();
}
return $album;
}

Extbase property mapping for deleted record

I would like to build a preview page for a create form. I set "deleted" property of the record to "1" when in previewAction because in the BE the list module is used to approve the inserted records - so if the record was never finally saved its deleted anyway.
Problem: I can create the record (deleted=1) - I can jump back to the form (no history back for I have to keep the created object). But if I submit again the property mapping tells me
Object of type MyModel with identity "3" not found.
Of course that's because its deleted. The settings in the Repository to ignore deleted are not taking action here.
Yes I could bypass the Extbase magic by filling up everything manually, but this is not what I want.
Here is the action to get an idea what I'm trying
/**
* action preview
*
* #param MyModel
* #return void
*/
public function previewAction(MyModel $newModel)
{
//check if model was already saved
$uid = $this->request->hasArgument('uid') ? this->request->getArgument('uid') : 0;
if($uid){
$newModel = $this->myRepository->findDeletedByUid($uid);
$this->myRepository->update($newModel);
}
else{
$newModel->setDeleted(true);
$this->myRepository->add($newModel);
}
$this->view->assign('ad', $newModel);
$this->persistenceManager->persistAll();
$uid = $this->persistenceManager->getIdentifierByObject($newModel);
$this->view->assign('uid', $uid);
}
Any ideas?
The Extbase default query settings suppress deleted objects.
Since you've already stated the custom query findDeletedByUid() in your repository, you just need to set it to include deleted records. It is important, however, that if you want to call your controller action using the object, you'll have to retrieve it before calling the action. Use an initialization action for that. The initializaton will be called automatically before the action.
If you want to set wether the object is deleted, you'll also going to need to define a property, getter and setter in your Domain Model and a proper definition in your tca to enable the data mapper to access the column.
In the repository:
public function findDeletedByUid($uid) {
$query = $this->createQuery();
$query->getQuerySettings()->setIncludeDeleted(true);
$query->matching(
$query->equals('uid',$uid)
);
return $query->execute();
}
In your Controller class:
/**
* initialize action previewAction
* Overrides the default initializeAction with one that can retrieve deleted objects
*/
public function initializePreviewAction(){
if( $this->request->hasArgument('mymodel') ){
$uid = $this->request->getArgument('mymodel');
if( $mymodel = $this->mymodelRepository->findDeletedByUid($uid) ){
$this->request->setArgument($mymodel);
} else {
// handle non retrievable object here
}
} else {
// handle missing argument here
}
}
In your Domain Model:
...
/**
* #var bool
*/
protected $deleted;
/**
* #return bool
*/
public function getDeleted() {
return $this->deleted;
}
/**
* #param bool $deleted
*/
public function setDeleted($deleted) {
$this->deleted = $deleted;
}
In your tca.php
...
'deleted' => array(
'exclude' => 1,
'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.deleted',
'config' => array(
'type' => 'check',
),
),
Instead of doing any magic with deleted, you should use the hidden field to allow editors to preview documents.
You can tell your query to include hidden records inside the repository.
Your findDeletedByUid($uid) function caught my eye. If it's not a custom function, should it use something like findByDeleted(TRUE) or findByDeleted(1) in combination with ->getFirst() or ->findByUid()? You can find discussions in the Extbase manual reference and the Repository __call() function API sections.
Thanks for all hints.
I think depending to the answers its not possible without bypass extbase property-mapping magic. So I think in general its not a good idea to do it like that.
So I put now my own flag "stored" to the model.
In BE List-Module the not "stored" objects are still visible, but using an own BE Module or deleting the not "stored" object by a cron-job should do the job.
If anyone has a bedder idea feel free to share it :-)

Laravel 5.2 set model attributes based on fields in Input

I have huge user update form. Sometimes update contains huge amount of fields, sometimes just one or two. This is my code:
public function updateUser(Request $request){
$user = User::where('id',$request->id)->firstOrFail();
if($request->first_name){
$user->first_name= $request->first_name;
}
if($request->last_name){
$user->last_name = $request->last_name;
}
if($request->job_name){
$user->job_name= $request->job_name;
}
//etc.. 20 more fields
$user->save();
It is possible to set model attributes dependent on fields in $request? Sometimes $request contains 1 field, sometimes 20. Please notice I want to touch database only once, using save() method at the end.
$user->update($request->all());
Make sure all necessary variables are specified in your $fillable array for User model
If you want to update model attributes without saving use fill method
If $request field name and Model field name are same(as it seems in your current code) try this:
$input = $request->all();
$user = User::firstOrFail('id',$input->id);
$updateNow = $user->update($input);
Another option is:
DB::table('users')
->where('id', $request->id)
->update($request); //or can use Input::all()
Have a look at it as well for more explanation: Query Builder
for User model
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password', 'facebook_id', 'job_name', '20 more fields...'
];
for Controller
public function store(Request $request){
$allRequest = $request->all();
// It is not in table
unset($allRequest['_token']);
User::create($allRequest);
}

How to modify the callback in a custom validator in Symfony2(.7)

The title might seem awkward, but the reason for doing is is the re-use of code.
Say I have a Form(type), an Entity and the validation on the entity as annotation.
For example :
the form:
$form = $this->createFormBuilder($entity)
->add('country', 'choice', [
'choices' => $entity->getLocations(),
'required' => true,
'multiple' => false,
'expanded' => false,
'label' => 'Are you located in:',
])
the entity :
/**
* #Assert\Choice(callback = "getLocations")
*/
protected $country;
#.....
public static function getLocations()
{
return array( 'en-wa' => 'England/Wales', 'sc' => 'Scotland');
}
Now of couse the validation will always fail because it works with values rather than with keys, so I was thinking of writing a custom validator called KeyChoice so I can still use the callback. but I can't seem to find any documentation about this, and reading the source didn't help out either.
Now I don't want judgement about best practice unless there is a way where I only have to define the options ones / form or even less, like for example in the form type, but then how do I use them in the callback in the validator ?
How about this:
/**
* #Assert/Choice(callback="getLocationChoices")
*/
...
public static function getLocationChoices(){
return array_keys(self::getLocations());
}
Update:
Or you can create a custom constraint for that:
// the constraint
namespace AppBundle\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class KeyChoice extends Constraint
{
public $message = 'the choice is not valid';
public $callback;
}
// the validator
namespace AppBundle\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
class KeyChoiceValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
if (!is_callable($choices = array($this->context->getClassName(), $constraint->callback))
&& !is_callable($choices = $constraint->callback)
) {
throw new ConstraintDefinitionException('The Choice constraint expects a valid callback');
}
$choices = call_user_func($choices);
if(!in_array($value, $choices)){
$this->context->buildViolation($constraint->message)->addViolation();
}
}
}
You can then use this constraint as stated in the linked doc.However this will not provide any of the other functionality that the choice constraint provides, if you want to have those you have to extend both Symfony\Component\Validator\Constraints\Choice and Symfony\Component\Validator\Constraints\ChoiceValidator, don't override anything in the ChoiceConstraint but you have to override the ChoiceConstraintValidator::validate completely and copy paste it, then add a $choices = array_keys($choices); at this line

ZendX_JQuery_Form_Element_AutoComplete

How can I get the javascript code generated wehen using ZendX_JQuery_Form_Element_AutoComplete
class?
$item = new ZendX_JQuery_Form_Element_AutoComplete('item', array(
'label' => 'Item' ,
'id' => 'auto-item'));
$item->setJQueryParams(array(
'source' => $some URL ,
'minLength' => '1'));
The javascript generated by the viewHelper will be :
$("#auto-item").autocomplete({"source":"some URL","minLength":"1"});
the last line is the target
Say you wanted to look-up a customer. Your source endpoint could be an action on a controller perhaps dedicated to serving autocomplete data.
Main thing to be aware of is format of the expected data:
[{"id":123,"label":"Some Customer","value":"Some Customer (ID#123)"},...
The 'label' and 'value' give you a chance to show alternate strings for the drop down list and what is ultimately selected.
Here is an example.
class Service_AutocompleteController extends Zend_Controller_Action
{
/**
* The query / q for the auto completion
*
* #var string
*/
protected $_term;
/**
* #see Zend_Controller_Action::init()
*/
public function init()
{
$this->_term = (isset($this->_request->term)) ? $this->_request->term : null;
}
/**
* Serve up JSON response for use by jQuery Autocomplete
*/
public function customerLookupAction()
{
// Disable the main layout renderer
$this->_helper->layout->disableLayout();
// Do not even attempt to render a view
$this->_helper->viewRenderer->setNoRender(true);
$query = "
SELECT TOP 50
c.id AS id,
c.name + '(ID#' + c.id + ')' AS label,
c.name AS value
FROM customer c
WHERE $label LIKE ?
ORDER BY c.name";
$db = Zend_Registry::get(SOME_DB_ADAPTER);
$results = $db->fetchAll($query, array('%' . $this->_term . '%'));
echo Zend_Json::encode($results);
}
}
Sometimes people take this a little further and have a view helper to send back the JSON, you could remove some of the repeated code in the controller actions that way.
Juste put this in your view file :
$this->jQuery()->getOnLoadActions()
it returns the javaScript generated by the jQuery helper