Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9e82d84

Browse filesBrowse files
Merge branch '6.4' into 7.1
* 6.4: remove conflict with symfony/serializer < 6.4 Reviewed and Translated zh_CN [PropertyInfo] Fix write visibility for Asymmetric Visibility and Virtual Properties [Translation] [Bridge][Lokalise] Fix empty keys array in PUT, DELETE requests causing Lokalise API error
2 parents 5a9aba6 + f132c09 commit 9e82d84
Copy full SHA for 9e82d84

File tree

Expand file treeCollapse file tree

8 files changed

+215
-23
lines changed
Filter options
Expand file treeCollapse file tree

8 files changed

+215
-23
lines changed

‎src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
+25-5Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -700,12 +700,18 @@ private function isAllowedProperty(string $class, string $property, bool $writeA
700700
try {
701701
$reflectionProperty = new \ReflectionProperty($class, $property);
702702

703-
if ($writeAccessRequired && $reflectionProperty->isReadOnly()) {
704-
return false;
705-
}
703+
if ($writeAccessRequired) {
704+
if ($reflectionProperty->isReadOnly()) {
705+
return false;
706+
}
707+
708+
if (\PHP_VERSION_ID >= 80400 && ($reflectionProperty->isProtectedSet() || $reflectionProperty->isPrivateSet())) {
709+
return false;
710+
}
706711

707-
if (\PHP_VERSION_ID >= 80400 && $writeAccessRequired && ($reflectionProperty->isProtectedSet() || $reflectionProperty->isPrivateSet())) {
708-
return false;
712+
if (\PHP_VERSION_ID >= 80400 &&$reflectionProperty->isVirtual() && !$reflectionProperty->hasHook(\PropertyHookType::Set)) {
713+
return false;
714+
}
709715
}
710716

711717
return (bool) ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags);
@@ -946,6 +952,20 @@ private function getReadVisiblityForMethod(\ReflectionMethod $reflectionMethod):
946952

947953
private function getWriteVisiblityForProperty(\ReflectionProperty $reflectionProperty): string
948954
{
955+
if (\PHP_VERSION_ID >= 80400) {
956+
if ($reflectionProperty->isVirtual() && !$reflectionProperty->hasHook(\PropertyHookType::Set)) {
957+
return PropertyWriteInfo::VISIBILITY_PRIVATE;
958+
}
959+
960+
if ($reflectionProperty->isPrivateSet()) {
961+
return PropertyWriteInfo::VISIBILITY_PRIVATE;
962+
}
963+
964+
if ($reflectionProperty->isProtectedSet()) {
965+
return PropertyWriteInfo::VISIBILITY_PROTECTED;
966+
}
967+
}
968+
949969
if ($reflectionProperty->isPrivate()) {
950970
return PropertyWriteInfo::VISIBILITY_PRIVATE;
951971
}

‎src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
+64Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php81Dummy;
3333
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php82Dummy;
3434
use Symfony\Component\PropertyInfo\Tests\Fixtures\SnakeCaseDummy;
35+
use Symfony\Component\PropertyInfo\Tests\Fixtures\VirtualProperties;
3536
use Symfony\Component\PropertyInfo\Type as LegacyType;
3637
use Symfony\Component\TypeInfo\Type;
3738
use Symfony\Component\TypeInfo\Type\NullableType;
@@ -703,6 +704,69 @@ public function testAsymmetricVisibility()
703704
$this->assertFalse($this->extractor->isWritable(AsymmetricVisibility::class, 'protectedPrivate'));
704705
}
705706

707+
/**
708+
* @requires PHP 8.4
709+
*/
710+
public function testVirtualProperties()
711+
{
712+
$this->assertTrue($this->extractor->isReadable(VirtualProperties::class, 'virtualNoSetHook'));
713+
$this->assertTrue($this->extractor->isReadable(VirtualProperties::class, 'virtualSetHookOnly'));
714+
$this->assertTrue($this->extractor->isReadable(VirtualProperties::class, 'virtualHook'));
715+
$this->assertFalse($this->extractor->isWritable(VirtualProperties::class, 'virtualNoSetHook'));
716+
$this->assertTrue($this->extractor->isWritable(VirtualProperties::class, 'virtualSetHookOnly'));
717+
$this->assertTrue($this->extractor->isWritable(VirtualProperties::class, 'virtualHook'));
718+
}
719+
720+
/**
721+
* @dataProvider provideAsymmetricVisibilityMutator
722+
* @requires PHP 8.4
723+
*/
724+
public function testAsymmetricVisibilityMutator(string $property, string $readVisibility, string $writeVisibility)
725+
{
726+
$extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PUBLIC | ReflectionExtractor::ALLOW_PROTECTED | ReflectionExtractor::ALLOW_PRIVATE);
727+
$readMutator = $extractor->getReadInfo(AsymmetricVisibility::class, $property);
728+
$writeMutator = $extractor->getWriteInfo(AsymmetricVisibility::class, $property, [
729+
'enable_getter_setter_extraction' => true,
730+
]);
731+
732+
$this->assertSame(PropertyReadInfo::TYPE_PROPERTY, $readMutator->getType());
733+
$this->assertSame(PropertyWriteInfo::TYPE_PROPERTY, $writeMutator->getType());
734+
$this->assertSame($readVisibility, $readMutator->getVisibility());
735+
$this->assertSame($writeVisibility, $writeMutator->getVisibility());
736+
}
737+
738+
public static function provideAsymmetricVisibilityMutator(): iterable
739+
{
740+
yield ['publicPrivate', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PRIVATE];
741+
yield ['publicProtected', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PROTECTED];
742+
yield ['protectedPrivate', PropertyReadInfo::VISIBILITY_PROTECTED, PropertyWriteInfo::VISIBILITY_PRIVATE];
743+
}
744+
745+
/**
746+
* @dataProvider provideVirtualPropertiesMutator
747+
* @requires PHP 8.4
748+
*/
749+
public function testVirtualPropertiesMutator(string $property, string $readVisibility, string $writeVisibility)
750+
{
751+
$extractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PUBLIC | ReflectionExtractor::ALLOW_PROTECTED | ReflectionExtractor::ALLOW_PRIVATE);
752+
$readMutator = $extractor->getReadInfo(VirtualProperties::class, $property);
753+
$writeMutator = $extractor->getWriteInfo(VirtualProperties::class, $property, [
754+
'enable_getter_setter_extraction' => true,
755+
]);
756+
757+
$this->assertSame(PropertyReadInfo::TYPE_PROPERTY, $readMutator->getType());
758+
$this->assertSame(PropertyWriteInfo::TYPE_PROPERTY, $writeMutator->getType());
759+
$this->assertSame($readVisibility, $readMutator->getVisibility());
760+
$this->assertSame($writeVisibility, $writeMutator->getVisibility());
761+
}
762+
763+
public static function provideVirtualPropertiesMutator(): iterable
764+
{
765+
yield ['virtualNoSetHook', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PRIVATE];
766+
yield ['virtualSetHookOnly', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PUBLIC];
767+
yield ['virtualHook', PropertyReadInfo::VISIBILITY_PUBLIC, PropertyWriteInfo::VISIBILITY_PUBLIC];
768+
}
769+
706770
/**
707771
* @dataProvider typesProvider
708772
*/
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\PropertyInfo\Tests\Fixtures;
13+
14+
class VirtualProperties
15+
{
16+
public bool $virtualNoSetHook { get => true; }
17+
public bool $virtualSetHookOnly { set => $value; }
18+
public bool $virtualHook { get => true; set => $value; }
19+
}

‎src/Symfony/Component/PropertyInfo/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Component/PropertyInfo/composer.json
+1-2Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@
3737
"conflict": {
3838
"phpdocumentor/reflection-docblock": "<5.2",
3939
"phpdocumentor/type-resolver": "<1.5.1",
40-
"symfony/dependency-injection": "<6.4",
41-
"symfony/serializer": "<6.4"
40+
"symfony/dependency-injection": "<6.4"
4241
},
4342
"autoload": {
4443
"psr-4": { "Symfony\\Component\\PropertyInfo\\": "" },

‎src/Symfony/Component/Security/Core/Resources/translations/security.zh_CN.xlf

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Core/Resources/translations/security.zh_CN.xlf
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
</trans-unit>
7777
<trans-unit id="20">
7878
<source>Too many failed login attempts, please try again in %minutes% minutes.</source>
79-
<target state="needs-review-translation">登录尝试失败次数过多,请在 %minutes% 分钟后再试。|登录尝试失败次数过多,请在 %minutes% 分钟后再试。</target>
79+
<target>登录尝试失败次数过多,请在 %minutes% 分钟后重试。</target>
8080
</trans-unit>
8181
</body>
8282
</file>

‎src/Symfony/Component/Translation/Bridge/Lokalise/LokaliseProvider.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Translation/Bridge/Lokalise/LokaliseProvider.php
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ public function delete(TranslatorBagInterface $translatorBag): void
113113
$keysIds += $this->getKeysIds($keysToDelete, $domain);
114114
}
115115

116+
if (!$keysIds) {
117+
return;
118+
}
119+
116120
$response = $this->client->request('DELETE', 'keys', [
117121
'json' => ['keys' => array_values($keysIds)],
118122
]);
@@ -245,6 +249,10 @@ private function updateTranslations(array $keysByDomain, TranslatorBagInterface
245249
}
246250
}
247251

252+
if (!$keysToUpdate) {
253+
return;
254+
}
255+
248256
$response = $this->client->request('PUT', 'keys', [
249257
'json' => ['keys' => $keysToUpdate],
250258
]);

‎src/Symfony/Component/Translation/Bridge/Lokalise/Tests/LokaliseProviderTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Translation/Bridge/Lokalise/Tests/LokaliseProviderTest.php
+82Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,56 @@ public function testCompleteWriteProcess()
251251
$this->assertTrue($updateProcessed, 'Translations update was not called.');
252252
}
253253

254+
public function testUpdateProcessWhenLocalTranslationsMatchLokaliseTranslations()
255+
{
256+
$getLanguagesResponse = function (string $method, string $url): ResponseInterface {
257+
$this->assertSame('GET', $method);
258+
$this->assertSame('https://api.lokalise.com/api2/projects/PROJECT_ID/languages', $url);
259+
260+
return new MockResponse(json_encode([
261+
'languages' => [
262+
['lang_iso' => 'en'],
263+
['lang_iso' => 'fr'],
264+
],
265+
]));
266+
};
267+
268+
$failOnPutRequest = function (string $method, string $url, array $options = []): void {
269+
$this->assertSame('PUT', $method);
270+
$this->assertSame('https://api.lokalise.com/api2/projects/PROJECT_ID/keys', $url);
271+
$this->assertSame(json_encode(['keys' => []]), $options['body']);
272+
273+
$this->fail('PUT request is invalid: an empty `keys` array was provided, resulting in a Lokalise API error');
274+
};
275+
276+
$mockHttpClient = (new MockHttpClient([
277+
$getLanguagesResponse,
278+
$failOnPutRequest,
279+
]))->withOptions([
280+
'base_uri' => 'https://api.lokalise.com/api2/projects/PROJECT_ID/',
281+
'headers' => ['X-Api-Token' => 'API_KEY'],
282+
]);
283+
284+
$provider = self::createProvider(
285+
$mockHttpClient,
286+
$this->getLoader(),
287+
$this->getLogger(),
288+
$this->getDefaultLocale(),
289+
'api.lokalise.com'
290+
);
291+
292+
// TranslatorBag with catalogues that do not store any message to mimic the behaviour of
293+
// Symfony\Component\Translation\Command\TranslationPushCommand when local translations and Lokalise
294+
// translations match without any changes in both translation sets
295+
$translatorBag = new TranslatorBag();
296+
$translatorBag->addCatalogue(new MessageCatalogue('en', []));
297+
$translatorBag->addCatalogue(new MessageCatalogue('fr', []));
298+
299+
$provider->write($translatorBag);
300+
301+
$this->assertSame(1, $mockHttpClient->getRequestsCount());
302+
}
303+
254304
public function testWriteGetLanguageServerError()
255305
{
256306
$getLanguagesResponse = function (string $method, string $url, array $options = []): ResponseInterface {
@@ -723,6 +773,38 @@ public function testDeleteProcess()
723773
$provider->delete($translatorBag);
724774
}
725775

776+
public function testDeleteProcessWhenLocalTranslationsMatchLokaliseTranslations()
777+
{
778+
$failOnDeleteRequest = function (string $method, string $url, array $options = []): void {
779+
$this->assertSame('DELETE', $method);
780+
$this->assertSame('https://api.lokalise.com/api2/projects/PROJECT_ID/keys', $url);
781+
$this->assertSame(json_encode(['keys' => []]), $options['body']);
782+
783+
$this->fail('DELETE request is invalid: an empty `keys` array was provided, resulting in a Lokalise API error');
784+
};
785+
786+
// TranslatorBag with catalogues that do not store any message to mimic the behaviour of
787+
// Symfony\Component\Translation\Command\TranslationPushCommand when local translations and Lokalise
788+
// translations match without any changes in both translation sets
789+
$translatorBag = new TranslatorBag();
790+
$translatorBag->addCatalogue(new MessageCatalogue('en', []));
791+
$translatorBag->addCatalogue(new MessageCatalogue('fr', []));
792+
793+
$mockHttpClient = new MockHttpClient([$failOnDeleteRequest], 'https://api.lokalise.com/api2/projects/PROJECT_ID/');
794+
795+
$provider = self::createProvider(
796+
$mockHttpClient,
797+
$this->getLoader(),
798+
$this->getLogger(),
799+
$this->getDefaultLocale(),
800+
'api.lokalise.com'
801+
);
802+
803+
$provider->delete($translatorBag);
804+
805+
$this->assertSame(0, $mockHttpClient->getRequestsCount());
806+
}
807+
726808
public static function getResponsesForOneLocaleAndOneDomain(): \Generator
727809
{
728810
$arrayLoader = new ArrayLoader();

‎src/Symfony/Component/Validator/Resources/translations/validators.zh_CN.xlf

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Resources/translations/validators.zh_CN.xlf
+15-15Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@
136136
</trans-unit>
137137
<trans-unit id="37" resname="This is not a valid IP address.">
138138
<source>This value is not a valid IP address.</source>
139-
<target state="needs-review-translation">该值不是有效的IP地址。</target>
139+
<target>该值不是有效的IP地址。</target>
140140
</trans-unit>
141141
<trans-unit id="38">
142142
<source>This value is not a valid language.</source>
@@ -192,7 +192,7 @@
192192
</trans-unit>
193193
<trans-unit id="51" resname="No temporary folder was configured in php.ini.">
194194
<source>No temporary folder was configured in php.ini, or the configured folder does not exist.</source>
195-
<target state="needs-review-translation">php.ini 中没有配置临时文件夹,或配置的文件夹不存在。</target>
195+
<target>php.ini 中未配置临时文件夹,或配置的文件夹不存在。</target>
196196
</trans-unit>
197197
<trans-unit id="52">
198198
<source>Cannot write temporary file to disk.</source>
@@ -224,7 +224,7 @@
224224
</trans-unit>
225225
<trans-unit id="59" resname="This is not a valid International Bank Account Number (IBAN).">
226226
<source>This value is not a valid International Bank Account Number (IBAN).</source>
227-
<target state="needs-review-translation">该值不是有效的国际银行账号(IBAN)。</target>
227+
<target>该值不是有效的国际银行账号(IBAN)。</target>
228228
</trans-unit>
229229
<trans-unit id="60">
230230
<source>This value is not a valid ISBN-10.</source>
@@ -312,15 +312,15 @@
312312
</trans-unit>
313313
<trans-unit id="81" resname="This is not a valid Business Identifier Code (BIC).">
314314
<source>This value is not a valid Business Identifier Code (BIC).</source>
315-
<target state="needs-review-translation">该值不是有效的业务标识符代码(BIC)。</target>
315+
<target>该值不是有效的银行识别代码(BIC)。</target>
316316
</trans-unit>
317317
<trans-unit id="82">
318318
<source>Error</source>
319319
<target>错误</target>
320320
</trans-unit>
321321
<trans-unit id="83" resname="This is not a valid UUID.">
322322
<source>This value is not a valid UUID.</source>
323-
<target state="needs-review-translation">该值不是有效的UUID。</target>
323+
<target>该值不是有效的UUID。</target>
324324
</trans-unit>
325325
<trans-unit id="84">
326326
<source>This value should be a multiple of {{ compared_value }}.</source>
@@ -428,43 +428,43 @@
428428
</trans-unit>
429429
<trans-unit id="110">
430430
<source>The extension of the file is invalid ({{ extension }}). Allowed extensions are {{ extensions }}.</source>
431-
<target state="needs-review-translation">文件的扩展名无效 ({{ extension }})。允许的扩展名为 {{ extensions }}。</target>
431+
<target>文件的扩展名无效 ({{ extension }})。允许的扩展名为 {{ extensions }}。</target>
432432
</trans-unit>
433433
<trans-unit id="111">
434434
<source>The detected character encoding is invalid ({{ detected }}). Allowed encodings are {{ encodings }}.</source>
435-
<target state="needs-review-translation">检测到的字符编码无效 ({{ detected }})。允许的编码为 {{ encodings }}。</target>
435+
<target>检测到的字符编码无效 ({{ detected }})。允许的编码为 {{ encodings }}。</target>
436436
</trans-unit>
437437
<trans-unit id="112">
438438
<source>This value is not a valid MAC address.</source>
439-
<target state="needs-review-translation">该值不是有效的MAC地址。</target>
439+
<target>该值不是有效的MAC地址。</target>
440440
</trans-unit>
441441
<trans-unit id="113">
442442
<source>This URL is missing a top-level domain.</source>
443-
<target state="needs-review-translation">此URL缺少顶级域名。</target>
443+
<target>此URL缺少顶级域名。</target>
444444
</trans-unit>
445445
<trans-unit id="114">
446446
<source>This value is too short. It should contain at least one word.|This value is too short. It should contain at least {{ min }} words.</source>
447-
<target state="needs-translation">This value is too short. It should contain at least one word.|This value is too short. It should contain at least {{ min }} words.</target>
447+
<target>该值太短,应该至少包含一个词。|该值太短,应该至少包含 {{ min }} 个词。</target>
448448
</trans-unit>
449449
<trans-unit id="115">
450450
<source>This value is too long. It should contain one word.|This value is too long. It should contain {{ max }} words or less.</source>
451-
<target state="needs-translation">This value is too long. It should contain one word.|This value is too long. It should contain {{ max }} words or less.</target>
451+
<target>该值太长,应该只包含一个词。|该值太长,应该只包含 {{ max }} 个或更少个词。</target>
452452
</trans-unit>
453453
<trans-unit id="116">
454454
<source>This value does not represent a valid week in the ISO 8601 format.</source>
455-
<target state="needs-translation">This value does not represent a valid week in the ISO 8601 format.</target>
455+
<target>该值不代表 ISO 8601 格式中的有效周。</target>
456456
</trans-unit>
457457
<trans-unit id="117">
458458
<source>This value is not a valid week.</source>
459-
<target state="needs-translation">This value is not a valid week.</target>
459+
<target>该值不是一个有效周。</target>
460460
</trans-unit>
461461
<trans-unit id="118">
462462
<source>This value should not be before week "{{ min }}".</source>
463-
<target state="needs-translation">This value should not be before week "{{ min }}".</target>
463+
<target>该值不应位于 "{{ min }}" 周之前。</target>
464464
</trans-unit>
465465
<trans-unit id="119">
466466
<source>This value should not be after week "{{ max }}".</source>
467-
<target state="needs-translation">This value should not be after week "{{ max }}".</target>
467+
<target>该值不应位于 "{{ max }}"周之后。</target>
468468
</trans-unit>
469469
</body>
470470
</file>

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.