Merge pull request #7376 from lchrusciel/api-locale-fix

[API] Fix normalization of locale codes
This commit is contained in:
Paweł Jędrzejewski 2017-02-02 11:52:23 +01:00 committed by GitHub
commit 4ce2bc8091
14 changed files with 214 additions and 53 deletions

View file

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\ApiBundle\Normalizer;
use FOS\RestBundle\Normalizer\CamelKeysNormalizer;
/**
* Normalizes the array by changing its keys from underscore to camel case, but does not perform this change if the key is an locale code.
*
* @author Łukasz Chruściel <lukasz.chrusciel@lakion.com>
*/
final class LocaleAwareCamelKeysNormalizer extends CamelKeysNormalizer
{
/**
* @param string $string
*
* @return string
*/
protected function normalizeString($string)
{
if (false === strpos($string, '_')) {
return $string;
}
// Custom logic to skip locale codes (e.g. nl_NL)
if (1 === preg_match('/^[a-z]{2}_[A-Z]{2}$/', $string)) {
return $string;
}
return preg_replace_callback('/_([a-zA-Z0-9])/', function ($matches) {
return strtoupper($matches[1]);
}, $string);
}
}

View file

@ -22,5 +22,6 @@
<argument type="service" id="fos_oauth_server.entity_manager" />
<argument>%fos_oauth_server.model.client.class%</argument>
</service>
<service id="sylius.normalizer.camel_keys" class="Sylius\Bundle\ApiBundle\Normalizer\LocaleAwareCamelKeysNormalizer" />
</services>
</container>

View file

@ -0,0 +1,114 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\ApiBundle\Tests\Normalizer;
use Sylius\Bundle\ApiBundle\Normalizer\LocaleAwareCamelKeysNormalizer;
/**
* @see \FOS\RestBundle\Tests\Normalizer\CamelKeysNormalizerTest
*
* @author Łukasz Chruściel <lukasz.chrusciel@lakion.com>
*/
final class LocaleAwareCamelKeysNormalizerTest extends \PHPUnit_Framework_TestCase
{
/**
* @test
*
* @expectedException \FOS\RestBundle\Normalizer\Exception\NormalizationException
*/
public function it_does_not_override_existing_keys()
{
$normalizer = new LocaleAwareCamelKeysNormalizer();
$normalizer->normalize([
'foo' => [
'foo_bar' => 'foo',
'foo_Bar' => 'foo',
],
]);
}
/**
* @test
*
* @dataProvider normalizeProvider
*/
public function it_normalizes_snake_cased_keys_to_camel_case(array $array, array $expected)
{
$normalizer = new LocaleAwareCamelKeysNormalizer();
$this->assertEquals($expected, $normalizer->normalize($array));
}
/**
* @test
*
* @dataProvider localeAwareNormalizeProvider
*/
public function it_does_not_normalize_locales_codes(array $array, array $expected)
{
$normalizer = new LocaleAwareCamelKeysNormalizer();
$this->assertEquals($expected, $normalizer->normalize($array));
}
/**
* @return array
*/
public function normalizeProvider()
{
$array = $this->normalizeProviderCommon();
$array[] = [[
'__username' => 'foo',
'_password' => 'bar',
'_foo_bar' => 'foobar',
], [
'_Username' => 'foo',
'Password' => 'bar',
'FooBar' => 'foobar',
]];
return $array;
}
/**
* @return array
*/
public function localeAwareNormalizeProvider()
{
$array = $this->normalizeProviderCommon();
$array[] = [['translations' => [
'en_US' => ['foo_bar' => 'mops'],
'nl_NL' => ['bar' => 'narwhale_io']],
], ['translations' => [
'en_US' => ['fooBar' => 'mops'],
'nl_NL' => ['bar' => 'narwhale_io']],
]];
return $array;
}
/**
* @return array
*/
private function normalizeProviderCommon()
{
return [
[[], []],
[
['foo' => ['Foo_bar_baz' => ['foo_Bar' => ['foo_bar' => 'foo_bar']]],
'foo_1ar' => ['foo_bar'],
],
['foo' => ['FooBarBaz' => ['fooBar' => ['fooBar' => 'foo_bar']]],
'foo1ar' => ['foo_bar'],
],
],
];
}
}

View file

@ -111,4 +111,4 @@ twig:
fos_rest:
body_listener:
array_normalizer: fos_rest.normalizer.camel_keys
array_normalizer: sylius.normalizer.camel_keys

View file

@ -71,7 +71,7 @@ class LocaleApiTest extends JsonApiTestCase
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$locales = $this->loadFixturesFromFile('resources/locales.yml');
$this->client->request('GET', '/api/v1/locales/'.$locales['locale_en']->getCode(), [], [], static::$authorizedHeaderWithAccept);
$this->client->request('GET', '/api/v1/locales/'.$locales['locale_en_US']->getCode(), [], [], static::$authorizedHeaderWithAccept);
$response = $this->client->getResponse();
$this->assertResponse($response, 'locale/show_response', Response::HTTP_OK);
@ -100,7 +100,7 @@ class LocaleApiTest extends JsonApiTestCase
$data =
<<<EOT
{
"code": "es"
"code": "es_ES"
}
EOT;
@ -156,12 +156,12 @@ EOT;
$this->loadFixturesFromFile('authentication/api_administrator.yml');
$locales = $this->loadFixturesFromFile('resources/locales.yml');
$this->client->request('DELETE', '/api/v1/locales/'.$locales['locale_en']->getCode(), [], [], static::$authorizedHeaderWithContentType, []);
$this->client->request('DELETE', '/api/v1/locales/'.$locales['locale_en_US']->getCode(), [], [], static::$authorizedHeaderWithContentType, []);
$response = $this->client->getResponse();
$this->assertResponseCode($response, Response::HTTP_NO_CONTENT);
$this->client->request('GET', '/api/v1/locales/'.$locales['locale_en']->getId(), [], [], static::$authorizedHeaderWithAccept);
$this->client->request('GET', '/api/v1/locales/'.$locales['locale_en_US']->getId(), [], [], static::$authorizedHeaderWithAccept);
$response = $this->client->getResponse();
$this->assertResponse($response, 'error/not_found_response', Response::HTTP_NOT_FOUND);

View file

@ -135,11 +135,11 @@ final class ProductApiTest extends JsonApiTestCase
{
"code": "MUG_TH",
"translations": {
"nl": {
"name": "Theme Mug",
"slug": "theme-mug2"
"nl_NL": {
"name": "Mok van het thema",
"slug": "mok-van-het-thema"
},
"en": {
"en_US": {
"name": "Theme Mug",
"slug": "theme-mug"
}
@ -179,7 +179,7 @@ EOT;
<<<EOT
{
"translations": {
"en__US": {
"en_US": {
"name": "Star Wars",
"slug": "star-wars"
}
@ -204,7 +204,7 @@ EOT;
<<<EOT
{
"translations": {
"en": {
"en_US": {
"name": "Mug Star Wars Episode V"
}
}

View file

@ -1,3 +1,3 @@
Sylius\Component\Locale\Model\Locale:
locale_{en, nl, fr}:
locale_{en_US, nl_NL, fr_FR, de_CH}:
code: <current()>

View file

@ -1,14 +1,14 @@
Sylius\Component\Core\Model\ProductTranslation:
productTranslation1:
slug: mug-star-wars
locale: en
name: 'Star wars mug'
locale: 'en_US'
name: 'Star Wars mug'
slug: 'star-wars-mug'
description: <paragraph(2)>
translatable: "@product1"
productTranslation2:
slug: star-wars-mug
locale: en_US
name: 'Star Wars mug'
locale: 'nl_NL'
name: 'Mok van het thema'
slug: 'mok-van-het-thema'
description: <paragraph(2)>
translatable: "@product1"
@ -20,27 +20,27 @@ Sylius\Component\Core\Model\ProductVariant:
Sylius\Component\Core\Model\Product:
product1:
updatedAt: 2015-10-10
fallbackLocale: en_US
currentLocale: en
code: MUG_SW
currentLocale: 'en_US'
fallbackLocale: 'en_US'
code: 'MUG_SW'
translations:
- "@productTranslation1"
- "@productTranslation2"
product2:
updatedAt: 2015-10-04
currentLocale: en_US
fallbackLocale: en_US
code: MUG_LOTR
slug: mug-lotr
currentLocale: 'en_US'
fallbackLocale: 'en_US'
code: 'MUG_LOTR'
slug: 'mug-lotr'
name: 'Lotr mug'
description: <paragraph(2)>
variants:
- "@productVariant"
product3:
updatedAt: 2015-10-05
currentLocale: en_US
fallbackLocale: en_US
code: MUG_BB
slug: mug-breaking-bad
currentLocale: 'en_US'
fallbackLocale: 'en_US'
code: 'MUG_BB'
slug: 'mug-breaking-bad'
name: 'Breaking bad mug'
description: <paragraph(2)>

View file

@ -1,6 +1,6 @@
{
"id": @integer@,
"code": "es",
"code": "es_ES",
"created_at": "@string@.isDateTime()",
"updated_at": "@string@.isDateTime()"
}

View file

@ -2,7 +2,7 @@
"page": 1,
"limit": 10,
"pages": 1,
"total": 3,
"total": 4,
"_links": {
"self": {
"href": "\/api\/v1\/locales\/?page=1&limit=10"
@ -18,17 +18,22 @@
"items": [
{
"id": @integer@,
"code": "en"
"code": "en_US"
},
{
"id": @integer@,
"code": "nl"
"code": "nl_NL"
},
{
"id": @integer@,
"code": "fr"
"code": "fr_FR"
},
{
"id": @integer@,
"code": "de_CH"
}
]
}

View file

@ -1,6 +1,6 @@
{
"id": @integer@,
"code": "en",
"code": "en_US",
"created_at": "@string@.isDateTime()",
"updated_at": "@string@.isDateTime()"
}

View file

@ -1,6 +1,6 @@
{
"id": @integer@,
"code": "en",
"code": "en_US",
"created_at": "@string@.isDateTime()",
"updated_at": "@string@.isDateTime()"
}

View file

@ -1,5 +1,6 @@
{
"id": @integer@,
"name": "Theme Mug",
"code": "MUG_TH",
"available_on": "@string@.isDateTime()",
"attributes": [],
@ -7,20 +8,17 @@
"options": [],
"associations": [],
"translations": {
"en": {
"en_US": {
"id": @integer@,
"locale": "en",
"locale": "en_US",
"name": "Theme Mug",
"slug": "theme-mug"
},
"nl": {
"nl_NL": {
"id": @integer@,
"locale": "nl",
"name": "Theme Mug",
"slug": "theme-mug2"
},
"en_US": {
"locale": "en_US"
"locale": "nl_NL",
"name": "Mok van het thema",
"slug": "mok-van-het-thema"
}
},
"product_taxons": [],

View file

@ -1,6 +1,6 @@
{
"id": @integer@,
"name": "Star wars mug",
"name": "Star Wars mug",
"code": "MUG_SW",
"available_on": "@string@.isDateTime()",
"attributes": [],
@ -8,19 +8,19 @@
"options": [],
"associations": [],
"translations": {
"en": {
"id": @integer@,
"locale": "en",
"name": "Star wars mug",
"slug": "mug-star-wars",
"description": "@string@"
},
"en_US": {
"id": @integer@,
"locale": "en_US",
"name": "Star Wars mug",
"slug": "star-wars-mug",
"description": "@string@"
},
"nl_NL": {
"id": @integer@,
"locale": "nl_NL",
"name": "Mok van het thema",
"slug": "mok-van-het-thema",
"description": "@string@"
}
},
"product_taxons": [],