I have a SymfonyForm which has 1:n embedForm(s). The main form and the embedForm class got their own PreValidation, which implements a conditional validation.
A part of the EmbedForm class looks like this:
private function configurePreValidators() {
$validator = new sfValidatorCallback( array('callback'=> array($this, 'preValidation')) );
$this->getValidatorSchema()->setPreValidator(new sfValidatorOr( array( $validator ) ));
}
public function preValidation(sfValidatorCallback $validator, array $values){
...
$this->getValidator(self::SOME_FIELD)->setOption('required', false);
...
}
public function configure() {
...
$this->configurePreValidators();
parent::configure();
}
The prevalidation of the main form is similar.
When I submit the form, the main form prevalidation works fine.
In the embed-Form the "SOME_FIELD" gets a required-validation-error although I set it explicit to setOption('required', false) in the preValidation of the embedForm.
Is there something what I have to consider when I use pre-validation in an embedForm? What about mergePreValidator? Any hints about that?
Thanks in advance!
The issue here is not that your pre and post validators aren't firing -- they are (or at least, they should be). The issue is that the validator you are modifying is preValidate is not the one referenced in the top-level validator schema, i.e. the validator schema for the top-level form.
One solution: rather than modify the validator in preValidate, simply perform the validation:
public function preValidation(sfValidatorCallback $validator, array $values)
{
if (!$this->getValidator(self::SOME_FIELD)->isEmpty($values[self::SOME_FIELD])
{
throw new sfValidatorErrorSchema(self::SOME_FIELD => new sfValdiatorError($validator, 'msg'));
}
}
Note, this solution has some danger: if you modify the validator for SOME_FIELD inside of the top-level form, it will not be modified in this pre validator and vice-versa.
Let's look at why. In sfForm::embedForm:
public function embedForm($name, sfForm $form, $decorator = null)
{
...
$this->validatorSchema[$name] = $form->getValidatorSchema();
...
}
Symfony simply nests the validators. This is why pre and post still get called. Why does the reference change? sfValidatorSchema::offsetSet:
public function offsetSet($name, $validator)
{
...
$this->fields[$name] = clone $validator;
}
So when a form is embedded, the validator schema is cloned. Thus, any modifications to the validators inside of an embedded form do not affect the top-level validator schema.
Related
How to add a custom action to an existing Controller in Shopware?
Examples (url structure):
/account/bonus
/account/custom
/account/...
Usually it's easier and cleaner to create a new controller for that purpose, but in some cases it's necessary.
You should not replace the "account" controller.
You can define you own action for existing controller with following:
public static function getSubscribedEvents()
{
return [
'Enlight_Controller_Action_Frontend_Account_MyBonus' => 'onAccountMyBonus',
];
}
and then
public function onAccountMyBonus(\Enlight_Event_EventArgs $args)
{
$args->setProcessed(true);
.....
your code here
}
Spoiler: Replace the controller
There is no cleaner way than to replace the whole controller and extend it's functionality, so it's nearly as clean as Shopware's hooks.
Guide
Add a new Subscriber to your Plugin
class AccountSubscriber implements SubscriberInterface
{
/**
* #return array
*/
public static function getSubscribedEvents()
{
return array(
'Enlight_Controller_Dispatcher_ControllerPath_Frontend_Account' => 'getAccountController'
);
}
/**
* #return string
*/
public function getAccountController()
{
return $this->getPath() . '/Controllers/Frontend/AccountExtended.php';
}
/**
* #return string
*/
public function getPath()
{
$plugin = Shopware()->Container()->get('kernel')->getPlugins()['AcmeYourPlugin'];
return $plugin->getPath();
}
}
Downsides
Unfortunately some controller have private methods which impact the logic. Like the Account Controller. So it's not always possible to simply extend the controller.
In the end, try to add a new controller with a new route.
It's easier, and cleaner.
There is a cleaner way than replacing the whole Controller.
It is also not recommended to replace a whole controller due to the lack of update compatibility.
In the worst case something like that could kill the whole website.
A while ago I created a thread in the shopware forum (german) discussing the exact same issue. I wanted to extend an existing finishAction() in the checkout Controller.
public function onPostDispatchCheckout(\Enlight_Controller_ActionEventArgs $args)
{
/** #var \Enlight_Controller_Action $controller */
$controller = $args->getSubject();
/** #var \Enlight_Controller_Request_Request $request */
$request = $controller->Request();
if ($request->getActionName() !== 'finish') {
return;
}
// do your stuff here
}
So even though it is not the exact same issue you have, the procedure is quite the same.
First off you subscribe to the controller (in my case the PostDispatchCheckout Controller) afterwards you edit the controller in your Bootstrap.php
To make sure, that it just alters a specific action you have to use the if-construction so your code gets just triggered on the wished action [in my case the finishAction()].
I hope this helps. What wonders me though is why you have to add a new action to an already existing controller. I can think of no situation where something like that is more practicable than creating a complete new custom controller.
Kind regards,
Max
Here's sample code:
class UsersController extends AppController
{
...
public function implementedEvents()
{
return [
'Auth.logout' => 'afterLogout'
];
}
public function afterLogout($event)
{
$this->Flash->toast(__('Good bye!'));
}
...
}
Before implementing implementedEvents() method, AppController::beforeRender() method was triggered properly.
I needed to listen to Auth.logout event, so wrote implementedEvents() method. I thought it would be merged to the default events array. But after that, AppController::beforeRender() stopped working. It didn't trigger any more. So I guess there's an overwriting behavior.
Is this a default behavior of CakePHP 3? And is this an intended behavior or a bug?
That's the intended behavior, as otherwise it would be complicated to overwrite instead of merging.
If you need to merge possible parent listener configuration, then you need to do that on your own, like
public function implementedEvents()
{
return [
'Auth.logout' => 'afterLogout'
] + parent::implementedEvents();
}
Yii2 has support for XSS(cross-site-scripting ) validation of displayed data using the helper class\yii\helpers\HtmlPurifier, however this only validates and cleans up output code like this
echo HtmlPurifier::process($html);
How to validate input for XSS of input such that this data is not stored in the database itself ?
This can be done using a filterValidator by calling the process as named callable function of validation like this
class MytableModel extends ActiveRecord {
....
public function rules(){
$rules = [
[['field1','field2'],'filter','filter'=>'\yii\helpers\HtmlPurifier::process']
];
return array_merge(parent::rules(),$rules);
}
....
}
Where field1, field2 etc are the inputs fields to be validated, the same applies for Form Model validations as well
in the before validate method add the following:
public function beforeValidate()
{
foreach (array_keys($this->getAttributes()) as $attr){
if(!empty($this->$attr)){
$this->$attr = \yii\helpers\HtmlPurifier::process($this->$attr);
}
}
return parent::beforeValidate();// to keep parent validator available
}
it will help you if you want to run Xss Validator before validate/save all attributes nested of adding the following line
return array_merge(parent::rules(),$rules);
to every class extends the new active record
I have a problem. I try to get the Entity-Manager without a Controller, but I found no way.
At this time, I get the Entity-Manager like this:
(Controller)
public function getEntityManager()
{
if (null === $this->_em) {
$this->_em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
}
return $this->_em;
}
(Plugin)
public function getEntityManager()
{
if($this->_em == null){
$this->_em = $this->getController()->getServiceLocator()->get('doctrine.entitymanager.orm_default');
}
return $this->_em;
}
You see, I need allways a controller. But, if I need the EntityManager in a model, i have a problem. I can give the model the controller, but I think this is really a bad way.
Have you any idea to get the EntityManager without a controller?
The way I handle Doctrine is through Services, i do it like the following:
//some Controller
public function someAction()
{
$service = $this->getServiceLocator()->get('my_entity_service');
return new ViewModel(array(
'entities' => $service->findAll()
));
}
The Service->findAll() would look something like this:
public function findAll()
{
return $this->getEntityRepository()->findAll();
}
Now we need to define the my_entity_service. I do this inside my Module.php
public function getServiceConfig()
{
return array(
'factories' => array(
'my_entity_service' => 'Namespace\Factory\MyServiceFactory'
)
);
}
Next I create the Factory (note: this could also be done via anonymous function inside the Module.php)
<?php
namespace Namespace\Factory;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\FactoryInterface;
use Namespace\Model\MyModel;
class MyServiceFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$myModel= new MyModel();
$myModel->setEntityManager($serviceLocator->get('Doctrine\ORM\EntityManager'));
return $myModel;
}
}
Now this is a lot to chew :D I totally get that. What is happening here is actually very simple though. Instead of creating your model and somehow get to the EntityManager, you call ZF2's ServiceManager to create the Model for you and inject the EntityManager into it.
If this is still confusing to you, I'll gladly try to explain myself better. For further clarification however I'd like to know about your use case. I.e.: what for do you need the EntityManager or where exactly do u need it.
This code example is outside of the question scope
Just to give you a live example of something I do via ServiceFactories with forms:
public function createService(ServiceLocatorInterface $serviceLocator)
{
$form = new ReferenzwertForm();
$form->setHydrator(new DoctrineEntity($serviceLocator->get('Doctrine\ORM\EntityManager')))
->setObject(new Referenzwert())
->setInputFilter(new ReferenzwertFilter())
->setAttribute('method', 'post');
return $form;
}
Your real question is "How to get an Instance of ServiceManager in my own classes"
For this, take a look at the docu: (bottom of page http://zf2.readthedocs.org/en/latest/modules/zend.service-manager.quick-start.html)
By default, the Zend Framework MVC registers an initializer that will
inject the ServiceManager instance, which is an implementation of
Zend\ServiceManager\ServiceLocatorInterface, into any class
implementing Zend\ServiceManager\ServiceLocatorAwareInterface. A
simple implementation looks like the following.
so implent the ServiceLocatorInterface in your classes and then inside your class you can call:
$this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
or any other service you have registered.
We have a possibility that data loaded from a GET operation could be invalid for posting, and would like to be able to display the validation messages when the data is first loaded. The validation all takes place on server side using ValidationAttributes.
How can I force the validation summary to be displayed when the data is first loaded? I am guessing that I need to force errors into ModelState somehow, but I first need to get them out of the model class.
I ended up adding a validation method for the model class which adds errors to the ModelState. Then I created and added a custom ModelValidator and AssociatedValidatorProvider
for calling it during the normal validation that takes place during form binding. That way the controller actions that don't bind to the Model class directly can still have a call to the model's .Validate(ModelState) method to fake a validation. This approach works well for server-side-only validation.
UserInfo Model class:
private IEnumerable<RuleViolation> GetRuleViolations()
{
List<RuleViolation> violationList = new List<RuleViolation>();
if (String.IsNullOrWhiteSpace(FirstName))
violationList.Add(new RuleViolation("First Name is required.", FirstName"));
return violationList;
}
public void Validate(System.Web.Mvc.ModelStateDictionary ModelState)
{
foreach (RuleViolation violation in GetRuleViolations())
{
ModelState.AddModelError(violation.PropertyName, violation.ErrorMessage);
}
}
This is how it can be used directly from a controller action. In this action the Model class object is returned as part of the UserSearch model.
public ActionResult Search(UserSearch model)
{
if (this.ModelState.IsValid)
{
model.Search();
if (model.UserInfo != null )
{
model.UserInfo.Validate(ModelState);
}
}...
That is all I had to do for the particular use case I was working on. But I went ahead and completed the work to do "normal" validation on a postback: created a simple ModelValidator, with the Validate override looking like this. If you followed the above pattern in all of your Model classes you could probably reusue this for them, too.
public override IEnumerable<ModelValidationResult> Validate(object container)
{
var results = new List<ModelValidationResult>();
if (Metadata.Model != null)
{
UserInfoViewModel uinfo = Metadata.Model as UserInfoViewModel;
foreach (var violation in uinfo.GetRuleViolations())
{
results.Add(new ModelValidationResult
{
MemberName = violation.PropertyName,
Message = violation.ErrorMessage
});
}
}
return results;
}
Finally, extend AssociatedValidationProvider to return this ModelValidator and add it to the ModelValidationProviders collection in Application_Start. There is a writeup of this at http://dotnetslackers.com/articles/aspnet/Customizing-ASP-NET-MVC-2-Metadata-and-Validation.aspx#s2-validation
I don't know if understand what you need, but here is it...
run validation to display the validation summary when the form is loaded, using jquery
$(document).ready(function() {
$('#FormId').valid();
});