Scanning translatable strings in zend 3 forms with Poedit - forms

Zend 3 translates form labels automatically.
If forms are created using array specification, how is it possible to scan translatable form element strings with Poedit?
How to add translator->translate() functionality to forms? I tried the following in module.php onBootstrap method but this does not work:
$sm = $e->getApplication()->getServiceManager();
$vhm = $sm->get('ViewHelperManager');
$translator = $sm->get('MvcTranslator');
$vhm->get('form')->setTranslator($translator);
I want to use it like $form->translator->translate(), in such a way it would be possible to scan code with Poedit to find translatable labeles, placeholders etc.

Here's a TranslatorFactory if you need
final class TranslatorFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
// get translator files' paths from config
$paths = $container->get('config')['settings']['localization-paths'] ?? [];
$translator = new Translator();
// add zend-i18n-resources files to translator
$translator->addTranslationFilePattern(
'phpArray',
Resources::getBasePath(),
Resources::getPatternForValidator()
);
// add your translation files to translator
foreach ($paths as $path) {
$translator->addTranslationFilePattern('phpArray', $path, '%s.php');
}
// todo: replace with user's preferred language
$translator->setLocale('tr');
return $translator;
}
}
And add your factory to service manager
'service_manager' => [
'factories' => [
\Zend\I18n\Translator\TranslatorInterface::class => \MyModule\Factory\TranslatorFactory::class,
],
],

Not sure if you're still looking for a solution, so I'll add mine.
I use the TranslatorAwareTrait in my AbstractForm class.
use Zend\I18n\Translator\TranslatorAwareTrait;
abstract class AbstractForm extends \Zend\Form\Form implements
{
use TranslatorAwareTrait;
// Form stuff
}
Then, in the *FormFactory do the following:
use Zend\I18n\Translator\Translator;
use Zend\ServiceManager\Factory\FactoryInterface;
class SomeFormFactory implements FactoryInterface
{
/**
* #param ContainerInterface $container
* #param string $requestedName
* #param array|null $options
* #return mixed|object|AbstractForm
* #throws \Psr\Container\ContainerExceptionInterface
* #throws \Psr\Container\NotFoundExceptionInterface
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
// Obviously you'll have more/other requirements. Fulfil them here.
$form = new SomeForm();
$form->setTranslator(
$container->get('translator')
);
return $form;
}
}
Usage example:
use Zend\I18n\Translator\TranslatorAwareTrait;
abstract class AbstractForm extends \Zend\Form\Form implements
{
use TranslatorAwareTrait;
public function init()
{
if (!$this->has('submit')) {
$this->addSubmitButton();
}
}
public function addSubmitButton($value = 'Save', array $classes = null)
{
$this->add([
'name' => 'submit',
'type' => Submit::class,
'attributes' => [
'value' =>
// Translate $value before passing to this function
$value === 'Save' ? $this->getTranslator()->translate('Save') : $value,
'class' => (!is_null($classes) ? join (' ', $classes) : 'btn btn-primary'),
],
]);
}
}
On the other hand, you could...
Translate strings before passing them if you're translating with Poedit.
If your modules contain the following config (in each module!):
'translator' => [
'translation_file_patterns' => [
[
'type' => 'gettext',
'base_dir' => __DIR__ . '/../language',
'pattern' => '%s.mo',
],
],
],
You can see here that translation is done using gettext. This is a PHP module that searches for the following code strings and translates its contents: _('translatable string').
The translation files to look for end with the .mo extension and can be found in __DIR__ . '/../language'.
Thus, if you make sure to have the PHP gettext module enabled to use this.
To use this with just normal strings, even in config for a Fieldset or a form, you could have the following:
$this->add([
'name' => 'street',
'required' => true,
'type' => Text::class,
'options' => [
'label' => _('Street'), // <<-- translated using gettext
],
]);

Related

Zend_Cache understanding issue

I try to use Zend_Cache (first try) to save information about user grants. The idea and most of the source code comes from Oleg Krivtsovs tutorial.
I get an error, if I try to retrieve my cache.
Call to a member function getItem() on array
Here the implementation of FilesystemCache, in my global.php
'caches' => [
'FilesystemCache' => [
'adapter' => [
'name' => Filesystem::class,
'options' => [
// Store cached data in this directory.
'cache_dir' => './data/cache',
// Store cached data for 1 hour.
'ttl' => 60*60*1
],
],
'plugins' => [
[
'name' => 'serializer',
'options' => [
],
],
],
],
],
Here my factory class:
<?php
namespace User\Service;
use User\Controller\Plugin\AuthPlugin;
use User\Model\GrantsTable;
use User\Model\UserTable;
use Zend\Authentication\AuthenticationService;
use Zend\ServiceManager\Factory\FactoryInterface;
use Interop\Container\ContainerInterface;
class AccessControlFactory implements FactoryInterface {
public function __invoke(ContainerInterface $container, $requestedName, array $options = null) {
$config = $container->get('config');
$userTable = $container->get(UserTable::class);
$grantsTable = $container->get(GrantsTable::class);
$cache = $config['caches']['FilesystemCache'];
$userplugin = $container->get(AuthPlugin::class);
// $authentication = $container->get( \Zend\Authentication\AuthenticationService::class);
return new AccessControl($userTable, $grantsTable, $cache, $userplugin);//, $authentication
}
}
Now in the init function within my AccessControl Service, I try to retrieve from the cache:
$this->cache->getItem('rbac_container', $result);
There I get the above error.
Any help with a bit of explanation would be appreciated.
What you're injecting to the AccessControl constructor is an array, not a cache implementation, because $config['caches']['FilesystemCache'] returns an array of FilesystemCache options (adapter, plugins, etc.). What you're supposed to do is fetch the cache implementation via the ContainerInterface, like this:
$cache = $container->get('FilesystemCache');
Then the ContainerInterface will depend on StorageCacheAbstractServiceFactory to find your requested cache configs and return the class for you.

How to update images stored as strings in extbase?

I have recently created an extension that has a file upload feature. I decided to store it as a string. I have used it like this:
In the controller:
public function initializeAction() {
if ($this->arguments->hasArgument('blog')) {
$this->arguments->getArgument('blog')->getPropertyMappingConfiguration()->setTargetTypeForSubProperty('image', 'array');
}
}
In the model:
/**
* Returns the image
*
* #return string $image
*/
public function getImage()
{
return $this->image;
}
/**
* Sets the image
*
* #param \array $image
* #return void
*/
public function setImage(array $image)
{
die(debug::var_dump($image));
if (!empty($image['name']))
{
$imageName = $image['name'];
$imageTempName = $image['tmp_name'];
$basicFileUtility = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Utility\\File\\BasicFileUtility');
$imageNameNew = $basicFileUtility->getUniqueName($imageName, \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName('uploads/tx_myextension/'));
\TYPO3\CMS\Core\Utility\GeneralUtility::upload_copy_move($imageTempName, $imageNameNew);
$this->image = basename($imageNameNew);
}
}
The TCA:
'config' => [
'type' => 'group',
'internal_type' => 'file',
'uploadfolder' => 'uploads/tx_myextension',
'show_thumbs' => 1,
'size' => 1,
'allowed' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'],
'disallowed' => ''
],
In my form:
<f:form action="update" name="blog" object="{blog}" >
<f:form.upload property="image" class="form-control" />
...
Now this works perfectly when a new object is created, however when I try to change this image (using updateAction), I get the this error message:
Exception while property mapping at property path "image":
No converter found which can be used to convert from "string" to "array".
I would like to avoid uploading via FAL or writing my own conversion. I'm hoping that I just missed something trivial.
Take an look to an update script from tt_address to see how to make an update.
To make it short: You must read all entries of your files and move them from upload dir to your file storage and then you must add an file_reference which connect the sys_file with your domain model:
GeneralUtility::upload_copy_move(
PATH_site . 'uploads/tx_myextension/' . $imageName,
PATH_site . 'fileadmin/tx_myextension/' . $imageName);
$fileObject = $this->storage->getFile('fileadmin/tx_myextension/' . $imageName);
if ($fileObject instanceof \TYPO3\CMS\Core\Resource\File) {
$this->fileRepository->add($fileObject);
$dataArray = [
'uid_local' => $fileObject->getUid(),
'tablenames' => 'tx_your_domain_model',
'fieldname' => 'image',
'uid_foreign' => 'your model uid',
'table_local' => 'sys_file',
'cruser_id' => 999,
'pid' => 'your_model_pid',
'sorting_foreign' => $imageCount,
'sys_language_uid' => 0
];
if ($this->getDatabaseConnection()->exec_INSERTquery('sys_file_reference', $dataArray)) {
$imageCount++;
}
}
It was a very stupid and simple mistake from my part. In the edit form I forgot to add this: enctype="multipart/form-data"
<f:form action="update" enctype="multipart/form-data" name="blog" object="{blog}" >

Injecting variables into Forms __construct function Zend 2

I am trying to prepopulate a form's drop down options via data stored statically in the module.config.php in Zend 2 and am running into a problem which entails:
I try to get the Servicemanager in the __construct() function but it is unavailable.
I move the element declarations to another function within the form class so I can pass variables into it but the view controller cannot find the elements.
I currently call the form via a Servicemanager Invokable. How can I pass these arrays into the form's __construct() function?
Here is the code:
class ILLForm extends Form
{
public function __construct($fieldsetName, $campuses, $ILLTypes, $getFromOptions)
{
parent::__construct('create_ill');
$this
->setAttribute('method', 'post')
->setHydrator(new ClassMethodsHydrator(false))
->setInputFilter(new InputFilter())
;
$ill = new ILLFieldset('ill', $campuses, $ILLTypes, $getFromOptions);
$ill->setName('ill')
->setOptions(array(
'use_as_base_fieldset' => true,
));
$captcha = new Element\Captcha('captcha');
$captchaAdapter = new Captcha\Dumb();
$captchaAdapter->setWordlen(5);
$captcha->setCaptcha($captchaAdapter)
->setLabelAttributes(array('class' => 'sq-form-question-title'))
->setAttribute('class', 'sq-form-field required')
->setLabel("* Captcha")
->setAttribute('title', 'Help to prevent SPAM');
$submit = new Element\Submit('submit');
$submit->setAttribute('value', 'Submit ILL')
->setAttribute('class', 'sq-form-submit')
->setAttribute('title', 'Submit ILL');
$this->add($ill)
->add($captcha)
->add($submit);
}
}
The Indexcontroller Factory that calls the Form:
class IndexControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $controllers)
{
$allServices = $controllers->getServiceLocator();
$sm = $allServices->get('ServiceManager');
$smConfig = $allServices->get('config');
$campuses = $smConfig['campuses'];
$illCategories = $smConfig['ILLCategories'];
$getFromOptions = $smConfig['getFromOptions'];
$controller = new IndexController();
$controller->setCampuses($campuses);
$controller->setILLCategories($illCategories);
$controller->setGetFromOptions($getFromOptions);
$controller->setILLForm($sm->get('ILL-form'));
$controller->setILLFormFilter($sm->get('ILL-form-filter'));
//$controller->setParams($sm->get('params'));
return $controller;
}
}
and the relevant module.config.php excerpt:
'service_manager' => array(
'abstract_factories' => array(
'Zend\Cache\Service\StorageCacheAbstractServiceFactory',
'Zend\Log\LoggerAbstractServiceFactory',
),
'invokables' => array(
'ILL-form-filter' => 'ILL\Form\ILLFormFilter',
'ILL-form' => 'ILL\Form\ILLForm',
),
I ended up taking the form out of the service manager invokables in module.config.php (removed line for 'ILL-form') and call it from the indexControllerFactory.php instead
$create_ill = new Form\ILLForm('create_ill', $campuses, $illCategories, $getFromOptions);
$controller->setILLForm($create_ill);
instead of
$controller->setILLForm($sm->get('ILL-form'));

Transform forms from Symfony2 to Symfony3

I want to transform a form like this to comply with Symfony3 code :
$form = $this->createForm(new AjoutQC(array('idcolle' => $idColle,'idqc' => $question->getId())),
$question,
array('action' => $this->generateUrl('paces_colle_qc_update',
array(
'id' => $question->getId(),
'idColle' => $idColle,
'idTuteur' => $idTuteur)
),
'method' => 'PUT',
));
$form->add('submit', SubmitType::class, array('label' => 'Sauvegarder'));
Symfony3 asks for something like :
AjoutQC::class
instead of :
new AjoutQC...
I didn't find anything about it after some research.
Check these upgrade notes for forms.
It says:
Passing type instances to Form::add(), FormBuilder::add() and the FormFactory::create*() methods is not supported anymore. Pass the fully-qualified class name of the type instead.
Before:
$form = $this->createForm(new MyType());
After:
$form = $this->createForm(MyType::class);
You can use OptionsResolver and pass your data array('idcolle' => $idColle,'idqc' => $question->getId()) that you are currently passing to your form type as a third argument to createForm method:
$form = $this->createForm(AjoutQC::class, $question, array(
'action' => $this->generateUrl('paces_colle_qc_update', array(
'id' => $question->getId(),
'idColle' => $idColle,
'idTuteur' => $idTuteur,
)),
'method' => 'PUT',
'idcolle' => $idColle,
'idqc' => $question->getId(),
));
Then in your AjoutQC type you need to do something like:
use Symfony\Component\OptionsResolver\OptionsResolver;
// ..
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->idcolle = $options['idcolle'];
$this->idqc = $options['idqc'];
$builder
->add(...)
// ..
;
}
// ..
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired(array('idcolle', 'idqc'));
}
This will set idcolle, idqc options as required, that must be passed to your form type (AjoutQC).
In Symfony 3 you need to use FQCN (fully-qualified class name) instead of an instance of the form (or a string reference to a service).
$form = $this->createForm(
AjoutQC::class,
$question,
array(
'action' => $this->generateUrl(
'paces_colle_qc_update',
array(
'id' => $question->getId(),
'idColle' => $idColle,
'idTuteur' => $idTuteur
)
),
'method' => 'PUT',
'idColle' => $idColle,
'idQc' => $question->getId()
)
)
In your AjoutQC class you need to go to your configureOptions method and add the idcolle and idqc options:
class AjoutQC extends AbstractType {
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired(array('idColle','idQc'));
$resolver->setDefaults(
array(
'data_class' => 'Your\Entity\Path',
'idColle' => null,
'idQc' => null
)
);
}
}
If you are just wondering about the ::class notation, you can find information about that here: http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.class.class
::class requires php >= 5.5
If you are using <5.5 you can just simply use the FQCN e.g. MyLong\Namespace\Fully\Qualified\ClassNameType

Symfony2 Setting a default choice field selection

I am creating a form in the following manner:
$form = $this->createFormBuilder($breed)
->add('species', 'entity', array(
'class' => 'BFPEduBundle:Item',
'property' => 'name',
'query_builder' => function(ItemRepository $er){
return $er->createQueryBuilder('i')
->where("i.type = 'species'")
->orderBy('i.name', 'ASC');
}))
->add('breed', 'text', array('required'=>true))
->add('size', 'textarea', array('required' => false))
->getForm()
How can I set a default value for the species listbox?
Thank you for your response, I apologise, I think I should rephrase my question. Once I have a value that I retrieve from the model, how do I set that value as SELECTED="yes" for the corresponding value in the species choice list?
So, that select option output from the TWIG view would appear like so:
<option value="174" selected="yes">Dog</option>
You can define the default value from the 'data' attribute. This is part of the Abstract "field" type (http://symfony.com/doc/2.0/reference/forms/types/field.html)
$form = $this->createFormBuilder()
->add('status', 'choice', array(
'choices' => array(
0 => 'Published',
1 => 'Draft'
),
'data' => 1
))
->getForm();
In this example, 'Draft' would be set as the default selected value.
If you use Cristian's solution, you'll need to inject the EntityManager into your FormType class. Here is a simplified example:
class EntityType extends AbstractType{
public function __construct($em) {
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options){
$builder
->add('MyEntity', 'entity', array(
'class' => 'AcmeDemoBundle:Entity',
'property' => 'name',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('e')
->orderBy('e.name', 'ASC');
},
'data' => $this->em->getReference("AcmeDemoBundle:Entity", 3)
));
}
}
And your controller:
// ...
$form = $this->createForm(new EntityType($this->getDoctrine()->getManager()), $entity);
// ...
From Doctrine Docs:
The method EntityManager#getReference($entityName, $identifier) lets you obtain a reference to an entity for which the identifier is known, without loading that entity from the database. This is useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier.
the solution: for type entity use option "data" but value is a object. ie:
$em = $this->getDoctrine()->getEntityManager();
->add('sucursal', 'entity', array
(
'class' => 'TestGeneralBundle:Sucursal',
'property'=>'descripcion',
'label' => 'Sucursal',
'required' => false,
'data'=>$em->getReference("TestGeneralBundle:Sucursal",3)
))
I think you should simply use $breed->setSpecies($species), for instance in my form I have:
$m = new Member();
$m->setBirthDate(new \DateTime);
$form = $this->createForm(new MemberType, $m);
and that sets my default selection to the current date. Should work the same way for external entities...
If you want to pass in an array of Doctrine entities, try something like this (Symfony 3.0+):
protected $entities;
protected $selectedEntities;
public function __construct($entities = null, $selectedEntities = null)
{
$this->entities = $entities;
$this->selectedEntities = $selectedEntities;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('entities', 'entity', [
'class' => 'MyBundle:MyEntity',
'choices' => $this->entities,
'property' => 'id',
'multiple' => true,
'expanded' => true,
'data' => $this->selectedEntities,
]);
}
I don't think you should use the data option, because this does more than just setting a default value.
You're also overriding any data that's being passed to the form during creation. So basically, you're breaking
support for that feature. - Which might not matter when you're letting the user create data, but does matter when you
want to (someday) use the form for updating data.
See http://symfony.com/doc/current/reference/forms/types/choice.html#data
I believe it would be better to pass any default data during form creation. In the controller.
For example, you can pass in a class and define the default value in your class itself.
(when using the default Symfony\Bundle\FrameworkBundle\Controller\Controller)
$form = $this->createForm(AnimalType::class, [
'species' => 174 // this id might be substituted by an entity
]);
Or when using objects:
$dog = new Dog();
$dog->setSpecies(174); // this id might be substituted by an entity
$form = $this->createForm(AnimalType::class, $dog);
Even better when using a factory:
(where dog probably extends from animal)
$form = $this->createForm(AnimalType::class, DogFactory::create());
This will enable you to separate form structure and content from each other and make
your form reusable in more situations.
Or, use the preferred_choices option, but this has the side effect of moving the default option to the top of your form.
See: http://symfony.com/doc/current/reference/forms/types/choice.html#preferred-choices
$builder->add(
'species',
'entity',
[
'class' => 'BFPEduBundle:Item',
'property' => 'name',
'query_builder' => ...,
'preferred_choices' => [174] // this id might be substituted by an entity
]
);
I'm not sure what you are doing wrong here, when I build a form using form classes Symfony takes care of selecting the correct option in the list. Here's an example of one of my forms that works.
In the controller for the edit action:
$entity = $em->getRepository('FooBarBundle:CampaignEntity')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find CampaignEntity entity.');
}
$editForm = $this->createForm(new CampaignEntityType(), $entity);
$deleteForm = $this->createDeleteForm($id);
return $this->render('FooBarBundle:CampaignEntity:edit.html.twig', array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
The campaign entity type class (src: Foo\BarBundle\Form\CampaignEntityType.php):
namespace Foo\BarBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Doctrine\ORM\EntityRepository;
class CampaignEntityType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('store', 'entity', array('class'=>'FooBarBundle:Store', 'property'=>'name', 'em'=>'my_non_default_em','required' => true, 'query_builder' => function(EntityRepository $er) {return $er->createQueryBuilder('s')->orderBy('s.name', 'ASC');}))
->add('reward');
}
public function getName()
{
return 'foo_barbundle_campaignentitytype';
}
}
From the docs:
public Form createNamed(string|FormTypeInterface $type, string $name, mixed $data = null, array $options = array())
mixed $data = null is the default options. So for example I have a field called status and I implemented it as so:
$default = array('Status' => 'pending');
$filter_form = $this->get('form.factory')->createNamedBuilder('filter', 'form', $default)
->add('Status', 'choice', array(
'choices' => array(
'' => 'Please Select...',
'rejected' => 'Rejected',
'incomplete' => 'Incomplete',
'pending' => 'Pending',
'approved' => 'Approved',
'validated' => 'Validated',
'processed' => 'Processed'
)
))->getForm();
Setting default choice for symfony2 radio button
$builder->add('range_options', 'choice', array(
'choices' => array('day'=>'Day', 'week'=>'Week', 'month'=>'Month'),
'data'=>'day', //set default value
'required'=>true,
'empty_data'=>null,
'multiple'=>false,
'expanded'=> true
))
The form should map the species->id value automatically to the selected entity select field. For example if your have a Breed entity that has a OnetoOne relationship with a Species entity in a join table called 'breed_species':
class Breed{
private $species;
/**
* #ORM\OneToOne(targetEntity="BreedSpecies", mappedBy="breed")
*/
private $breedSpecies;
public function getSpecies(){
return $breedSpecies->getSpecies();
}
private function getBreedSpecies(){
return $this->$breedSpecies;
}
}
The field 'species' in the form class should pick up the species->id value from the 'species' attribute object in the Breed class passed to the form.
Alternatively, you can explicitly set the value by explicitly passing the species entity into the form using SetData():
$breedForm = $this->createForm( new BreedForm(), $breed );
$species = $breed->getBreedSpecies()->getSpecies();
$breedForm->get('species')->setData( $species );
return $this->render( 'AcmeBundle:Computer:edit.html.twig'
, array( 'breed' => $breed
, 'breedForm' => $breedForm->createView()
)
);
You can either define the right default value into the model you want to edit with this form or you can specify an empty_data option so your code become:
$form = $this
->createFormBuilder($breed)
->add(
'species',
'entity',
array(
'class' => 'BFPEduBundle:Item',
'property' => 'name',
'empty_data' => 123,
'query_builder' => function(ItemRepository $er) {
return $er
->createQueryBuilder('i')
->where("i.type = 'species'")
->orderBy('i.name', 'ASC')
;
}
)
)
->add('breed', 'text', array('required'=>true))
->add('size', 'textarea', array('required' => false))
->getForm()
;
You can use "preferred_choices" and "push" the name you want to select to the top of the list. Then it will be selected by default.
'preferred_choices' => array(1), //1 is item number
entity Field Type