ZF-9275: Zend_Form_Element overrides Zend_Validate DefaultTranslator
Description
If I set Zend_Validate_Abstract::$_defaultTranslator to an other Translator than that one of Zend_Form. Zend_Form_Element will allways use it's global default Translator from the registry.
protected function _initTranslator()
{
$translate = new Zend_Translate('gettext',APPLICATION_PATH.'/languages/de/de.mo','de');
Zend_Registry::set('Zend_Translate', $translate);
$translateValidate = new Zend_Translate('array', APPLICATION_PATH.'/languages/de/Zend_Validate.php','de');
Zend_Validate_Abstract::setDefaultTranslator($translateValidate);
}
public function testtranslateAction()
{
$form = new Zend_Form();
$input = new Zend_Form_Element_Text
(
'example',
array
(
'required' => true
)
);
$submit = new Zend_Form_Element_Submit
(
'test',
array
(
'label' => _('submit')
)
);
$form->addElements(array($input, $submit));
if($this->getRequest()->isPost())
{
$form->isValid($this->getRequest()->getPost());
}
$this->view->form = $form;
}
If I send this form with an empty post, the error message should be the german text "Es wird ein Wert benötigt. Dieser darf nicht leer sein" ,but it's allways the original message "Value is required and can't be empty".
Comments
Posted by Matthew Weier O'Phinney (matthew) on 2010-02-26T14:02:15.000+0000
Can you please provide some reproduce code, and clearly indicate expected and actual results?
Posted by Thomas Rothe (burnred) on 2010-02-26T14:33:35.000+0000
Added example code
Posted by Christian Albrecht (alab) on 2010-03-18T08:27:57.000+0000
Fixed in [ZF-9364]
Posted by Dominique Lorre (dlorre) on 2010-03-24T06:34:31.000+0000
Please reopen this issue as the validator translator is overriden by the form element translator. ZF 9364 addresses another issue see the comments.
Posted by Christian Albrecht (alab) on 2010-03-24T07:28:51.000+0000
However i am not sure, if that is correct since $validator will return Translator from Zend_Registry when it has none set and therefor break BC because the default Translator may differ from that set to Zend_Form or Zend_Form_Element.
Posted by Dominique Lorre (dlorre) on 2010-03-24T08:03:57.000+0000
Thank you for reopening. Yes, the fix is tricky.
I suggest checking $validator->getTranslator() against Zend_Registry::get('Zend_Translate').
If they are identical then it would be fine to override the validator. That follows the logic of going from general to specific.
Posted by Christian Albrecht (alab) on 2010-03-25T06:32:11.000+0000
Suggested fix, add new members to Zend_Validate_Abstract
protected $_isSpecificTranslator = false; protected static $_isSpecificDefaultTranslator = false;
with appropriate getter Methods and set the new members true within setTranslator() and setDefaultTranslator().
Posted by Dominique Lorre (dlorre) on 2010-03-25T06:56:17.000+0000
It's an interesting approach but I don't think we need specificc translator flag, because when we use:
Zend_Validate_Abstract::setDefaultTranslator($translateValidate);
we precisely mean that we want the validators to use this translator.
First case: we want to handle all the translations. We do :
and everything is fine. ==> Zend_Validate_Abstract::getDefaultTranslator() == Zend_Registry::get('Zend_Translate')
Second case: we want to handle translations but want to use the default translations for validators then we do:
and:
==> Zend_Validate_Abstract::getDefaultTranslator() != Zend_Registry::get('Zend_Translate')
Third case: we don't want to use Zend_Translate but our project is not in english. So we will to use the translator for the validators only.
==> Zend_Validate_Abstract::getDefaultTranslator() != Zend_Registry::get('Zend_Translate')
So, if Zend_Validate_Abstract::getDefaultTranslator() == Zend_Registry::get('Zend_Translate') then we can assume it is safe to override the validator translator.
Posted by Christian Albrecht (alab) on 2010-03-25T07:15:22.000+0000
Dominique, i think the problem is
$translate = Zend_Registry::get('Zend_Translate'); $translate instanceof Zend_Translate || $translate instanceof Zend_Translate_Adapter
but always
Zend_Validate_Abstract::getDefaultTranslator() instanceof Zend_Translate_Adapter
Posted by Christian Albrecht (alab) on 2010-03-25T07:38:56.000+0000
Baah, much to complicated my solution, here is a simpler one
Posted by Dominique Lorre (dlorre) on 2010-03-25T08:00:08.000+0000
Christian, that code works with my project. It's probably safer to do it that way.
Posted by Christian Albrecht (alab) on 2010-03-25T08:06:28.000+0000
Which one do you refer to with 'that'? My simpler solution, or yours?
Posted by Dominique Lorre (dlorre) on 2010-03-25T08:49:04.000+0000
My safety concern is about Zend_Form::getDefaultTranslator(). Actually Zend_Validate_Abstract::getDefaultTranslator() ignores the default form translator but should the Zend dev team changes this then the registry check would stop working. So, yes your solution is safer in that perspective.
Posted by Christian Albrecht (alab) on 2010-03-25T09:17:50.000+0000
proove that it works
Posted by Rob Allen (rob) on 2010-03-28T08:22:46.000+0000
This unit test shows the problem:
Posted by Rob Allen (rob) on 2010-03-28T09:17:26.000+0000
Christian's solution will result in a translator attached directly to an element not being set for that element's validators which is wrong.
This is a potential patch that passes my unit test above:
The problem with this patch is that the translator attached to Zend_Form::setDefaultTranslator() is now ignored for validation messages, so you would then have to set them using Zend_Validate_Abstract::setDefaultTranslator().
Posted by Rob Allen (rob) on 2010-03-28T09:54:02.000+0000
The correct order of translators to apply to a validator when validating it via a form element is:
(most generic to most specific) 1. Zend_Registry's 'Zend_Translate' translator 2. Zend_Validate_Abstract's static default translator 3. Zend_Form's static default translator 4. The form instance's directly attached translator 5. The element instance's directly attached translator 6. The validator instance's directly attached translator
To prove this, I've created 4 unit tests:
This is the patch that passes this set of unit tests:
All other Zend_Form tests also continue to pass.
Posted by Rob Allen (rob) on 2010-03-28T09:55:38.000+0000
This patch fixes the reported issue and also ensures that related issues around the order of applied translate objects is now correct.
Posted by Rob Allen (rob) on 2010-03-31T12:57:55.000+0000
Fixed on trunk r21724 (test) r21725 (library) Fixed on branch: r21726
Posted by Dominique Lorre (dlorre) on 2010-05-11T09:19:12.000+0000
Hello, this issue is not fixed as of 1.10.4. Please reopen it.
This code is wrong:
When Zend_Validate_Abstract::setdefaultTranslator() is used, it means we intend to use the special translation files for the validators which are included in the Zend Framework package under resources/languages/xx/Zend_Validate.php. The one and only case when we don't want to use this translator for the validators is when we directly set the validator translator using $validator->setTranslator() The code above prevents this behavior to work since the element always have a translator set with an init code like this one:
Posted by Dominique Lorre (dlorre) on 2010-05-11T09:30:45.000+0000
code should be:
Posted by Rob Allen (rob) on 2010-05-11T13:18:05.000+0000
"The one and only case when we don't want to use this translator for the validators is when we directly set the validator translator using $validator->setTranslator()"
I disagree. If a validator is set on the form, then it should override.
Can you provide a use-case where you would want to set a form translator, but expect the global Zend_Validate translator to win?
Alternatively, if you mean that the default Zend_Validate_Abstract static translator is never used, could you provide a unit test that shows this?
Posted by Dominique Lorre (dlorre) on 2010-05-12T02:14:06.000+0000
The part Zend_Form vs Zend_Validate is not the main issue. In my code I did not use Zend_Form default translator in first place.
I expect Zend_Validate translator to "win" when I use the translated files in resources/languages of the Zend Framework full package. I don't know any other case where the default translator for Zend_Validate is useful.
The problem is we get translations for most validators in many languages but there is no simple way to use them!
Actually I'm using a workaround by setting the form default translator. It works but using the form default translator for a validate translator is not correct.
You don't need test file, the default Zend_Validate translator is not used if a translator is set for the form element:
and this happens always if a translator file is used. For instance:
Used in conjunction with the init code I already mentioned. This part is the main issue. We are back to the title of this issue: "Zend_Form_Element overrides Zend_Validate DefaultTranslator"
Posted by Christian Albrecht (alab) on 2010-05-12T03:18:49.000+0000
Dominique, my guess is this works as expected now if you stop using your workaround setting the Form default Translator.
Posted by Dominique Lorre (dlorre) on 2010-05-12T05:13:43.000+0000
Christian, only if I remove the registry:
Then I wouldn't need the workaround, or else this would be set as the translator of all elements in Zend_Form::isValid, because of this code:
Actually, commenting out this line would not help because this registry is already set in getTranslate() of Zend/Application/Resource/Translate.php, the other workaround would be to change the key name (untested atm)in application.ini.
Now we clearly see the problem: the element translator is set in Form::isValid because the registry key Zend_Translate is initialized and since the element has a translator then Zend_Validate default translator is overriden.
Once again, setting Zend_Validate default translator has a special meaning, and should not be overriden in order to use the resources/languages translations.
Posted by Christian Albrecht (alab) on 2010-05-12T05:44:27.000+0000
oh, now i see what you mean - So you are arguing that the precedence list from [Rob's comment|#action_39619] should read
Posted by Rob Allen (rob) on 2010-05-12T06:34:29.000+0000
My list was:
(most generic to most specific) 1. Zend_Registry's 'Zend_Translate' translator 2. Zend_Validate_Abstract's static default translator 3. Zend_Form's static default translator 4. The form instance's directly attached translator 5. The element instance's directly attached translator 6. The validator instance's directly attached translator
I could certainly be persuaded that 2 and 3 should be swapped around.
I definitely think that any translator attached to an instance object should "beat" a global static translator.
Posted by Dominique Lorre (dlorre) on 2010-05-12T08:25:53.000+0000
I am comfortable with any set of priorities as long as we can specify that: a) we use a personal translation file b) we use for the validators the Zend_Validate.php files from resources/languages c) we can use specific translators for some validators (such as the Captcha validator which is not translated).
The problem is that the element translator is attached automatically when Zend_Translate is set in the registry. Thus it breaks your priority list Rob, since Zend_Translate goes between 4 and 5 when it should be 1.
Posted by Dominique Lorre (dlorre) on 2010-05-13T01:37:23.000+0000
Back to priorities. I think the keyword is 'reusability'. When you have a translation file you want to use it on your next projects. This is the case with Zend_Validate.php which is conveniently set with
When I set this I mean to reuse a file that is provided in Zend_Framework package. If I don't want to use this file then I don't set it. This is why I said the only case it should be overriden is when I directly set the validator translator (captcha for instance).
Rob asked: {quote} Can you provide a use-case where you would want to set a form translator, but expect the global Zend_Validate translator to win?{quote} Yes. I create a couple forms (for example registration and options) in project A which uses validators such as emailaddress. I use Zend_Validate.php as
and my own translation file for the forms which will translate sentences such as "Enter your email address".
Now, why on earth would I want to add the validator messages to my form translation since I have them already translated? This goes against the reusability principle. When I create project B with the same forms I will reuse my custom form translation file and the Zend_Validate.php file.
Zend form elements have their own messages such as "Enter your email" or "Confirm deletion", and validators have other messages which are totally different such as "Invalid type given, value should be a string" or "A crc32 hash could not be evaluated for the given file".
Why would I want to insert all the validators error messages in my custom form translation file because I want to use one? This makes no sense.
Priority should be a fallback: I don't have set the validator translator or the validator default translator: then we fallback to the element translator. If the element translator is not set then I fallback to the form translator and so on.
Now lets see why the priority code is broken.
This init code sets a translator file for the project and uses the default translation for validators. As mentioned already, the call to getTranslate() sets 'Zend_Translate' in Zend_Registry:
In Form::isValid:
We look if the form has a translator.
This looks if the form has a default translator:
This in turn looks if Zend_Translate is set:
Therefore the form gets a translator which is set to each element translator:
Since the elements translators have been set, then the validator translator is overriden:
This is why Zend_Translate is set to the element translators which in turn override the default translator for Zend_Validate. Your priority list is not working as expected Rob.
Posted by David Salvador (dsalvador) on 2010-06-28T13:53:02.000+0000
$translate = new Zend_Translate('tmx', $tmxFileAndPath, $lang); $translatorForValidationMessages = new Zend_Translate('array',APPLICATION_PATH.'/resources/languages',$lang,array('scan' => Zend_Translate::LOCALE_DIRECTORY)); Zend_Validate_Abstract::setDefaultTranslator($translatorForValidationMessages);
Zend_Registry::set('Zend_Translate', $translate);
As it is, it translates the labels of the form, but leaves the validation messages in english.
And if i comment the line setting the Zend_Registry for Zend_Translate as per below: $translate = new Zend_Translate('tmx', $tmxFileAndPath, $lang); $translatorForValidationMessages = new Zend_Translate('array',APPLICATION_PATH.'/resources/languages',$lang,array('scan' => Zend_Translate::LOCALE_DIRECTORY)); Zend_Validate_Abstract::setDefaultTranslator($translatorForValidationMessages);
//Zend_Registry::set('Zend_Translate', $translate);
Then it gives me the validation errors properly translated, but the form labels remain in english.
Should this issue be reopened? I have tested it with 10.4 and 10.5, none of them work. I do not have the rights to reopen it,
Posted by David Salvador (dsalvador) on 2010-06-28T13:54:45.000+0000
As it is, it translates the labels of the form, but leaves the validation messages in english. —
And if i comment the line setting the Zend_Registry for Zend_Translate as per below:
Then it gives me the validation errors properly translated, but the form labels remain in english.
Should this issue be reopened? I have tested it with 10.4 and 10.5, none of them work. I do not have the rights to reopen it,
Posted by Christian Albrecht (alab) on 2010-06-30T14:52:51.000+0000
Reopening.
Posted by Thomas Weidner (thomas) on 2010-07-12T11:27:30.000+0000
Erasing fix version as the issue was reopened
Posted by Kim Blomqvist (kblomqvist) on 2010-07-31T12:38:13.000+0000
Zend_Form::isValid() shouldn't set element translator by it's own default translator, because this leads into situation where form element has set translator which overrides Zend_Validate's default translator.
Index: Form.php =================================================================== --- Form.php (revision 22746) +++ Form.php (working copy) @@ -2222,7 +2222,9 @@ } $context = $data; foreach ($this->getElements() as $key => $element) { - if (null !== $translator && !$element->hasTranslator()) { + if (null !== $translator && $this->hasTranslator() && + !$element->hasTranslator()) + { $element->setTranslator($translator); } $check = $data;Posted by Kim Blomqvist (kblomqvist) on 2010-08-01T00:16:02.000+0000
To make it more clear...
Init: Only global Zend_Registry's 'Zend_Translate' translator has been set
What happens? 1) Zend_Form::hasTranslator() returns false 2) Zend_Form::getTranslator() returns Zend_Registry's 'Zend_Translate' translator
3) Zend_Form_Element::hasTranslator() returns true (wrong, should be false) 4) Zend_Form_Element::getTranslator() returns $this->_translator (wrong, should be Zend_Registry's 'Zend_Translate' translator)
Why it happens? Because Zend_Form used Zend_Form_Element's setTranslator() by its default Zend_Registry's 'Zend_Translate' translator. Zend_Form should override Zend_Form_Element's validator only when its own hasTranslator() returns true && Zend_Form_Element's hasTranslator() returns false.
Posted by Kim Blomqvist (kblomqvist) on 2010-08-07T02:38:27.000+0000
Zend.diff includes patch for Zend_Form Form.diff includes new test for Zend_Form_FormTest
Posted by Rob Allen (rob) on 2010-08-13T07:39:22.000+0000
Fixed on trunk in svn r22833.
Need to check with alab before merging to 1.10 branch.
Posted by Rob Allen (rob) on 2010-08-30T03:02:21.000+0000
As 1.10.8 is out, this fix will be released with 1.11