What is the correct way to add validation on the form level in ZF2? - validation

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;
}

Related

Yii2 - Attributes in DynamicModel

I created a yii\base\DynamicModel in controller and I have one form with attributes from this model. I need access these attributes after submitting form in controller.
controller.php
public function actionCreate()
{
$model = new DynamicModel([
'name', 'age', 'city'
]);
if($model->load(Yii::$app->request->post())){
$model->age = $model->age + 5;
/*
* code....
* */
return $this->redirect(['index']);
} else {
return $this->render('create', [
'model' => $model,
]);
}
}
But $model->age, $model->name etc. returns nothing.
I could only access the attribute this way: Yii::$app->request->get('DynamicModel')['age']
What is the correct way to access these attributes?
You need to configure validation rules in order to automatically load attributes by load():
$model = new DynamicModel(['name', 'age', 'city']);
$model->addRule(['name', 'age', 'city'], 'safe');
if ($model->load(Yii::$app->request->post())) {
// ...
Using safe will accept values as is without actual validation, but you may consider adding real validation rules to ensure correct state of your model.

Symfony2 validate text input when containing something only

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;
}

Using performAjaxValidation inside a widget (Yii)

I've created a widget with a CActiveForm in it. Everything works ok, but now i want to enable ajax validation for it.
The problem is that the output of my ajax validation is containing, besides the validation JSON string, all (well a part of it, since Yii::app()->end() stops the rest) of my html as well. Not weird, because i'm using it within a widget and the validation request is done to the controller/action where i've placed this widget on.
Is there some way to prevent outputting all the html, so a valid JSON string is returned?
I've already tried to set the validationUrl in the CActiveForm to another controller/action, but the problem is that i have to send the model with it and this model is determined in my widget and not on the validationUrl.
Widget:
public function run()
{
$model = new User;
$model->scenario = 'create';
$this->performAjaxValidation($model);
if (isset($_POST['User'])) {
$model->attributes = $_POST['User'];
if ($model->save()) {
}
}
$this->render('register-form', array(
'model' => $model
));
}
/**
* Performs the AJAX validation.
* #param User $model the model to be validated
*/
protected function performAjaxValidation($model)
{
if(isset($_POST['ajax']))
{
echo CActiveForm::validate($model);
Yii::app()->end();
}
}
Output of performAjaxValidation() (the ajax call):
.. more html here ..
<section class="box">
<h1>Register form simple</h1>
{"UserPartialSignup_email":["Email is geen geldig emailadres."]}
I solved it this way:
I've created an AJAX controller where the validation is done:
AjaxController:
/**
* Validates a model.
*
* Validates a model, POST containing the data. This method is usually used for widget based forms.
*
* #param $m model name which have to be validated
* #param $s scenario for this model, optional.
* #return string JSON containing the validation data
*/
public function actionValidate($m, $s = null)
{
if ($this->checkValidationData($m, $s) && isset($_POST['ajax']))
{
$model = new $m;
$model->scenario = $s;
echo CActiveForm::validate($model);
Yii::app()->end();
} else {
throw new CHttpException(500, 'No valid validation combination used');
}
}
You can give the model name and a scenario as GET parameters with it, i'm checking if this combination is allowed by the checkValidationData() method.
In the view of my widget where the CActiveForm is placed, i've added the validationUrl, referring to ajax/validate:
widgets/views/registerform.php:
<?php $form = $this->beginWidget('CActiveForm', array(
'id'=>'signup-form-advanced',
'enableAjaxValidation'=>true,
'clientOptions' => array(
'validationUrl' => array('ajax/validate', 'm' => get_class($model), 's' => 'create')
)
//'enableClientValidation'=>true,
)); ?>

Zend Framework 2: get matched route in view

I'm currently learning ZF2 by developing a small MVC application roughly based on the skeleton app. Right now I'm trying to hide some fixed HTML elements based on the route matched: just as an example, I don't want the main menu to show during the login phase.
I can do that easily by passing toggle parameters as return values from the controller actions, but it doesn't feel right, so I'd like to just check the matched route from the layout and compose the layout accordingly.
Problem is, I don't know how to get the matched route in the template. Is this possible? Are there other solutions to avoid adding layout logic into controllers?
Edit after some good Frankenstein work, I was able to find a solution for this. I like the idea of using a helper, so I just tried to pass it the Application object, from the boostrap function in the main module:
$app = $e->getApplication();
$serviceManager = $app->getServiceManager();
....
$serviceManager->get('viewhelpermanager')->setFactory('getRoute', function($sm) use ($app) {
return new Helper\GetRoute($app);
});
and the helper function:
use Zend\View\Helper\AbstractHelper;
class GetRoute extends AbstractHelper {
private $sm;
public function __construct($app) {
$this->sm = $app->getServiceManager();
}
public function echoRoute() {
$router = $this->sm->get('router');
$request = $this->sm->get('request');
$routeMatch = $router->match($request);
if (!is_null($routeMatch))
echo $routeMatch->getMatchedRouteName();
}
}
perhaps there's a cleaner, more ZF2ish way to do this...
Another solution without a new match
$routeMatch = $serviceLocator->get('Application')->getMvcEvent()->getRouteMatch();
echo $routeMatch->getMatchedRouteName();
There is a way to get service manager in layout:
$sm = $this->getHelperPluginManager()->getServiceLocator();
and then you can access $sm->get('router') etc.
You could create a View helper that implements ServiceManagerAwareInterface. Then inside the View helper using the ServiceManager instance to retrieve both the router and request objects then reconstruct the route match.
$services = $this->getServiceManager();
$router = $services->get('router');
$request = $services->get('request');
$routeMatch = $router->match($request);
echo $routeMatch->getMatchedRouteName();
I'd also recommend writing the View helper so that code only triggers once per request.
When moving to ZF3, you should consider use this method... since getLocator isn't available anymore (and it's not correct inject it).
Create the Helper
namespace Application\View\Helper;
use Zend\Http\Request;
use Zend\Router\RouteStackInterface;
use Zend\View\Helper\AbstractHelper;
/**
* Helper to get the RouteMatch
*/
class RouteMatch extends AbstractHelper
{
/**
* RouteStackInterface instance.
*
* #var RouteStackInterface
*/
protected $router;
/**
* #var Request
*/
protected $request;
/**
* RouteMatch constructor.
* #param RouteStackInterface $router
* #param Request $request
*/
public function __construct(RouteStackInterface $router, Request $request)
{
$this->router = $router;
$this->request = $request;
}
/**
* #return \Zend\Router\RouteMatch
*/
public function __invoke()
{
return $this->router->match($this->request);
}
}
Create a Factory for this helper
namespace Application\View\Helper;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class RouteMatchFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$router = $container->get('router');
$request = $container->get('request');
return new RouteMatch($router, $request);
}
}
Call your Factory on your Module.php and create an Alias for it.
public function getViewHelperConfig()
{
return array(
'factories' => array(
RouteMatch::class => RouteMatchFactory::class,
),
'aliases' => array(
'routeMatch' => RouteMatch::class,
)
);
}
That's it... you have a RouteMatch Helper using the new ZF3 standards.
Bye!
In view you can use:
$this->getHelperPluginManager()->getServiceLocator()->get('request')->getUri()->getPath();
or
$this->getHelperPluginManager()->getServiceLocator()->get('request')->getUri()->toString();
I believe you can solve it by finding the action/controller names:
$controller = $this->getRequest()->getControllerName();
$action = $this->getRequest()->getActionName();
Once you know the action, you can have specific conditions to enable sections of the layout.
I view you can use
$this->getHelperPluginManager()->getServiceLocator()->get('Application')->getMvcEvent()->getRouteMatch()->getMatchedRouteName();
Additional information about "Rodrigo Boratto" post for integrating getRouteMatch in ZF3 (I can't comment because I have under 50 repo...)
In view helper file these line:
use Zend\Mvc\Router\RouteMatch as MvcRouteMatch;
use Zend\Mvc\Router\RouteStackInterface;
should be:
use Zend\Router\RouteMatch as MvcRouteMatch;
use Zend\Router\RouteStackInterface;
I don't know when they made that change but the files are in Zend\Router namespace.
P.S. I use composer if that matters.
My controller:
<?PHP
namespace SomeName\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class SomeController extends AbstractActionController
{
public function getIdAction()
{
$id = $this->params()->fromRoute('id', 0);
return new ViewModel(array(
'id' => $id,
));
}
}
My Router:
<?php
return array(
'controllers' => array(
'invokables' => array(
'SomeName\Controller\Some' => 'SomeName\Controller\SomeController',
),
),
'router' => array(
'routes' => array(
'testId' => array(
'type' => 'segment',
'options' => array(
'route' => '/[:id]',
'constraints' => array(
'id' => '\d*',
),
'defaults' => array(
'controller' => 'SomeName\Controller\Some',
'action' => 'getId',
),
),
),
),
),
'view_manager' => array(
'template_path_stack' => array(
'album' => __DIR__ . '/../view',
),
),
);
At any view or layout, you are able to test route with this function:
<?php function itsRoute($routeName){
$flag = false;
if($this->serverUrl(true) == $this->url($route,[],['force_canonical'=>true]))){
$flag = true;
}
return $flag;
}

how we can add rule in Yii model for input must be greater than 0

do anyone know how can I apply rule in Yii model for input must be greater than 0 value, without any custom approach ..
like :
public function rules()
{
return array(
....
....
array('SalePrice', 'required', "on"=>"sale"),
....
....
);
}
many thanks ..
Simpler way
array('SalePrice', 'numerical', 'min'=>1)
with a custom validator method
array('SalePrice', 'greaterThanZero')
public function greaterThanZero($attribute,$params)
{
if ($this->$attribute<=0)
$this->addError($attribute, 'Saleprice has to be greater than 0');
}
I see it is a price so you could use 0.01 (a penny) as a minimum value like so:
array('SalesPrice', 'numerical', 'min'=>0.01),
Note that this solution does not validate that the number entered is a price, just that it is > 0.01
I know I am too late for this . But just for future reference you can use this class also
<?php
class greaterThanZero extends CValidator
{
/**
* Validates the attribute of the object.
* If there is any error, the error message is added to the object.
* #param CModel $object the object being validated
* #param string $attribute the attribute being validated
*/
protected function validateAttribute($object,$attribute)
{
$value=$object->$attribute;
if($value <= 0)
{
$this->addError($object,$attribute,'your password is too weak!');
}
}
/**
* Returns the JavaScript needed for performing client-side validation.
* #param CModel $object the data object being validated
* #param string $attribute the name of the attribute to be validated.
* #return string the client-side validation script.
* #see CActiveForm::enableClientValidation
*/
public function clientValidateAttribute($object,$attribute)
{
$condition="value<=0";
return "
if(".$condition.") { messages.push(".CJSON::encode($object->getAttributeLabel($attribute).' should be greater than 0').");
}";
}
}
?>
Just make sure this class is imported before use.
Did nobody check the docs?
There is a built-in validator CCompareValidator:
['SalePrice', 'compare', 'operator'=>'>', 'compareValue'=>0]
you can use this one too:
array('SalePrice', 'in','range'=>range(0,90))
I handled this by regular expression, may be it will help too ..
array('SalePrice', 'match', 'not' => false, 'pattern' => '/[^a-zA-Z0]/', 'message' => 'Please enter a Leader Name', "on"=>"sale"),
many thanks #sdjuan & #Ors for your help and time ..

Resources