[AttributeValue][Product] Translatable attribute values#7219
Conversation
GSadee
commented
Jan 9, 2017
| Q | A |
|---|---|
| Bug fix? | no |
| New feature? | yes |
| BC breaks? | yes |
| Related tickets | |
| License | MIT |
5248da2 to
2a3884c
Compare
b23149d to
b2b764c
Compare
|
|
||
| ### Attribute / AttributeBundle | ||
|
|
||
| * `AttributeValue::$localeCode` property was added to make it translatable. Now, every attribute value has a locale code to be properly displaying in different locales. All attribute values are migrated to new concept with migration `Version20170109143010`. Look at [this PR](https://github.com/Sylius/Sylius/pull/7219) if you have any problems with upgrade. |
There was a problem hiding this comment.
to be displayed properly in different locales; to the new concept;
| When I switch to the "Polish (Poland)" locale | ||
| When I view product "T-shirt banana" | ||
| Then I should see the product attribute "T-shirt material" with value "Skórka banana" | ||
| Then I should also see the product attribute "T-shirt details" with value "Banana is very good material." |
There was a problem hiding this comment.
"Banana is a very good material."
There was a problem hiding this comment.
Banana is a fruit, not a material :( 🍌
bd4155b to
ec752f3
Compare
| * {@inheritdoc} | ||
| */ | ||
| public function hasAttributeByCode($attributeCode) | ||
| public function hasAttributeByCodeAndLocale($attributeCode, $localeCode) |
There was a problem hiding this comment.
We could perhaps improve the DX here a bit and make the 2nd parameter optional. If not provided, the entity would use the currentLocale stored on it at the moment of call. WDYT?
| $defaultLocale = $this->container->getParameter('locale'); | ||
|
|
||
| $this->addSql('ALTER TABLE sylius_product_attribute_value ADD locale_code VARCHAR(255) NOT NULL'); | ||
| $this->addSql('UPDATE sylius_product_attribute_value SET locale_code = "'.$defaultLocale.'"'); |
There was a problem hiding this comment.
Why not just use SET DEFAULT in the ALTER clause instead of this?
There was a problem hiding this comment.
That would add this default locale to the column definition in the DB. I'd prefer to avoid that.
| $defaultLocale = $this->container->getParameter('locale'); | ||
|
|
||
| $this->addSql('ALTER TABLE sylius_product_attribute_value ADD locale_code VARCHAR(255) NOT NULL'); | ||
| $this->addSql('UPDATE sylius_product_attribute_value SET locale_code = "'.$defaultLocale.'"'); |
There was a problem hiding this comment.
Are you sure it works? The first SQL query will probably fail if there are any records in the database.
| And the store has a product "T-shirt banana" | ||
| And this product has text attribute "T-shirt material" with value "Banana skin" in "English (United States)" locale | ||
| And this product has text attribute "T-shirt material" with value "Skórka banana" in "Polish (Poland)" locale | ||
| And this product has textarea attribute "T-shirt details" with value "Banana is a very good material." in "English (United States)" locale |
There was a problem hiding this comment.
Yes, because I want to test if using a fallback locale when attribute has no value in current locale is working correctly ; )
|
|
||
| /** | ||
| * @Given /^(this product) has ([^"]+) attribute "([^"]+)" with value "([^"]+)"$/ | ||
| * @Given /^(this product) has ([^"]+) attribute "([^"]+)" with value "([^"]+)" in ("[^"]+" locale)$/ |
There was a problem hiding this comment.
([^"]+) attribute -> (.+?) attribute
There was a problem hiding this comment.
Why? What is the difference?
There was a problem hiding this comment.
Matching every character except " does not make any sense here, let's match all the characters instead.
| * @param string|null $code | ||
| * | ||
| * @return ProductAttributeInterface | ||
| */ |
| sprintf('ProductAttribute "%s" should have value "%s" but it does not.', $attribute, $value) | ||
| $this->updateSimpleProductPage->getAttributeValue($attributeName, $language), | ||
| sprintf( | ||
| 'Product attribute "%s" should have value "%s" in language "%s" but it does not.', |
| * @param string[] $localeCodes | ||
| * | ||
| * @return FormView | ||
| * @return array |
…ext attributes in different locales
…attribute values to a product in different locales
…ale on the product page
ec752f3 to
dea6b21
Compare
dea6b21 to
111f9ce
Compare
| { | ||
| $attribute = $this->createProductAttribute($productAttributeType, $productAttributeName); | ||
| $attributeValue = $this->createProductAttributeValue($value, $attribute); | ||
| public function thisProductHasAttributeWithValue( |
There was a problem hiding this comment.
Wouldn't it be worthy to extract ProductAttributes to separate context?
| * @return ProductAttributeInterface | ||
| */ | ||
| private function provideProductAttribute($type, $name, $code = null) | ||
| { |
There was a problem hiding this comment.
$code = $code?:StringInflector::nameToCode($name);
| if (null !== $productAttribute) { | ||
| return $productAttribute; | ||
| } | ||
|
|
There was a problem hiding this comment.
I'm not sure about it. Shouldn't we create an attribute before first usage of it?
| public function buildForm(FormBuilderInterface $builder, array $options) | ||
| { | ||
| $builder | ||
| ->add('localeCode') |
There was a problem hiding this comment.
Shouldn't it be
->add('localeCode', LocaleChoiceType::class, [
'constraints' => [
new NotBlank(['groups' => ['sylius']]),
],
])And then we will use a transformer:
$builder->get('localeCode')->addModelTransformer(
new ReversedTransformer(new ResourceToIdentifierTransformer($this->localeRepository, 'code'))
);With this solution we will be sure, that locale exist in the repository
| ); | ||
|
|
||
| foreach ($attributes as $attribute) { | ||
| foreach ($localeCodes as $localeCode) { |
There was a problem hiding this comment.
Can we extract this foreach at least to another private method?
There was a problem hiding this comment.
Any idea? When I extract this to another method, this method will have to return array and then I will have to merge arrays.. I know that current solution isn't clear, but I have no simple idea how to do it clearer.
There was a problem hiding this comment.
/**
* @param FormEvent $event
*/
public function preSetData(FormEvent $event)
{
/** @var ProductInterface $product */
$product = $event->getData();
$localeCodes = $this->localeProvider->getDefinedLocalesCodes();
$defaultLocaleCode = $this->localeProvider->getDefaultLocaleCode();
$attributes = $product->getAttributes()->filter(
function (ProductAttributeValueInterface $attribute) use ($defaultLocaleCode) {
return $attribute->getLocaleCode() === $defaultLocaleCode;
}
);
foreach ($attributes as $attribute) {
$this->resolveLocalizedAttributes($localeCodes, $product, $attribute);
}
}
/**
* @param array $localeCodes
* @param ProductInterface $product
* @param ProductAttributeInterface $productAttribute
*/
private function resolveLocalizedAttributes(array $localeCodes, ProductInterface $product, ProductAttributeInterface $productAttribute)
{
foreach ($localeCodes as $localeCode) {
if (!$product->hasAttributeByCodeAndLocale($productAttribute->getCode(), $localeCode)) {
$attributeValue = $this->createProductAttributeValue($productAttribute->getAttribute(), $localeCode);
$product->addAttribute($attributeValue);
}
}
}or
/**
* @param FormEvent $event
*/
public function preSetData(FormEvent $event)
{
/** @var ProductInterface $product */
$product = $event->getData();
$defaultLocaleCode = $this->localeProvider->getDefaultLocaleCode();
$attributes = $product->getAttributes()->filter(
function (ProductAttributeValueInterface $attribute) use ($defaultLocaleCode) {
return $attribute->getLocaleCode() === $defaultLocaleCode;
}
);
foreach ($attributes as $attribute) {
$this->resolveLocalizedAttributes($product, $attribute);
}
}
/**
* @param ProductInterface $product
* @param ProductAttributeInterface $productAttribute
*/
private function resolveLocalizedAttributes(ProductInterface $product, ProductAttributeInterface $productAttribute)
{
$localeCodes = $this->localeProvider->getDefinedLocalesCodes();
foreach ($localeCodes as $localeCode) {
if (!$product->hasAttributeByCodeAndLocale($productAttribute->getCode(), $localeCode)) {
$attributeValue = $this->createProductAttributeValue($productAttribute->getAttribute(), $localeCode);
$product->addAttribute($attributeValue);
}
}
}Anyway, we should add Assert::isInstanceOf(ProductInterface) just after $product = $event->getData();
| if ($attribute->getAttribute()->getCode() === $attributeCode) { | ||
| if ($attribute->getAttribute()->getCode() === $attributeCode && | ||
| $attribute->getLocaleCode() === $localeCode) { | ||
| return true; |
There was a problem hiding this comment.
Didn't notice that the if above has and... My bad. Anyway, the if above will fit one line. And also we have to decide for some convention with multiple conditions in on if statement. Here you have && at the end of first line, and a few lines below the || is at the beginning of a second one...
| foreach ($this->attributes as $attribute) { | ||
| if ($attributeCode === $attribute->getAttribute()->getCode()) { | ||
| if ($attribute->getAttribute()->getCode() === $attributeCode && | ||
| $attribute->getLocaleCode() === $localeCode) { |
| public function getAttributeByCode($attributeCode) | ||
| public function getAttributeByCodeAndLocale($attributeCode, $localeCode = null) | ||
| { | ||
| if (null === $localeCode) { |
There was a problem hiding this comment.
$localeCode = $localeCode:?$this->getTranslation()->getLocale();
|
|
||
| $attributeValueInDifferentLocale = $this->getAttributeByCodeAndLocale($attributeValue->getCode(), $localeCode); | ||
| if ('' === $attributeValueInDifferentLocale->getValue() | ||
| || null === $attributeValueInDifferentLocale->getValue()) { |
There was a problem hiding this comment.
empty($attributeValueInDifferentLocale->getValue()) will be equivalent to this if. And we will remove an or condition
There was a problem hiding this comment.
empty('0') === true, so I guess it won't.
|
|
||
| $attributeValueInDifferentLocale = $this->getAttributeByCodeAndLocale($attributeValue->getCode(), $localeCode); | ||
| if ('' === $attributeValueInDifferentLocale->getValue() | ||
| || null === $attributeValueInDifferentLocale->getValue()) { |
There was a problem hiding this comment.
empty($attributeValueInDifferentLocale->getValue()) will be equivalent to this if. And we will remove an or condition
There was a problem hiding this comment.
empty('0') === true, so I guess it won't.
|
Nice work Grzesiu, thank you! Apply @lchrusciel's comments in a separate PR if you agree, please. |
