I am currently on a project where i have a lot of entities with a lot of fields (clients with addresses, phones, age, firm number...), and i am doing it for a French client. So i code in English:
class Client
{
/**
* #var integer
*/
private $id;
/**
* #var string
*/
private $firstName;
}
And i have a config like this:
AppBundle\Entity\Client:
type: entity
table: null
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
firstName:
type: string
length: 255
options:
label: Prénom
So the options.label doesn't seem to work, i'm wondering where i could do these translations as it will concern a lot of fields, and as I am using Sonata admin i don't want to be obliged to put them in the ClientAdmin class:
/**
* #param FormMapper $formMapper
*/
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('firstName')
->add('lastName')
->add('birthDate', 'birthday')
;
}
/**
* #param ShowMapper $showMapper
*/
protected function configureShowFields(ShowMapper $showMapper)
{
$showMapper
->add('id')
->add('firstName')
->add('lastName')
->add('birthDate')
;
}
because i would have to translate at least twice (form, display...) so I would love it to handle one translation in all the app.
Any idea ? I looked into the Gedmo translatable extension, but it doesn't correspond to what I am looking for: i simply want to translate the labels of the form, the whole application will be in a unique language: french.
Symfony 2.6
Doctrine 2.2 to 2.5
For those who don't know it, turned out that the automatic labels are already translation keys, you simply need to specify the translation_domain in your form, so i have now a messages.fr.yml:
First Name: Prénom
And it's enough! No need to generate / specify a translation label. Plus it is reusable in between forms with similar fields ! This works within our out of sonata admin's focus.
You should take a look at this. It will help you automate the translation process. http://jmsyst.com/bundles/JMSTranslationBundle
You'll just have to use keys instead of real labels(although it's optional in your case), like this:
$formMapper
->add('firstName',null, array('label'=>'sonata.user.firstName')
Then you just run a command and it will extract all the keys in a nice web GUI, and you can translate them from there in whatever language you want.
try this:
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Client',
'label' => 'Prénom'
));
}
Related
I'm updating Symfony.
When I upgraded to Symfony3, all the "State." Names displayed on the form changed to numbers.
All state values are set in services.yml. By reversing the yml value, the display in the browser was restored, but when I tried to save the value selected in the pull-down in the form, the system works to save the number instead of "State.".
Is there any good solution?
Also, I don't want to use `` `choices_as_value``` because it will not be usable in the future.
Code
Type
$builder->add("prefId", ChoiceType::class, array(
"required" => true,
"choices" => Parameters::getParameter("state"),
));
services.yml
state:
//Symfoyn 2
1: "Alabama"
//Symfony 3
"Alabama": 1
Version
Symfony3.0.9
PHP 5.6
I solved the problem by changing array_keys in Parameter.php to array_reverse. Thanks to Cerad.
//Parameter.php
/**
*
*
* #return array
*/
public static function getState()
{
return self::$container->getParameter("state");
}
/**
*
*
* #return array
*/
public static function getStateKeys()
{
//return array_keys(self::getState());
return array_reverse(self::getState());
}
I'm creating a form using built-in Symfony3 services:
1) creating new class in AppBundle\Form which extends AbstractType
2) creating a form object in my controller (using createForm())
3) pushing that object directly to twig layer (by createView())
In my Entities direction I've got two classes, already mapped to database by ORM.
First one is User, and the second one is UserAttribute. User is related to UserAttribute by OneToMany annotation. Relationship looks like:
class UserAttr
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="userAttr" )
* #ORM\JoinColumn(nullable=false)
*/
private $user;
And from the User side:
class User
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="UserAttr", mappedBy="user")
* #ORM\JoinColumn(nullable=false)
*/
private $userAttr;
When I'm adding new fields (using $builder->add()) everything works fine if they are associated to User class properties. But if I'm doing the same with UserAttribute properties - symfony can't find get/set methods for that properties. I know - I can fix it by class User extends UserAttribute but probably that's not the point. Symfony must have another solution for that, probably I missed something.
Thanks for your time !
// SOLVED | there should be defined an EntityClassType as below:
$builder->add('credit',EntityType::class,array(
'class' => UserAttr::class
));
You have One-To-Many Association to UserAttr from User Entity. Hence, A User might have multiple credit.
OPTION 1 :
Considering this, you have to use a collection field type in UserFormType, which is a bit lengthy process.
$builder->add('userAttr', CollectionType::class, array(
'label' => false,
'allow_add' => true,
'allow_delete' => true,
'entry_type' => UserAttrType::class
));
Then create another FormType : UserAttrType to represent UserAttr where you can have the credit field as a property from UserAttr.
$builder
->add('credit', TextType::class, array(
'label' => 'Credit',
))
This way, The form will load and submit accordingly, The credit value will also be updated when User form gets updated. Here is a link for collection docs. This is how to embed a collection form.
OPTION 2 :
But, If you want to simplify it further, add mapped = false (doc) to the credit field. This will ignore the current error. However, you have to manually collect the credit value from Form Object and set value to appropriate UserAttr Object in Submit Handler.
Hope it helps!
So here is the scenario: I have a radio button group. Based on their value, I should or shouldn't validate other three fields (are they blank, do they contain numbers, etc).
Can I pass all these values to a constraint somehow, and compare them there?
Or a callback directly in the controller is a better way to solve this?
Generally, what is the best practice in this case?
I suggest you to use a callback validator.
For example, in your entity class:
<?php
use Symfony\Component\Validator\Constraints as Assert;
/**
* #Assert\Callback(methods={"myValidation"})
*/
class Setting {
public function myValidation(ExecutionContextInterface $context)
{
if (
$this->getRadioSelection() == '1' // RADIO SELECT EXAMPLE
&&
( // CHECK OTHER PARAMS
$this->getFiled1() == null
)
)
{
$context->addViolation('mandatory params');
}
// put some other validation rule here
}
}
Otherwise you can build your own custom validator as described here.
Let me know you need more info.
Hope this helps.
You need to use validation groups. This allows you to validate an object against only some constraints on that class. More information can be found in the Symfony2 documentation http://symfony.com/doc/current/book/validation.html#validation-groups and also http://symfony.com/doc/current/book/forms.html#validation-groups
In the form, you can define a method called setDefaultOptions, that should look something like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
// some other code here ...
$builder->add('SOME_FIELD', 'password', array(
'constraints' => array(
new NotBlank(array(
'message' => 'Password is required',
'groups' => array('SOME_OTHER_VALIDATION_GROUP'),
)),
)
))
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function (FormInterface $form) {
$groups = array('Default');
$data = $form->getData();
if ($data['SOME_OTHER_FIELD']) { // then we want password to be required
$groups[] = 'SOME_OTHER_VALIDATION_GROUP';
}
return $groups;
}
));
}
The following link provides a detailed example of how you can make use them http://web.archive.org/web/20161119202935/http://marcjuch.li:80/blog/2013/04/21/how-to-use-validation-groups-in-symfony/.
Hope this helps!
For anyone that may still care, whilst a callback validator is perfectly acceptable for simpler dependencies an expression validator is shorter to implement.
For example if you've got a field called "Want a drink?" then if yes (true) "How many?" (integer), you could simplify this with:
/**
* #var bool|null $wantDrink
* #ORM\Column(name="want_drink", type="boolean", nullable=true)
* #Assert\NotNull()
* #Assert\Type(type="boolean")
*/
private $wantDrink;
/**
* #var int|null $howManyDrinks
* #ORM\Column(name="how_many_drinks", type="integer", nullable=true)
* #Assert\Type(type="int")
* #Assert\Expression(
* "true !== this.getWantDrink() or (null !== this.getHowManyDrinks() and this.getHowManyDrinks() >= 1)",
* message="This value should not be null."
* )
*/
private $howManyDrinks;
You write the expression in PASS context, so the above is saying that $howManyDrinks must be a non-null integer at least 1 if $wantDrink is true, otherwise we don't care about $howManyDrinks. Make use of the expression syntax, which is sufficient for a broad range of scenarios.
Another scenario I find myself frequently using a expression validator are when I've got two fields to the effect of "date start" and "date end", so that they can each ensure that they are the right way around (so that the start date is before or equal to the end date and the end date is greater or equal to the start date).
I am developing an application and I came across the following: Lets say I have an entity called Contact, that Contact belongs to a Company and the Company has a Primary Contact and a Secondary Contact and also has the remaining Contacts which I've named Normal.
My question is, what is the best approach for this when talking about entities properties and also form handling. I've though about two things:
Having 2 fields on the Company entity called PrimaryContact and SecondaryContact and also have a one-to-many relationship to a property called contacts.
What I don't like (or I'm not 100% how to do) about this option is that on the Contact entity I would need an inversedBy field for each of the 2 one-to-one properties and also 1 for the one-to-many relationship and my personal thought is that this is kind of messy for the purpose.
Having a property on the Contact entity called Type which would hold if it's primary, secondary or normal and in the Company methods that has to do with Contacts I would modify it and add the getPrimaryContact, getSecondaryContact, etc.
What I don't like about this option is that I would need to have 2 unmapped properties for the Company and I would need to do a lot on the form types in order to get this to work smoothly.
My question is what is the best approach for this structure and how to deal with forms and these dependencies. Let me know if this is not clear enough and I will take time and preparate an example with code and images.
I'm not yet a Symfony expert but i'm currently learning entites manipulation and relations !
And there is not simple way to do relations with attributes.
You have to create an entity that represent your relation.
Let's suppose you have an entity Company and and entity Contact
Then you will have an entity named CompanyContact whick will represent the relation between your objects. (you can have as many attributes as you wish in your relation entity). (Not sure for the Many-to-One for your case but the idea is the same)
<?php
namespace My\Namespace\Entity
use Doctrine\ORM\Mapping as ORM
/**
* #ORM\Entity(repositoryClass="My\Namespace\Entity\CompanyContactRepository")
*/
class CompanyContact
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="contact_type", type="string", length=255)
*/
private $contactType;
/**
* #ORM\ManyToOne(targetEntity="My\Namespace\Entity\Company")
* #ORM\JoinColumn(nullable=false)
*/
private $company;
/**
* #ORM\ManyToOne(targetEntity="My\Namespace\Entity\Contact")
* #ORM\JoinColumn(nullable=false)
*/
private $contact;
}
And in your controller you can do this:
$em = $this->getDoctrine()->getManager();
$company = $em->getRepository('YourBundle:Company')->find($yourCompanyId);
$yourType = "primary";
$companyContacts = $em->getRepository('YourBundle:CompanyContact')
->findBy(array('company' => $company, 'type' => $yourType));
What do you think about this approach ?
If i learn more soon i will get you posted ;)
Thanks to #Cerad this is the following approach I took:
I have a OneToMany property on the Company to hold all the contacts.
Implemented the getPrimaryContact/setPrimaryContact methods and looped through all the contacts and retrieving the one of the type I want. Did the same for the secondary.
On the Form type of the company my issue was that I had the 'mapped' => 'false' option, I removed this since I implemented the getters and setters SF2 knows it has to go to these methods.
`
<?php
namespace XYZ\Entity;
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks()
*/
class Company
{
...
/**
* #ORM\OneToMany(targetEntity="\XYZ\Entity\Contact", mappedBy="company", cascade={"persist", "remove"})
*/
private $contacts;
public function getPrimaryContact() { ... }
public function setPrimaryContact(Contact $contact) { //Set the type of $contact and add it $this->addContact($contact) }
public function getSecondaryContact() { ... }
public function setSecondaryContact(Contact $contact) { //Set the type of $contact and add it $this->addContact($contact) }
}`
And for the Form Type I have:
`
class CompanyType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
...
->add('primaryContact', new ContactType())
->add('secondaryContact', new ContactType())
}
...
}`
With this set everything runs smoothly and I can CRUD without much struggle.
I have an app using symfony 2.2 that has a file upload form, and another 'reportbuilder' form.
The issue I'm having is that when any of my input field values begin with a 'c', that letter is dropped. So if the field is submitted with a value of 'cat', the value after binding will become 'at'.
This only appears to occur with strings that begin with 'c'
This issue happens on any strings on any of my forms
The issue happens during the $form->bind() method (or somewhere therein - I've verified that the post variables are correct up to that point)
This issue occurs on my PREPROD box, but not my DEV box (environments SHOULD be the same, haven't done an extensive comparison to find a needle in a haystack ... both Redhat installs)
Based on some searching I've done, I suspect it may have something to do with character encoding (which I have attempted to compare between environments), but I'm somewhat at a loss.
I can provide some code if it helps, though since the issue is only occurring on one server and not another, I'm not sure which (if any) of the symfony code will help.
Does any of this stand out as a rookie encoding oversight or something like that?
Edit: This happens with any number of leading 'c's, so 'cat' and 'ccat' and 'Ccccccat' will all be converted to 'at'
Edit2: I'm able to manually set the text field from the post variable after bind as a workaround( $document->setName($postvars['name']) ). It becomes more of an issue with the 'Reportbuilder' form, which has a variable number of nested forms (report has one or more tabs, tabs have one or more extra columns, etc) - so a similar workaround is clunky and not ideal
Edit3: Adding code
class DefaultController extends Controller
{
public function indexAction()
{
...
$document = new Document();
$form = $this->createForm(new DocumentType($em,$user), $document);
/// Here the name variable in the request is 'cat', as expected
$form->bind($this->getRequest());
/// Here the value of the 'name' parameter in the form is 'at'
...
}
}
document.orm.yml:
Finance\GstBundle\Entity\Document:
type: entity
manyToOne:
report:
targetEntity: Report
mappedBy: report
user:
targetEntity: Finance\UserBundle\Entity\User
mappedBy: user
oneToMany:
doctabs:
targetEntity: Doctab
mappedBy: document
tabgroups:
targetEntity: Tabgroup
mappedBy: document
table: document
fields:
id:
type: integer
id: true
generator:
strategy: AUTO
name:
type: string
length: 255
nullable: true
path:
type: string
length: 255
... and the DocumentType definition:
namespace Finance\GstBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class DocumentType extends AbstractType
{
protected $em;
protected $user;
public function __construct($em=null,$user=null){
$this->em = $em;
$this->user = $user;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('report','entity',array(
'class' => 'FinanceGstBundle:Report',
'property'=>'name',
'empty_value' => '--Choose a Template--'
))
->add('name')
->add('file')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Finance\GstBundle\Entity\Document'
));
}
public function getName()
{
return 'finance_gstbundle_documenttype';
}
}
After patching RHEL on all 3 (dev/pre/prod) boxes, the issue has resolved itself. I'm doing a post-mortem to attempt to identify the specific package that caused the bug we were experiencing.
Our versions of RHEL were in different 'states' on the different servers. PRE/PROD were signifigantly further behind in system patches than Dev.
Problem: where 'cat' becomes 'at'?
1 Disable javascript to make sure it doesn't update your name value on submit.
2 Make sure the value submitted is 'cat'
$form->get('name')->getData(); //should return 'cat'
Note: it's mentioned you've done this in your post but we don't see the code.
3 You might have a listener somewhere that reacts to POST_BIND:
$builder->addEventListener(FormEvents::POST_BIND, $yourListener);
4 Maybe your using a data transformer, and there is something wrong with the way you transform() and reverseTransform(). A good way to debug would be to check your ViewModel in the NormModel and ModelData.
4 Maybe you have a lifecycle callback
/** #PrePersist */
public function doStuffOnPrePersist()
{
$this->name = 'changed from prePersist callback!';
}