Perform operation on an entity before its persistence - symfony

I am trying to do a PUT request on an entity, from an angular 5 client to API Platform.
On the angular side, I retrieve an address via Google maps. This address is a property of an Entity JourneyAddress, so I send a PUT request to API Platform with my JourneyAddress model, with an address property which is an array of google map address components (street_address, locality, etc.).
Now I need to perform some operations on this property before submitting it to Doctrine, ie I need to match the locality given by google with a Locality in our Database.
I was thinking about a listener which would listen for JourneyAddress prePersist and preUpdate events, find the locality instance I need with something like LocalityRepository::findBy(['name' => 'Paris']), update the journeyAddress instance and give it back to Doctrine to perform persist/update operations.
The problem is that API Platform checks if the type of the data submitted corresponds to what Doctrine expects. I sent API Platform an array, but Doctrine actually expects a string.
For context, the array sent could be :
src/Doctrine/EventListener/JourneyAddressListener.php:32:
object(App\Entity\JourneyAddress)[2615]
private 'id' => null
private 'title' => string 'dzfkdqsmlfjsldkflm' (length=18)
private 'search' => string 'mlsqjfkldsjfsqdjlmf' (length=19)
private 'address' =>
array (size=8)
'street_number' => string '2650' (length=4)
'route' => string 'Avenida Rivadavia' (length=17)
'sublocality_level_1' => string 'Balvanera' (length=9)
'administrative_area_level_2' => string 'Comuna 3' (length=8)
'administrative_area_level_1' => string 'Buenos Aires' (length=12)
'country' => string 'Argentine' (length=9)
'postal_code' => string 'C1034' (length=5)
'postal_code_suffix' => string 'ACS' (length=3)
private 'latitude' => float 50.6507791
private 'longitude' => float 3.0657951
private 'media' => null
private 'indication' => string 'klqsjflkmqjfkqjfksflmlqfmlks' (length=28)
I need to extract the street_address and save it as the address property of my JourneyAddress, but the Doctrine entity is :
/**
* #ORM\Column(type="string", length=255)
* #Groups("journey")
* #Assert\Type("string")
* #Assert\NotBlank(
* message = "Le champs 'Adresse du point de départ' doit être rempli",
* groups={"departureFormValidation"}
* )
*/
private $address;
Is there a way that my listener will be used before actual API Platform type-checking ? I also tried to do a custom operation but the result was the same, type-checking always comes first and prevents any further action.
I could of course replace the type of address by array and then send ['my string'], but I feed it should not be that way.

Doctrine listeners are always executed after validation. There are built in api-platform (Symfony) event listeners that are executed before validation: https://api-platform.com/docs/core/events/ PRE_VALIDATE seems like a good place for this.
However, having mixed data type (array, string) for the same field is not a good idea, consider using a separate model class for your array address or a separate unmapped field.

Related

Symfony form has ManyToOne field but still requires the __tostring() function

In a Symfony entity that stores the address information of a client I have made a ManyToOne connection with a entity that contains all countries. So the entity has de following link:
/**
* #var string
*
* #ORM\Column(name="country_code", type="string", length=2)
*
* #ORM\ManyToOne(targetEntity="Country")
* #ORM\JoinColumn(name="country_code", referencedColumnName="country_code")
*/
private $countryCode;
In the form generated of this entity I have defined it like this:
->add('countryCode', 'entity', array(
'class' => 'MyBundle:Country',
'choice_label' => 'name_en',
'choice_value' => 'country_code',
'data' => 'nl',
))
So it does not store the primary key but the country_code a two letter code like "nl" for the Netherlands.
Then I have to add __tostring() code to make it work, but why is that? I though the __tostring function would not be required anymore as there is already a ManyToOne connection.
public function __toString()
{
return strval($this->countryCode);
}
First in your entity you just have to write this :
/**
* #ORM\ManyToOne(targetEntity="PathTo/YourBundle/Entity/Country")
*/
private $country;
Then $country will be a reference to a Country Entity (it's ID in the DB),
and will allow you to acces ALL IT'S FIELDS.
After that in your form you should not use
->add('country', 'entity', array(....
As this syntax is deprecated and use instead :
use Symfony\Bridge\Doctrine\Form\Type\EntityType; //don't forget it on top of your form file
->add('country', EntityType::class, array(....
As you probably want to order your countries by alphabetical order you will use a query to do so and eventually your code may look like :
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
...
->add('country', EntityType::class, array(
'class' => 'MyBundle:Country',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->orderBy('c.name_en', 'ASC');
},
'choice_label' => 'name_en',
'data' => 'nl'
))
you normally don't have to worry about choice_value, which will be the unique ID of the chosen country : when you'll acces your Client Entity, you will do :
$client->getCountry()->getCountryCode() ;
to get the country code.
I guess you are missing something about how database stores the relationships.
The first problem: you don't need this:
#ORM\Column(name="country_code", type="string", length=2)
... Because #ORM\ManyToOne already describes the schema for this property.
And here we meet the second problem: #ORM\ManyToOne creates an integer column, which contains an ID of the appropriate Country entity. Which means, when you create a form field for this property, it doesn't know which property should it render as a string representation, because your address book (or whatever you called it) entity stores only digits, not the country code.

symfony2 entities data not persisted

I'm trying to persist a new entity, a "member", which has a contact field. The contact field is another entity, composed of a mail and a phone field. When I try to persist a new member, I add an email and a phone number, but the phone is not persisted. I did a var_dump over the object in my controller, and I found out that the private phone => null but doctrine added a new attribute : public phones => <the actual phone number>. This attribute doesnt exist in my entity.. What did I do wrong ? the relation between member and contact is
#ORM\OneToOne(targetEntity="Interne\FichierBundle\Entity\Contact", cascade={"persist", "remove"})
Thanks a lot for your help
EDIT :
result of the var_dump over my"member", for the contact entry :
private 'contact' =>
object(Interne\FichierBundle\Entity\Contact)[891]
private 'id' => null
private 'telephone' => null
private 'email' => string 'test#gmail.com' (length=25)
public 'telephones' => float 2187187392749
As you can see, telephone is empty, but telephones isnt. Problem is, there are no telephones attribute in my entities.
Probably your setter is not ok. Please check that your telephone setter method is:
public function setTelephone($telephone)
{
$this->telephone = $telephone;
}

Choice Multiple = true, create new Entries

My Entity
/**
* Set friend
*
* #param \Frontend\ChancesBundle\Entity\UserFriends $friend
* #return ChanceRequest
*/
public function setFriend(\Frontend\ChancesBundle\Entity\UserFriends $friend = null)
{
$this->friend = $friend;
return $this;
}
My Action
$task = new ChanceRequest();
$form = $this->createFormBuilder($task)
->add('friend', 'choice', array(
'required' => true,
'expanded' => true,
'choices' => $fb_friends,
'multiple' => true,
'mapped' => true
))
->getForm();
Because setFriend is expecting a scalar, I cannot validate this or save it to db. It is an array from how many friends the user want to send a message to somebody. How can I change it?
I have seen here a post:
Symfony2 Choice : Expected argument of type "scalar", "array" given
but this don't work that I put an array in front of \Frontend or $friend. I guess because of the related table.
What do I have to do in Entity to get it work?
If friends could be found in your database (for example it is a User entity), you should declare ManyToMany relationship for these two tables. If not, make friend property to be a doctrine array type. if friends is not a valid entity class, all the rest you have is to make a custom validator and datatransformer. Otherwise you have nothing left to do. All this information you can find in the official symfony and doctrine documentation.
How to create a Custom Validation Constraint
How to use Data Transformers
Association Mapping
Working with Associations

Disable auto translation for input values in Symfony 2

Symfony2 automatically translate the input field values with type decimal or integer.
I have a two languages for my app: arabic and english
I created an Entity with the following field:
/**
* #var float $price
*
* #ORM\Column(name="price", type="decimal", scale=2, nullable=true)
*
* #Assert\Regex(pattern="/^[0-9]+(\.\d{1,2})?$/",message="Incorrect price.")
* #Assert\Type(type="float")
* #Assert\Min(0)
*/
private $price;
In form I let the sf to guess a field type:
$builder->add('price')
I load the form for editing this entity in Arabic Interface.
In the price field I see ١٢٫٤ instead of 12.40.
I can't save the form because HTML5 validation is failed.
If I enter 12.40 in the current field and save Entity, 12 will be saved, instead of 12.40.
Why? How to disable it? how to validate the Arabic digits?
Any suggestions?
EDIT: solved, see below
I found the answer why it happens here
As you can see, symfony register a ViewTransformer for these widget types:
$builder->addViewTransformer(
new IntegerToLocalizedStringTransformer(
$options['precision'],
$options['grouping'],
$options['rounding_mode']
));
Current transformer transform an integer value to localized string. It happens for the number widget (NumberToLocalizedStringTransformer) and money widget (MoneyToLocalizedStringTransformer) too.
So I think need to register a new FieldType which will not used a ViewTransformer.
EDIT: I solved the problem just disabling intl extension and now all numeric fields are using a default english numbers. If you enable the intl extension you should use only localized numbers in the input values, it's a default behavior.
This is an old post, but i hope my answer will help anyone encounters this issue.
Instead of disabling intl extension as suggested above, you can use Data Transformers in form type to manually set the default locale to English and that will let the fields to be shown with English digits.
1) Use Data Transformers to interrupt (hook into) the process of showing the field data.
2) Manually set Locale to English.
here is what i've done with dateOfBirth field in the form type:
$builder -> add(
$builder -> create(
'dateOfBirth', DateType::class, array(
'label' => 'Date of Birth',
'widget' => 'single_text',
'required' => false,
'html5' => false,
'attr' => ['class' => 'form-control']
)) -> addModelTransformer(new CallbackTransformer(
function($dateOfBirth) {
\Locale::setDefault('en');
return $dateOfBirth;
},
function($dateOfBirth) {
return $dateOfBirth;
}
))
);

How can I remove an individual parameter from a Symfony2 request object

I have the following request object and would like to remove 'email_suffix' from a controller before binding to a form. Is this possible?
public 'request' =>
object(Symfony\Component\HttpFoundation\ParameterBag)[8]
protected 'parameters' =>
array
'registration' =>
array
'email' => string 's' (length=1)
'email_suffix' => string 'y.com' (length=5)
'password' => string '1234' (length=4)
'_token' => string '967d99ba9f955aa67eb9eb004bd331151d816d06' (length=40)
'product_id' => string '2' (length=1)
'product_description' => string '12 month membership' (length=19)
'product_price' => string '6.99' (length=4)
I have tried $request->request->remove("registration[email_suffix]");
I can do $request->request->remove("registration") - this works.
For now, I am doing this:
$requestReg = $request->request->get('registration');
$requestReg['email'] = $requestReg['email'].'#'.$requestReg['email_suffix'];
unset($requestReg['email_suffix']);
$request->request->set('registration',$requestReg);
There's the possibility to add and to remove the parameters from the request object in symfony2.
You have to look at ParameterBag Component, there's such the method called remove($key), that's what you need.
So the solution for your request would be like this, if you call it from controller object:
$this->get('request')->query->remove('email_suffix');
I am not sure, if your call $request->request is typo.
You should operate with $request->attributes which represents ParameterBag class.
If you'll go through methods in ParameterBag you'll see that there is no way to unset variable inside array.

Resources