Reputation: 21
I'm working with an Expense entity containing Price information, for which I have chosen to split the amount (int) from the currency (string).
/**
* @var integer
*
* @ORM\Column(name="price_amount", type="integer")
*/
private $priceAmount;
/**
* @var string
*
* @ORM\Column(name="price_currency", type="string", length=10)
*/
private $priceCurrency;
I am using the MoneyBundle (through TBBC bundle) to have the creation form display the list of supported currencies and letting the user choose the correct one. After validation of the form, the price currency is stored as a string in the database.
// ExpenseType.php
[...]
use Tbbc\MoneyBundle\Form\Type\CurrencyType;
[...]
$builder
->add('priceAmount', NumberType::class, array(
'label' => 'Amount spent',
'attr' => array(
'placeholder' => "Amount",
'autocomplete' => 'off'
)))
->add('priceCurrency', CurrencyType::class, array(
'label' => 'Currency'
))
I am trying to create another form type ExpenseEditType (extends the ExpenseType above) that will help the user modify the amount (amount itself + its currency); Obviously when I load the ExpenseEditType form symfony tries to create a CurrencyType from my doctrine entity, but it only finds a string (e.g. "CAD", "EUR", etc.) in the database.
$form = $this->createForm(new ExpenseEditType(), $expense);
Exception raised:
Expected argument of type "Currency", "string" given
My question is: how can I create the same kind of form than for the creation one, and ask symfony to display the same list of currency, selecting a default value with what it finds in my price.currency field?
I am thinking of 2 options (but I might be totally wrong): - working with DataTransformer, but only beginning with Symfony, I really don't know anything about this kind of stuff - having an extra Money field in my Expense entity that will store the amount+currency information in one single Money field, on top of my 2 separated fields (amount / currency); I would like to stick with these 2 separated fields for better querying possibility and lisibility in the database.
Thanks a lot for your help and advices! Vincent
Upvotes: 0
Views: 1194
Reputation: 21
Thanks to Heah and Jason suggestion, I went with DataTransformer solution and it's working well :) I based my code on the official documentation.
Find below the result:
StringToCurrencyTransformer.php
<?php
namespace VP\AccountsBundle\Form\DataTransformer;
// some uses
class StringToCurrencyTransformer implements DataTransformerInterface {
private $manager;
public function __construct(ObjectManager $manager) {
$this->manager = $manager;
}
/**
* Transforms a String (currency_str) to a Currency
*
* @param String|null $currency_str
* @return Currency The Currency Object
* @throws UnknownCurrencyException if Currency is not found
*/
public function transform($currency_str) {
if (null === $currency_str) {
return;
}
try {
$currency = new Currency($currency_str);
} catch (UnknownCurrencyException $ex) {
throw new TransformationFailedException(
sprintf(
'The currency "%s" does not exist', $currency_str
));
}
return $currency;
}
/**
* Transforms a Currency (currency) to a String
* @param Currency $currency
* @return String|null The ISO code of the currency
* @throws TransformationFailedException if currency is not found
*/
public function reverseTransform($currency) {
// if no currency provided
if (!$currency) {
return;
}
$currency_str = $currency->getName();
return $currency_str;
}
}
My new ExpenseEditType.php
<?php
// some uses
class ExpenseEditType extends ExpenseType {
private $manager;
public function __construct(ObjectManager $manager) {
$this->manager = $manager;
}
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
parent::buildForm($builder, $options);
$builder->remove('submitnew')
->remove('priceCurrency')
->add('priceCurrency', CurrencyType::class, array(
'label' => 'Devise',
'invalid_message' => 'Currency not found'
))
->add('submitnew', SubmitType::class, array(
'label' => 'Edit expense',
'attr' => array(
'class' => 'btn btn-primary',
)
));
$builder->get('priceCurrency')
->addModelTransformer(new StringToCurrencyTransformer($this->manager));
}
/**
* @return string
*/
public function getName() {
return 'vp_accountsbundle_expenseedit';
}
My service.yml:
services:
app.form.type.expenseedit:
class: VP\AccountsBundle\Form\ExpenseEditType
arguments: ["@doctrine.orm.entity_manager"]
tags:
- { name: form.type }
My call from the controller:
$form = $this->createForm(ExpenseEditType::class, $expense);
Thanks again folks!
Upvotes: 2