Merge branch '5.2' into 5.x
* 5.2: (23 commits) [Console] Fix Windows code page support [SecurityBundle] Allow ips parameter in access_control accept comma-separated string [Form] Add TranslatableMessage support to choice_label option of ChoiceType Remove code that deals with legacy behavior of PHP_Incomplete_Class [Config][DependencyInjection] Uniformize trailing slash handling [PropertyInfo] Make ReflectionExtractor correctly extract nullability [PropertyInfo] fix attribute namespace with recursive traits [PhpUnitBridge] Fix tests with `@doesNotPerformAssertions` annotations Check redis extension version [Security] Update Russian translations [Notifier] Fix return SentMessage then Messenger not used [VarExporter] Add support of PHP enumerations [Security] Added missing Japanese translations [Security] Added missing Polish translations [Security] Add missing Italian translations #41051 [Security] Missing translations pt_BR getProtocolVersion may return null Fix return type on isAllowedProperty method Make FailoverTransport always pick the first transport [TwigBridge] Fix HTML for translatable custom-file label in Bootstrap 4 theme ...
This commit is contained in:
commit
0c1261ebe1
@ -239,7 +239,7 @@ class SymfonyTestsListenerTrait
|
||||
}
|
||||
|
||||
if ($this->checkNumAssertions) {
|
||||
$this->checkNumAssertions = $test->getTestResultObject()->isStrictAboutTestsThatDoNotTestAnything() && !$test->doesNotPerformAssertions();
|
||||
$this->checkNumAssertions = $test->getTestResultObject()->isStrictAboutTestsThatDoNotTestAnything();
|
||||
}
|
||||
|
||||
$test->getTestResultObject()->beStrictAboutTestsThatDoNotTestAnything(false);
|
||||
@ -268,7 +268,10 @@ class SymfonyTestsListenerTrait
|
||||
$groups = Test::getGroups($className, $test->getName(false));
|
||||
|
||||
if ($this->checkNumAssertions) {
|
||||
if (!self::$expectedDeprecations && !$test->getNumAssertions() && $test->getTestResultObject()->noneSkipped()) {
|
||||
$assertions = \count(self::$expectedDeprecations) + $test->getNumAssertions();
|
||||
if ($test->doesNotPerformAssertions() && $assertions > 0) {
|
||||
$test->getTestResultObject()->addFailure($test, new RiskyTestError(sprintf('This test is annotated with "@doesNotPerformAssertions", but performed %s assertions', $assertions)), $time);
|
||||
} elseif ($assertions === 0 && $test->getTestResultObject()->noneSkipped()) {
|
||||
$test->getTestResultObject()->addFailure($test, new RiskyTestError('This test did not perform any assertions'), $time);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bridge\PhpUnit\Tests\FailTests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
|
||||
|
||||
/**
|
||||
* This class is deliberately suffixed with *TestRisky.php so that it is ignored
|
||||
* by PHPUnit. This test is designed to fail. See ../expectrisky.phpt.
|
||||
*/
|
||||
final class NoAssertionsTestRisky extends TestCase
|
||||
{
|
||||
use ExpectDeprecationTrait;
|
||||
|
||||
/**
|
||||
* Do not remove this test in the next major version.
|
||||
*
|
||||
* @group legacy
|
||||
*/
|
||||
public function testOne()
|
||||
{
|
||||
$this->expectNotToPerformAssertions();
|
||||
$this->expectDeprecation('foo');
|
||||
@trigger_error('foo', \E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not remove this test in the next major version.
|
||||
*/
|
||||
public function testTwo()
|
||||
{
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
}
|
22
src/Symfony/Bridge/PhpUnit/Tests/expectrisky.phpt
Normal file
22
src/Symfony/Bridge/PhpUnit/Tests/expectrisky.phpt
Normal file
@ -0,0 +1,22 @@
|
||||
--TEST--
|
||||
Test NoAssertionsTestRisky risky test
|
||||
--FILE--
|
||||
<?php
|
||||
$test = realpath(__DIR__.'/FailTests/NoAssertionsTestRisky.php');
|
||||
passthru('php '.getenv('SYMFONY_SIMPLE_PHPUNIT_BIN_DIR').'/simple-phpunit.php --fail-on-risky --colors=never '.$test);
|
||||
?>
|
||||
--EXPECTF--
|
||||
PHPUnit %s by Sebastian Bergmann and contributors.
|
||||
|
||||
%ATesting Symfony\Bridge\PhpUnit\Tests\FailTests\NoAssertionsTestRisky
|
||||
R. 2 / 2 (100%)
|
||||
|
||||
Time: %s, Memory: %s
|
||||
|
||||
There was 1 risky test:
|
||||
|
||||
1) Symfony\Bridge\PhpUnit\Tests\FailTests\NoAssertionsTestRisky::testOne
|
||||
This test is annotated with "@doesNotPerformAssertions", but performed 1 assertions
|
||||
|
||||
OK, but incomplete, skipped, or risky tests!
|
||||
Tests: 2, Assertions: 1, Risky: 1.
|
@ -121,11 +121,12 @@
|
||||
{% block file_widget -%}
|
||||
<{{ element|default('div') }} class="custom-file">
|
||||
{%- set type = type|default('file') -%}
|
||||
{{- block('form_widget_simple') -}}
|
||||
{%- set label_attr = label_attr|merge({ class: (label_attr.class|default('') ~ ' custom-file-label')|trim }) -%}
|
||||
{%- set input_lang = 'en' -%}
|
||||
{% if app is defined and app.request is defined %}{%- set input_lang = app.request.locale -%}{%- endif -%}
|
||||
<label for="{{ form.vars.id }}" lang="{{ input_lang }}" {% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}>
|
||||
{%- set attr = {lang: input_lang} | merge(attr) -%}
|
||||
{{- block('form_widget_simple') -}}
|
||||
{%- set label_attr = label_attr|merge({ class: (label_attr.class|default('') ~ ' custom-file-label')|trim }) -%}
|
||||
<label for="{{ form.vars.id }}" {% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}>
|
||||
{%- if attr.placeholder is defined and attr.placeholder is not none -%}
|
||||
{{- translation_domain is same as(false) ? attr.placeholder : attr.placeholder|trans({}, translation_domain) -}}
|
||||
{%- endif -%}
|
||||
|
@ -1026,7 +1026,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
|
||||
foreach ($ips as $ip) {
|
||||
$container->resolveEnvPlaceholders($ip, null, $usedEnvs);
|
||||
|
||||
if (!$usedEnvs && !$this->isValidIp($ip)) {
|
||||
if (!$usedEnvs && !$this->isValidIps($ip)) {
|
||||
throw new \LogicException(sprintf('The given value "%s" in the "security.access_control" config option is not a valid IP address.', $ip));
|
||||
}
|
||||
|
||||
@ -1084,6 +1084,25 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
|
||||
return new MainConfiguration($this->factories, $this->userProviderFactories);
|
||||
}
|
||||
|
||||
private function isValidIps($ips): bool
|
||||
{
|
||||
$ipsList = array_reduce((array) $ips, static function (array $ips, string $ip) {
|
||||
return array_merge($ips, preg_split('/\s*,\s*/', $ip));
|
||||
}, []);
|
||||
|
||||
if (!$ipsList) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($ipsList as $cidr) {
|
||||
if (!$this->isValidIp($cidr)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function isValidIp(string $cidr): bool
|
||||
{
|
||||
$cidrParts = explode('/', $cidr);
|
||||
|
@ -388,6 +388,33 @@ class SecurityExtensionTest extends TestCase
|
||||
$this->assertEquals($secure, $definition->getArgument(3)['secure']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider acceptableIpsProvider
|
||||
*/
|
||||
public function testAcceptableAccessControlIps($ips)
|
||||
{
|
||||
$container = $this->getRawContainer();
|
||||
|
||||
$container->loadFromExtension('security', [
|
||||
'providers' => [
|
||||
'default' => ['id' => 'foo'],
|
||||
],
|
||||
'firewalls' => [
|
||||
'some_firewall' => [
|
||||
'pattern' => '/.*',
|
||||
'http_basic' => [],
|
||||
],
|
||||
],
|
||||
'access_control' => [
|
||||
['ips' => $ips, 'path' => '/somewhere', 'roles' => 'IS_AUTHENTICATED_FULLY'],
|
||||
],
|
||||
]);
|
||||
|
||||
$container->compile();
|
||||
|
||||
$this->assertTrue(true, 'Ip addresses is successfully consumed: '.(\is_string($ips) ? $ips : json_encode($ips)));
|
||||
}
|
||||
|
||||
public function testCustomRememberMeHandler()
|
||||
{
|
||||
$container = $this->getRawContainer();
|
||||
@ -430,6 +457,16 @@ class SecurityExtensionTest extends TestCase
|
||||
];
|
||||
}
|
||||
|
||||
public function acceptableIpsProvider(): iterable
|
||||
{
|
||||
yield [['127.0.0.1']];
|
||||
yield ['127.0.0.1'];
|
||||
yield ['127.0.0.1, 127.0.0.2'];
|
||||
yield ['127.0.0.1/8, 127.0.0.2/16'];
|
||||
yield [['127.0.0.1/8, 127.0.0.2/16']];
|
||||
yield [['127.0.0.1/8', '127.0.0.2/16']];
|
||||
}
|
||||
|
||||
public function testSwitchUserWithSeveralDefinedProvidersButNoFirewallRootProviderConfigured()
|
||||
{
|
||||
$container = $this->getRawContainer();
|
||||
|
@ -203,7 +203,7 @@ trait RedisTrait
|
||||
}
|
||||
|
||||
try {
|
||||
@$redis->{$connect}($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ['stream' => $params['ssl'] ?? null]);
|
||||
@$redis->{$connect}($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...\defined('Redis::SCAN_PREFIX') ? [['stream' => $params['ssl'] ?? null]] : []);
|
||||
|
||||
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
|
||||
$isConnected = $redis->isConnected();
|
||||
@ -266,7 +266,7 @@ trait RedisTrait
|
||||
}
|
||||
|
||||
try {
|
||||
$redis = new $class(null, $hosts, $params['timeout'], $params['read_timeout'], (bool) $params['persistent'], $params['auth'] ?? '', $params['ssl'] ?? null);
|
||||
$redis = new $class(null, $hosts, $params['timeout'], $params['read_timeout'], (bool) $params['persistent'], $params['auth'] ?? '', ...\defined('Redis::SCAN_PREFIX') ? [$params['ssl'] ?? null] : []);
|
||||
} catch (\RedisClusterException $e) {
|
||||
throw new InvalidArgumentException(sprintf('Redis connection "%s" failed: ', $dsn).$e->getMessage());
|
||||
}
|
||||
|
@ -76,8 +76,8 @@ abstract class FileLoader extends Loader
|
||||
$excluded = [];
|
||||
foreach ((array) $exclude as $pattern) {
|
||||
foreach ($this->glob($pattern, true, $_, false, true) as $path => $info) {
|
||||
// normalize Windows slashes
|
||||
$excluded[str_replace('\\', '/', $path)] = true;
|
||||
// normalize Windows slashes and remove trailing slashes
|
||||
$excluded[rtrim(str_replace('\\', '/', $path), '/')] = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,6 +127,28 @@ class FileLoaderTest extends TestCase
|
||||
$this->assertCount(2, $loadedFiles);
|
||||
$this->assertNotContains('ExcludeFile.txt', $loadedFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider excludeTrailingSlashConsistencyProvider
|
||||
*/
|
||||
public function testExcludeTrailingSlashConsistency(string $exclude)
|
||||
{
|
||||
$loader = new TestFileLoader(new FileLocator(__DIR__.'/../Fixtures'));
|
||||
$loadedFiles = $loader->import('ExcludeTrailingSlash/*', null, false, null, $exclude);
|
||||
$this->assertCount(2, $loadedFiles);
|
||||
$this->assertNotContains('baz.txt', $loadedFiles);
|
||||
}
|
||||
|
||||
public function excludeTrailingSlashConsistencyProvider(): iterable
|
||||
{
|
||||
yield [__DIR__.'/../Fixtures/Exclude/ExcludeToo/'];
|
||||
yield [__DIR__.'/../Fixtures/Exclude/ExcludeToo'];
|
||||
yield [__DIR__.'/../Fixtures/Exclude/ExcludeToo/*'];
|
||||
yield [__DIR__.'/../Fixtures/*/ExcludeToo'];
|
||||
yield [__DIR__.'/../Fixtures/*/ExcludeToo/'];
|
||||
yield [__DIR__.'/../Fixtures/Exclude/ExcludeToo/*'];
|
||||
yield [__DIR__.'/../Fixtures/Exclude/ExcludeToo/AnotheExcludedFile.txt'];
|
||||
}
|
||||
}
|
||||
|
||||
class TestFileLoader extends FileLoader
|
||||
|
@ -177,6 +177,7 @@ class GlobResourceTest extends TestCase
|
||||
|
||||
$expected = [
|
||||
$dir.'/Exclude/ExcludeToo/AnotheExcludedFile.txt',
|
||||
$dir.'/ExcludeTrailingSlash/exclude/baz.txt',
|
||||
$dir.'/foo.xml',
|
||||
];
|
||||
|
||||
|
@ -110,11 +110,6 @@ class QuestionHelper extends Helper
|
||||
$inputStream = $this->inputStream ?: \STDIN;
|
||||
$autocomplete = $question->getAutocompleterCallback();
|
||||
|
||||
if (\function_exists('sapi_windows_cp_set')) {
|
||||
// Codepage used by cmd.exe on Windows to allow special characters (éàüñ).
|
||||
@sapi_windows_cp_set(1252);
|
||||
}
|
||||
|
||||
if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
|
||||
$ret = false;
|
||||
if ($question->isHidden()) {
|
||||
@ -514,7 +509,10 @@ class QuestionHelper extends Helper
|
||||
private function readInput($inputStream, Question $question)
|
||||
{
|
||||
if (!$question->isMultiline()) {
|
||||
return fgets($inputStream, 4096);
|
||||
$cp = $this->setIOCodepage();
|
||||
$ret = fgets($inputStream, 4096);
|
||||
|
||||
return $this->resetIOCodepage($cp, $ret);
|
||||
}
|
||||
|
||||
$multiLineStreamReader = $this->cloneInputStream($inputStream);
|
||||
@ -523,6 +521,7 @@ class QuestionHelper extends Helper
|
||||
}
|
||||
|
||||
$ret = '';
|
||||
$cp = $this->setIOCodepage();
|
||||
while (false !== ($char = fgetc($multiLineStreamReader))) {
|
||||
if (\PHP_EOL === "{$ret}{$char}") {
|
||||
break;
|
||||
@ -530,7 +529,37 @@ class QuestionHelper extends Helper
|
||||
$ret .= $char;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
return $this->resetIOCodepage($cp, $ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set console I/O to the host code page.
|
||||
*
|
||||
* @return int Previous code page in IBM/EBCDIC format
|
||||
*/
|
||||
private function setIOCodepage(): int
|
||||
{
|
||||
if (\function_exists('sapi_windows_cp_set')) {
|
||||
$cp = sapi_windows_cp_get();
|
||||
sapi_windows_cp_set(sapi_windows_cp_get('oem'));
|
||||
|
||||
return $cp;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set console I/O to the specified code page and convert the user input.
|
||||
*/
|
||||
private function resetIOCodepage(int $cp, string $input): string
|
||||
{
|
||||
if (\function_exists('sapi_windows_cp_set') && 0 < $cp) {
|
||||
sapi_windows_cp_set($cp);
|
||||
$input = sapi_windows_cp_conv(sapi_windows_cp_get('oem'), $cp, $input);
|
||||
}
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -180,8 +180,8 @@ abstract class FileLoader extends BaseFileLoader
|
||||
$excludePrefix = $resource->getPrefix();
|
||||
}
|
||||
|
||||
// normalize Windows slashes
|
||||
$excludePaths[str_replace('\\', '/', $path)] = true;
|
||||
// normalize Windows slashes and remove trailing slashes
|
||||
$excludePaths[rtrim(str_replace('\\', '/', $path), '/')] = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,6 +244,35 @@ class FileLoaderTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider excludeTrailingSlashConsistencyProvider
|
||||
*/
|
||||
public function testExcludeTrailingSlashConsistency(string $exclude)
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures'));
|
||||
$loader->registerClasses(
|
||||
new Definition(),
|
||||
'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\',
|
||||
'Prototype/*',
|
||||
$exclude
|
||||
);
|
||||
|
||||
$this->assertTrue($container->has(Foo::class));
|
||||
$this->assertFalse($container->has(DeeperBaz::class));
|
||||
}
|
||||
|
||||
public function excludeTrailingSlashConsistencyProvider(): iterable
|
||||
{
|
||||
yield ['Prototype/OtherDir/AnotherSub/'];
|
||||
yield ['Prototype/OtherDir/AnotherSub'];
|
||||
yield ['Prototype/OtherDir/AnotherSub/*'];
|
||||
yield ['Prototype/*/AnotherSub'];
|
||||
yield ['Prototype/*/AnotherSub/'];
|
||||
yield ['Prototype/*/AnotherSub/*'];
|
||||
yield ['Prototype/OtherDir/AnotherSub/DeeperBaz.php'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP 8
|
||||
*
|
||||
|
@ -359,7 +359,6 @@ class FlattenException
|
||||
return ['array', '*SKIPPED over 10000 entries*'];
|
||||
}
|
||||
if ($value instanceof \__PHP_Incomplete_Class) {
|
||||
// is_object() returns false on PHP<=7.1
|
||||
$result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)];
|
||||
} elseif (\is_object($value)) {
|
||||
$result[$key] = ['object', \get_class($value)];
|
||||
|
@ -20,6 +20,7 @@ use Symfony\Component\Form\ChoiceList\Loader\FilterChoiceLoaderDecorator;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
use Symfony\Component\Translation\TranslatableMessage;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ChoiceListFactoryInterface}.
|
||||
@ -182,7 +183,14 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
|
||||
// If "choice_label" is set to false and "expanded" is true, the value false
|
||||
// should be passed on to the "label" option of the checkboxes/radio buttons
|
||||
$dynamicLabel = $label($choice, $key, $value);
|
||||
$label = false === $dynamicLabel ? false : (string) $dynamicLabel;
|
||||
|
||||
if (false === $dynamicLabel) {
|
||||
$label = false;
|
||||
} elseif ($dynamicLabel instanceof TranslatableMessage) {
|
||||
$label = $dynamicLabel;
|
||||
} else {
|
||||
$label = (string) $dynamicLabel;
|
||||
}
|
||||
}
|
||||
|
||||
$view = new ChoiceView(
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\Form\ChoiceList\View;
|
||||
|
||||
use Symfony\Component\Translation\TranslatableMessage;
|
||||
|
||||
/**
|
||||
* Represents a choice in templates.
|
||||
*
|
||||
@ -35,11 +37,11 @@ class ChoiceView
|
||||
/**
|
||||
* Creates a new choice view.
|
||||
*
|
||||
* @param mixed $data The original choice
|
||||
* @param string $value The view representation of the choice
|
||||
* @param string|false $label The label displayed to humans; pass false to discard the label
|
||||
* @param array $attr Additional attributes for the HTML tag
|
||||
* @param array $labelTranslationParameters Additional parameters used to translate the label
|
||||
* @param mixed $data The original choice
|
||||
* @param string $value The view representation of the choice
|
||||
* @param string|TranslatableMessage|false $label The label displayed to humans; pass false to discard the label
|
||||
* @param array $attr Additional attributes for the HTML tag
|
||||
* @param array $labelTranslationParameters Additional parameters used to translate the label
|
||||
*/
|
||||
public function __construct($data, string $value, $label, array $attr = [], array $labelTranslationParameters = [])
|
||||
{
|
||||
|
@ -21,6 +21,7 @@ use Symfony\Component\Form\ChoiceList\Loader\FilterChoiceLoaderDecorator;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
|
||||
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
|
||||
use Symfony\Component\Translation\TranslatableMessage;
|
||||
|
||||
class DefaultChoiceListFactoryTest extends TestCase
|
||||
{
|
||||
@ -759,6 +760,24 @@ class DefaultChoiceListFactoryTest extends TestCase
|
||||
$this->assertFlatViewWithAttr($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires function Symfony\Component\Translation\TranslatableMessage::__construct
|
||||
*/
|
||||
public function testPassTranslatableMessageAsLabelDoesntCastItToString()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
$this->list,
|
||||
[$this->obj1],
|
||||
static function ($choice, $key, $value) {
|
||||
return new TranslatableMessage('my_message', ['param1' => 'value1']);
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(TranslatableMessage::class, $view->choices[0]->label);
|
||||
$this->assertEquals('my_message', $view->choices[0]->label->getMessage());
|
||||
$this->assertArrayHasKey('param1', $view->choices[0]->label->getParameters());
|
||||
}
|
||||
|
||||
public function testCreateViewFlatLabelTranslationParametersAsArray()
|
||||
{
|
||||
$view = $this->factory->createView(
|
||||
|
@ -1502,7 +1502,7 @@ class Request
|
||||
* if the proxy is trusted (see "setTrustedProxies()"), otherwise it returns
|
||||
* the latter (from the "SERVER_PROTOCOL" server parameter).
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getProtocolVersion()
|
||||
{
|
||||
|
@ -95,6 +95,9 @@ final class SlackTransport extends AbstractTransport
|
||||
throw new TransportException(sprintf('Unable to post the Slack message: "%s".', $result['error']), $response);
|
||||
}
|
||||
|
||||
return new SentMessage($message, (string) $this);
|
||||
$sentMessage = new SentMessage($message, (string) $this);
|
||||
$sentMessage->setMessageId($result['ts']);
|
||||
|
||||
return $sentMessage;
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ final class SlackTransportTest extends TransportTestCase
|
||||
|
||||
$response->expects($this->once())
|
||||
->method('getContent')
|
||||
->willReturn(json_encode(['ok' => true]));
|
||||
->willReturn(json_encode(['ok' => true, 'ts' => '1503435956.000247']));
|
||||
|
||||
$expectedBody = json_encode(['channel' => $channel, 'text' => $message]);
|
||||
|
||||
@ -131,7 +131,9 @@ final class SlackTransportTest extends TransportTestCase
|
||||
|
||||
$transport = $this->createTransport($client, $channel);
|
||||
|
||||
$transport->send(new ChatMessage('testMessage'));
|
||||
$sentMessage = $transport->send(new ChatMessage('testMessage'));
|
||||
|
||||
$this->assertSame('1503435956.000247', $sentMessage->getMessageId());
|
||||
}
|
||||
|
||||
public function testSendWithNotification()
|
||||
@ -147,7 +149,7 @@ final class SlackTransportTest extends TransportTestCase
|
||||
|
||||
$response->expects($this->once())
|
||||
->method('getContent')
|
||||
->willReturn(json_encode(['ok' => true]));
|
||||
->willReturn(json_encode(['ok' => true, 'ts' => '1503435956.000247']));
|
||||
|
||||
$notification = new Notification($message);
|
||||
$chatMessage = ChatMessage::fromNotification($notification);
|
||||
@ -167,7 +169,9 @@ final class SlackTransportTest extends TransportTestCase
|
||||
|
||||
$transport = $this->createTransport($client, $channel);
|
||||
|
||||
$transport->send($chatMessage);
|
||||
$sentMessage = $transport->send($chatMessage);
|
||||
|
||||
$this->assertSame('1503435956.000247', $sentMessage->getMessageId());
|
||||
}
|
||||
|
||||
public function testSendWithInvalidOptions()
|
||||
|
@ -49,9 +49,7 @@ final class Chatter implements ChatterInterface
|
||||
public function send(MessageInterface $message): ?SentMessage
|
||||
{
|
||||
if (null === $this->bus) {
|
||||
$this->transport->send($message);
|
||||
|
||||
return null;
|
||||
return $this->transport->send($message);
|
||||
}
|
||||
|
||||
if (null !== $this->dispatcher) {
|
||||
|
63
src/Symfony/Component/Notifier/Tests/ChatterTest.php
Normal file
63
src/Symfony/Component/Notifier/Tests/ChatterTest.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Notifier\Tests;
|
||||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\Notifier\Chatter;
|
||||
use Symfony\Component\Notifier\Message\SentMessage;
|
||||
use Symfony\Component\Notifier\Tests\Transport\DummyMessage;
|
||||
use Symfony\Component\Notifier\Transport\TransportInterface;
|
||||
|
||||
class ChatterTest extends TestCase
|
||||
{
|
||||
/** @var MockObject&TransportInterface */
|
||||
private $transport;
|
||||
|
||||
/** @var MockObject&MessageBusInterface */
|
||||
private $bus;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->transport = $this->createMock(TransportInterface::class);
|
||||
$this->bus = $this->createMock(MessageBusInterface::class);
|
||||
}
|
||||
|
||||
public function testSendWithoutBus()
|
||||
{
|
||||
$message = new DummyMessage();
|
||||
|
||||
$sentMessage = new SentMessage($message, 'any');
|
||||
|
||||
$this->transport
|
||||
->expects($this->once())
|
||||
->method('send')
|
||||
->with($message)
|
||||
->willReturn($sentMessage);
|
||||
|
||||
$chatter = new Chatter($this->transport);
|
||||
$this->assertSame($sentMessage, $chatter->send($message));
|
||||
$this->assertSame($message, $sentMessage->getOriginalMessage());
|
||||
}
|
||||
|
||||
public function testSendWithBus()
|
||||
{
|
||||
$message = new DummyMessage();
|
||||
|
||||
$this->transport
|
||||
->expects($this->never())
|
||||
->method('send')
|
||||
->with($message);
|
||||
|
||||
$this->bus
|
||||
->expects($this->once())
|
||||
->method('dispatch')
|
||||
->with($message)
|
||||
->willReturn(new Envelope(new \stdClass()));
|
||||
|
||||
$chatter = new Chatter($this->transport, $this->bus);
|
||||
$this->assertNull($chatter->send($message));
|
||||
}
|
||||
}
|
62
src/Symfony/Component/Notifier/Tests/TexterTest.php
Normal file
62
src/Symfony/Component/Notifier/Tests/TexterTest.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Notifier\Tests;
|
||||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\Notifier\Message\SentMessage;
|
||||
use Symfony\Component\Notifier\Tests\Transport\DummyMessage;
|
||||
use Symfony\Component\Notifier\Texter;
|
||||
use Symfony\Component\Notifier\Transport\TransportInterface;
|
||||
|
||||
class TexterTest extends TestCase
|
||||
{
|
||||
/** @var MockObject&TransportInterface */
|
||||
private $transport;
|
||||
|
||||
/** @var MockObject&MessageBusInterface */
|
||||
private $bus;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->transport = $this->createMock(TransportInterface::class);
|
||||
$this->bus = $this->createMock(MessageBusInterface::class);
|
||||
}
|
||||
|
||||
public function testSendWithoutBus()
|
||||
{
|
||||
$message = new DummyMessage();
|
||||
$sentMessage = new SentMessage($message, 'any');
|
||||
|
||||
$this->transport
|
||||
->expects($this->once())
|
||||
->method('send')
|
||||
->with($message)
|
||||
->willReturn($sentMessage);
|
||||
|
||||
$texter = new Texter($this->transport);
|
||||
$this->assertSame($sentMessage, $texter->send($message));
|
||||
$this->assertSame($message, $sentMessage->getOriginalMessage());
|
||||
}
|
||||
|
||||
public function testSendWithBus()
|
||||
{
|
||||
$message = new DummyMessage();
|
||||
|
||||
$this->transport
|
||||
->expects($this->never())
|
||||
->method('send')
|
||||
->with($message);
|
||||
|
||||
$this->bus
|
||||
->expects($this->once())
|
||||
->method('dispatch')
|
||||
->with($message)
|
||||
->willReturn(new Envelope(new \stdClass()));
|
||||
|
||||
$texter = new Texter($this->transport, $this->bus);
|
||||
$this->assertNull($texter->send($message));
|
||||
}
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Notifier\Tests\Transport;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Notifier\Exception\LogicException;
|
||||
use Symfony\Component\Notifier\Exception\RuntimeException;
|
||||
use Symfony\Component\Notifier\Exception\TransportExceptionInterface;
|
||||
use Symfony\Component\Notifier\Message\SentMessage;
|
||||
use Symfony\Component\Notifier\Transport\FailoverTransport;
|
||||
use Symfony\Component\Notifier\Transport\TransportInterface;
|
||||
|
||||
/**
|
||||
* @group time-sensitive
|
||||
*/
|
||||
class FailoverTransportTest extends TestCase
|
||||
{
|
||||
public function testSendNoTransports()
|
||||
{
|
||||
$this->expectException(LogicException::class);
|
||||
|
||||
new FailoverTransport([]);
|
||||
}
|
||||
|
||||
public function testToString()
|
||||
{
|
||||
$t1 = $this->createMock(TransportInterface::class);
|
||||
$t1->expects($this->once())->method('__toString')->willReturn('t1://local');
|
||||
|
||||
$t2 = $this->createMock(TransportInterface::class);
|
||||
$t2->expects($this->once())->method('__toString')->willReturn('t2://local');
|
||||
|
||||
$t = new FailoverTransport([$t1, $t2]);
|
||||
|
||||
$this->assertEquals('t1://local || t2://local', (string) $t);
|
||||
}
|
||||
|
||||
public function testSendMessageNotSupportedByAnyTransport()
|
||||
{
|
||||
$t1 = $this->createMock(TransportInterface::class);
|
||||
$t2 = $this->createMock(TransportInterface::class);
|
||||
|
||||
$t = new FailoverTransport([$t1, $t2]);
|
||||
|
||||
$this->expectException(LogicException::class);
|
||||
|
||||
$t->send(new DummyMessage());
|
||||
}
|
||||
|
||||
public function testSendFirstWork()
|
||||
{
|
||||
$message = new DummyMessage();
|
||||
|
||||
$t1 = $this->createMock(TransportInterface::class);
|
||||
$t1->method('supports')->with($message)->willReturn(true);
|
||||
$t1->expects($this->exactly(3))->method('send')->with($message)->willReturn(new SentMessage($message, 'test'));
|
||||
|
||||
$t2 = $this->createMock(TransportInterface::class);
|
||||
$t2->expects($this->never())->method('send');
|
||||
|
||||
$t = new FailoverTransport([$t1, $t2]);
|
||||
|
||||
$t->send($message);
|
||||
$t->send($message);
|
||||
$t->send($message);
|
||||
}
|
||||
|
||||
public function testSendAllDead()
|
||||
{
|
||||
$message = new DummyMessage();
|
||||
|
||||
$t1 = $this->createMock(TransportInterface::class);
|
||||
$t1->method('supports')->with($message)->willReturn(true);
|
||||
$t1->expects($this->once())->method('send')->with($message)->will($this->throwException($this->createMock(TransportExceptionInterface::class)));
|
||||
|
||||
$t2 = $this->createMock(TransportInterface::class);
|
||||
$t2->method('supports')->with($message)->willReturn(true);
|
||||
$t2->expects($this->once())->method('send')->with($message)->will($this->throwException($this->createMock(TransportExceptionInterface::class)));
|
||||
|
||||
$t = new FailoverTransport([$t1, $t2]);
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('All transports failed.');
|
||||
|
||||
$t->send($message);
|
||||
}
|
||||
|
||||
public function testSendOneDead()
|
||||
{
|
||||
$message = new DummyMessage();
|
||||
|
||||
$t1 = $this->createMock(TransportInterface::class);
|
||||
$t1->method('supports')->with($message)->willReturn(true);
|
||||
$t1->expects($this->once())->method('send')->will($this->throwException($this->createMock(TransportExceptionInterface::class)));
|
||||
|
||||
$t2 = $this->createMock(TransportInterface::class);
|
||||
$t2->method('supports')->with($message)->willReturn(true);
|
||||
$t2->expects($this->exactly(1))->method('send')->with($message)->willReturn(new SentMessage($message, 'test'));
|
||||
|
||||
$t = new FailoverTransport([$t1, $t2]);
|
||||
|
||||
$t->send($message);
|
||||
}
|
||||
|
||||
public function testSendAllDeadWithinRetryPeriod()
|
||||
{
|
||||
$message = new DummyMessage();
|
||||
|
||||
$t1 = $this->createMock(TransportInterface::class);
|
||||
$t1->method('supports')->with($message)->willReturn(true);
|
||||
$t1->method('send')->will($this->throwException($this->createMock(TransportExceptionInterface::class)));
|
||||
$t1->expects($this->once())->method('send');
|
||||
$t2 = $this->createMock(TransportInterface::class);
|
||||
$t2->method('supports')->with($message)->willReturn(true);
|
||||
$t2->expects($this->exactly(3))
|
||||
->method('send')
|
||||
->willReturnOnConsecutiveCalls(
|
||||
new SentMessage($message, 't2'),
|
||||
new SentMessage($message, 't2'),
|
||||
$this->throwException($this->createMock(TransportExceptionInterface::class))
|
||||
);
|
||||
$t = new FailoverTransport([$t1, $t2], 40);
|
||||
$t->send($message);
|
||||
sleep(4);
|
||||
$t->send($message);
|
||||
sleep(4);
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('All transports failed.');
|
||||
|
||||
$t->send($message);
|
||||
}
|
||||
|
||||
public function testSendOneDeadButRecover()
|
||||
{
|
||||
$message = new DummyMessage();
|
||||
|
||||
$t1 = $this->createMock(TransportInterface::class);
|
||||
$t1->method('supports')->with($message)->willReturn(true);
|
||||
$t1->expects($this->exactly(2))->method('send')->willReturnOnConsecutiveCalls(
|
||||
$this->throwException($this->createMock(TransportExceptionInterface::class)),
|
||||
new SentMessage($message, 't1')
|
||||
);
|
||||
$t2 = $this->createMock(TransportInterface::class);
|
||||
$t2->method('supports')->with($message)->willReturn(true);
|
||||
$t2->expects($this->exactly(2))->method('send')->willReturnOnConsecutiveCalls(
|
||||
new SentMessage($message, 't2'),
|
||||
$this->throwException($this->createMock(TransportExceptionInterface::class))
|
||||
);
|
||||
|
||||
$t = new FailoverTransport([$t1, $t2], 1);
|
||||
|
||||
$t->send($message);
|
||||
sleep(2);
|
||||
$t->send($message);
|
||||
}
|
||||
}
|
@ -49,9 +49,7 @@ final class Texter implements TexterInterface
|
||||
public function send(MessageInterface $message): ?SentMessage
|
||||
{
|
||||
if (null === $this->bus) {
|
||||
$this->transport->send($message);
|
||||
|
||||
return null;
|
||||
return $this->transport->send($message);
|
||||
}
|
||||
|
||||
if (null !== $this->dispatcher) {
|
||||
|
@ -31,6 +31,11 @@ class FailoverTransport extends RoundRobinTransport
|
||||
return $this->currentTransport;
|
||||
}
|
||||
|
||||
protected function getInitialCursor(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function getNameSymbol(): string
|
||||
{
|
||||
return '||';
|
||||
|
@ -27,7 +27,7 @@ class RoundRobinTransport implements TransportInterface
|
||||
private $deadTransports;
|
||||
private $transports = [];
|
||||
private $retryPeriod;
|
||||
private $cursor = 0;
|
||||
private $cursor = -1;
|
||||
|
||||
/**
|
||||
* @param TransportInterface[] $transports
|
||||
@ -41,9 +41,6 @@ class RoundRobinTransport implements TransportInterface
|
||||
$this->transports = $transports;
|
||||
$this->deadTransports = new \SplObjectStorage();
|
||||
$this->retryPeriod = $retryPeriod;
|
||||
// the cursor initial value is randomized so that
|
||||
// when are not in a daemon, we are still rotating the transports
|
||||
$this->cursor = mt_rand(0, \count($transports) - 1);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
@ -64,6 +61,10 @@ class RoundRobinTransport implements TransportInterface
|
||||
|
||||
public function send(MessageInterface $message): SentMessage
|
||||
{
|
||||
if (!$this->supports($message)) {
|
||||
throw new LogicException(sprintf('None of the configured Transports of "%s" supports the given message.', static::class));
|
||||
}
|
||||
|
||||
while ($transport = $this->getNextTransport($message)) {
|
||||
try {
|
||||
return $transport->send($message);
|
||||
@ -80,12 +81,17 @@ class RoundRobinTransport implements TransportInterface
|
||||
*/
|
||||
protected function getNextTransport(MessageInterface $message): ?TransportInterface
|
||||
{
|
||||
if (-1 === $this->cursor) {
|
||||
$this->cursor = $this->getInitialCursor();
|
||||
}
|
||||
|
||||
$cursor = $this->cursor;
|
||||
while (true) {
|
||||
$transport = $this->transports[$cursor];
|
||||
|
||||
if (!$transport->supports($message)) {
|
||||
$cursor = $this->moveCursor($cursor);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -114,6 +120,13 @@ class RoundRobinTransport implements TransportInterface
|
||||
return $this->deadTransports->contains($transport);
|
||||
}
|
||||
|
||||
protected function getInitialCursor(): int
|
||||
{
|
||||
// the cursor initial value is randomized so that
|
||||
// when are not in a daemon, we are still rotating the transports
|
||||
return mt_rand(0, \count($this->transports) - 1);
|
||||
}
|
||||
|
||||
protected function getNameSymbol(): string
|
||||
{
|
||||
return '&&';
|
||||
|
@ -22,7 +22,8 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/event-dispatcher-contracts": "^2",
|
||||
"symfony/http-client-contracts": "^2"
|
||||
"symfony/http-client-contracts": "^2",
|
||||
"symfony/messenger": "^4.4 || ^5.0"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/http-kernel": "<4.4",
|
||||
|
@ -277,17 +277,15 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$reflector = $reflectionProperty->getDeclaringClass();
|
||||
$reflector = $reflectionProperty->getDeclaringClass();
|
||||
|
||||
foreach ($reflector->getTraits() as $trait) {
|
||||
if ($trait->hasProperty($property)) {
|
||||
$reflector = $trait;
|
||||
|
||||
break;
|
||||
}
|
||||
foreach ($reflector->getTraits() as $trait) {
|
||||
if ($trait->hasProperty($property)) {
|
||||
return $this->getDocBlockFromProperty($trait->getName(), $property);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->docBlockFactory->create($reflectionProperty, $this->createFromReflector($reflector));
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null;
|
||||
@ -325,17 +323,15 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$reflector = $reflectionMethod->getDeclaringClass();
|
||||
$reflector = $reflectionMethod->getDeclaringClass();
|
||||
|
||||
foreach ($reflector->getTraits() as $trait) {
|
||||
if ($trait->hasMethod($methodName)) {
|
||||
$reflector = $trait;
|
||||
|
||||
break;
|
||||
}
|
||||
foreach ($reflector->getTraits() as $trait) {
|
||||
if ($trait->hasMethod($methodName)) {
|
||||
return $this->getDocBlockFromMethod($trait->getName(), $ucFirstProperty, $type);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return [$this->docBlockFactory->create($reflectionMethod, $this->createFromReflector($reflector)), $prefix];
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null;
|
||||
|
@ -156,20 +156,8 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
||||
return $fromConstructor;
|
||||
}
|
||||
|
||||
if ($fromDefaultValue = $this->extractFromDefaultValue($class, $property)) {
|
||||
return $fromDefaultValue;
|
||||
}
|
||||
|
||||
if (\PHP_VERSION_ID >= 70400) {
|
||||
try {
|
||||
$reflectionProperty = new \ReflectionProperty($class, $property);
|
||||
$type = $reflectionProperty->getType();
|
||||
if (null !== $type && $types = $this->extractFromReflectionType($type, $reflectionProperty->getDeclaringClass())) {
|
||||
return $types;
|
||||
}
|
||||
} catch (\ReflectionException $e) {
|
||||
// noop
|
||||
}
|
||||
if ($fromPropertyDeclaration = $this->extractFromPropertyDeclaration($class, $property)) {
|
||||
return $fromPropertyDeclaration;
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -530,10 +518,19 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
||||
return null;
|
||||
}
|
||||
|
||||
private function extractFromDefaultValue(string $class, string $property): ?array
|
||||
private function extractFromPropertyDeclaration(string $class, string $property): ?array
|
||||
{
|
||||
try {
|
||||
$reflectionClass = new \ReflectionClass($class);
|
||||
|
||||
if (\PHP_VERSION_ID >= 70400) {
|
||||
$reflectionProperty = $reflectionClass->getProperty($property);
|
||||
$reflectionPropertyType = $reflectionProperty->getType();
|
||||
|
||||
if (null !== $reflectionPropertyType && $types = $this->extractFromReflectionType($reflectionPropertyType, $reflectionProperty->getDeclaringClass())) {
|
||||
return $types;
|
||||
}
|
||||
}
|
||||
} catch (\ReflectionException $e) {
|
||||
return null;
|
||||
}
|
||||
@ -547,7 +544,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
||||
$type = \gettype($defaultValue);
|
||||
$type = static::MAP_TYPES[$type] ?? $type;
|
||||
|
||||
return [new Type($type, false, null, Type::BUILTIN_TYPE_ARRAY === $type)];
|
||||
return [new Type($type, $this->isNullableProperty($class, $property), null, Type::BUILTIN_TYPE_ARRAY === $type)];
|
||||
}
|
||||
|
||||
private function extractFromReflectionType(\ReflectionType $reflectionType, \ReflectionClass $declaringClass): array
|
||||
@ -587,12 +584,31 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
|
||||
return $name;
|
||||
}
|
||||
|
||||
private function isNullableProperty(string $class, string $property): bool
|
||||
{
|
||||
try {
|
||||
$reflectionProperty = new \ReflectionProperty($class, $property);
|
||||
|
||||
if (\PHP_VERSION_ID >= 70400) {
|
||||
$reflectionPropertyType = $reflectionProperty->getType();
|
||||
|
||||
return null !== $reflectionPropertyType && $reflectionPropertyType->allowsNull();
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (\ReflectionException $e) {
|
||||
// Return false if the property doesn't exist
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function isAllowedProperty(string $class, string $property): bool
|
||||
{
|
||||
try {
|
||||
$reflectionProperty = new \ReflectionProperty($class, $property);
|
||||
|
||||
return $reflectionProperty->getModifiers() & $this->propertyReflectionFlags;
|
||||
return (bool) ($reflectionProperty->getModifiers() & $this->propertyReflectionFlags);
|
||||
} catch (\ReflectionException $e) {
|
||||
// Return false if the property doesn't exist
|
||||
}
|
||||
|
@ -109,7 +109,11 @@ class PhpDocExtractorTest extends TestCase
|
||||
['a', [new Type(Type::BUILTIN_TYPE_INT)], 'A.', null],
|
||||
['b', [new Type(Type::BUILTIN_TYPE_OBJECT, true, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')], 'B.', null],
|
||||
['c', [new Type(Type::BUILTIN_TYPE_BOOL, true)], null, null],
|
||||
['ct', [new Type(Type::BUILTIN_TYPE_TRUE, true)], null, null],
|
||||
['cf', [new Type(Type::BUILTIN_TYPE_FALSE, true)], null, null],
|
||||
['d', [new Type(Type::BUILTIN_TYPE_BOOL)], null, null],
|
||||
['dt', [new Type(Type::BUILTIN_TYPE_TRUE)], null, null],
|
||||
['df', [new Type(Type::BUILTIN_TYPE_FALSE)], null, null],
|
||||
['e', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_RESOURCE))], null, null],
|
||||
['f', [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime'))], null, null],
|
||||
['g', [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)], 'Nullable array.', null],
|
||||
@ -341,6 +345,9 @@ class PhpDocExtractorTest extends TestCase
|
||||
['propertyInTraitPrimitiveType', new Type(Type::BUILTIN_TYPE_STRING)],
|
||||
['propertyInTraitObjectSameNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)],
|
||||
['propertyInTraitObjectDifferentNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)],
|
||||
['propertyInExternalTraitPrimitiveType', new Type(Type::BUILTIN_TYPE_STRING)],
|
||||
['propertyInExternalTraitObjectSameNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)],
|
||||
['propertyInExternalTraitObjectDifferentNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)],
|
||||
];
|
||||
}
|
||||
|
||||
@ -358,6 +365,9 @@ class PhpDocExtractorTest extends TestCase
|
||||
['methodInTraitPrimitiveType', new Type(Type::BUILTIN_TYPE_STRING)],
|
||||
['methodInTraitObjectSameNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)],
|
||||
['methodInTraitObjectDifferentNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)],
|
||||
['methodInExternalTraitPrimitiveType', new Type(Type::BUILTIN_TYPE_STRING)],
|
||||
['methodInExternalTraitObjectSameNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)],
|
||||
['methodInExternalTraitObjectDifferentNamespace', new Type(Type::BUILTIN_TYPE_OBJECT, false, DummyUsedInTrait::class)],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,11 @@ class ReflectionExtractorTest extends TestCase
|
||||
'date',
|
||||
'element',
|
||||
'c',
|
||||
'ct',
|
||||
'cf',
|
||||
'd',
|
||||
'dt',
|
||||
'df',
|
||||
'e',
|
||||
'f',
|
||||
],
|
||||
@ -134,7 +138,11 @@ class ReflectionExtractorTest extends TestCase
|
||||
'parentAnnotationNoParent',
|
||||
'date',
|
||||
'c',
|
||||
'ct',
|
||||
'cf',
|
||||
'd',
|
||||
'dt',
|
||||
'df',
|
||||
'e',
|
||||
'f',
|
||||
],
|
||||
@ -444,6 +452,7 @@ class ReflectionExtractorTest extends TestCase
|
||||
$this->assertEquals([new Type(Type::BUILTIN_TYPE_OBJECT, false, Dummy::class)], $this->extractor->getTypes(Php74Dummy::class, 'dummy'));
|
||||
$this->assertEquals([new Type(Type::BUILTIN_TYPE_BOOL, true)], $this->extractor->getTypes(Php74Dummy::class, 'nullableBoolProp'));
|
||||
$this->assertEquals([new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))], $this->extractor->getTypes(Php74Dummy::class, 'stringCollection'));
|
||||
$this->assertEquals([new Type(Type::BUILTIN_TYPE_INT, true)], $this->extractor->getTypes(Php74Dummy::class, 'nullableWithDefault'));
|
||||
$this->assertEquals([new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true)], $this->extractor->getTypes(Php74Dummy::class, 'collection'));
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\PropertyInfo\Tests\Fixtures;
|
||||
|
||||
use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsedInTrait;
|
||||
|
||||
trait DummyTraitExternal
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $propertyInExternalTraitPrimitiveType;
|
||||
|
||||
/**
|
||||
* @var Dummy
|
||||
*/
|
||||
private $propertyInExternalTraitObjectSameNamespace;
|
||||
|
||||
/**
|
||||
* @var DummyUsedInTrait
|
||||
*/
|
||||
private $propertyInExternalTraitObjectDifferentNamespace;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMethodInExternalTraitPrimitiveType()
|
||||
{
|
||||
return 'value';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Dummy
|
||||
*/
|
||||
public function getMethodInExternalTraitObjectSameNamespace()
|
||||
{
|
||||
return new Dummy();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DummyUsedInTrait
|
||||
*/
|
||||
public function getMethodInExternalTraitObjectDifferentNamespace()
|
||||
{
|
||||
return new DummyUsedInTrait();
|
||||
}
|
||||
}
|
@ -65,6 +65,20 @@ class ParentDummy
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true|null
|
||||
*/
|
||||
public function isCt()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false|null
|
||||
*/
|
||||
public function isCf()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
@ -72,6 +86,20 @@ class ParentDummy
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true
|
||||
*/
|
||||
public function canDt()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false
|
||||
*/
|
||||
public function canDf()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $e
|
||||
*/
|
||||
|
@ -20,6 +20,7 @@ class Php74Dummy
|
||||
private ?bool $nullableBoolProp;
|
||||
/** @var string[] */
|
||||
private array $stringCollection;
|
||||
private ?int $nullableWithDefault = 1;
|
||||
public array $collection = [];
|
||||
|
||||
public function addStringCollection(string $string): void
|
||||
|
@ -12,9 +12,12 @@
|
||||
namespace Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage;
|
||||
|
||||
use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy;
|
||||
use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyTraitExternal;
|
||||
|
||||
trait DummyTrait
|
||||
{
|
||||
use DummyTraitExternal;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
@ -24,6 +24,8 @@ class Type
|
||||
public const BUILTIN_TYPE_FLOAT = 'float';
|
||||
public const BUILTIN_TYPE_STRING = 'string';
|
||||
public const BUILTIN_TYPE_BOOL = 'bool';
|
||||
public const BUILTIN_TYPE_TRUE = 'true';
|
||||
public const BUILTIN_TYPE_FALSE = 'false';
|
||||
public const BUILTIN_TYPE_RESOURCE = 'resource';
|
||||
public const BUILTIN_TYPE_OBJECT = 'object';
|
||||
public const BUILTIN_TYPE_ARRAY = 'array';
|
||||
@ -41,6 +43,8 @@ class Type
|
||||
self::BUILTIN_TYPE_FLOAT,
|
||||
self::BUILTIN_TYPE_STRING,
|
||||
self::BUILTIN_TYPE_BOOL,
|
||||
self::BUILTIN_TYPE_TRUE,
|
||||
self::BUILTIN_TYPE_FALSE,
|
||||
self::BUILTIN_TYPE_RESOURCE,
|
||||
self::BUILTIN_TYPE_OBJECT,
|
||||
self::BUILTIN_TYPE_ARRAY,
|
||||
|
@ -64,12 +64,20 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="17">
|
||||
<source>Too many failed login attempts, please try again later.</source>
|
||||
<target>Troppi tentaivi di login falliti. Riprova tra un po'.</target>
|
||||
<target>Troppi tentativi di login falliti, riprova tra un po'.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="18">
|
||||
<source>Invalid or expired login link.</source>
|
||||
<target>Link di login scaduto o non valido.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="19">
|
||||
<source>Too many failed login attempts, please try again in %minutes% minute.</source>
|
||||
<target>Troppi tentativi di login falliti, riprova tra %minutes% minuto.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="20">
|
||||
<source>Too many failed login attempts, please try again in %minutes% minutes.</source>
|
||||
<target>Troppi tentativi di login falliti, riprova tra %minutes% minuti.</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
@ -63,13 +63,21 @@
|
||||
<target>アカウントはロックされています。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="17">
|
||||
<source>Too many failed login attempts, please try again later.</source>
|
||||
<target>ログイン試行回数を超えました。しばらくして再度お試しください。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="18">
|
||||
<source>Invalid or expired login link.</source>
|
||||
<target>ログインリンクが有効期限切れ、もしくは無効です。</target>
|
||||
</trans-unit>
|
||||
<source>Too many failed login attempts, please try again later.</source>
|
||||
<target>ログイン試行回数を超えました。しばらくして再度お試しください。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="18">
|
||||
<source>Invalid or expired login link.</source>
|
||||
<target>ログインリンクが有効期限切れ、もしくは無効です。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="19">
|
||||
<source>Too many failed login attempts, please try again in %minutes% minute.</source>
|
||||
<target>ログイン試行回数が多すぎます。%minutes%分後に再度お試しください。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="20">
|
||||
<source>Too many failed login attempts, please try again in %minutes% minutes.</source>
|
||||
<target>ログイン試行回数が多すぎます。%minutes%分後に再度お試しください。</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
@ -70,6 +70,14 @@
|
||||
<source>Invalid or expired login link.</source>
|
||||
<target>Nieprawidłowy lub wygasły link logowania.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="19">
|
||||
<source>Too many failed login attempts, please try again in %minutes% minute.</source>
|
||||
<target>Zbyt wiele nieudanych prób logowania, spróbuj ponownie po upływie %minutes% minut.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="20">
|
||||
<source>Too many failed login attempts, please try again in %minutes% minutes.</source>
|
||||
<target>Zbyt wiele nieudanych prób logowania, spróbuj ponownie po upływie %minutes% minut.</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
@ -70,6 +70,14 @@
|
||||
<source>Invalid or expired login link.</source>
|
||||
<target>Link de login inválido ou expirado.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="19">
|
||||
<source>Too many failed login attempts, please try again in %minutes% minute.</source>
|
||||
<target>Muitas tentativas de login inválidas, por favor, tente novamente em um minuto.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="20">
|
||||
<source>Too many failed login attempts, please try again in %minutes% minutes.</source>
|
||||
<target>Muitas tentativas de login inválidas, por favor, tente novamente em %minutes% minutos.</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
@ -70,6 +70,14 @@
|
||||
<source>Invalid or expired login link.</source>
|
||||
<target>Ссылка для входа недействительна или просрочена.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="19">
|
||||
<source>Too many failed login attempts, please try again in %minutes% minute.</source>
|
||||
<target>Слишком много неудачных попыток входа в систему, повторите попытку через %minutes% минуту.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="20">
|
||||
<source>Too many failed login attempts, please try again in %minutes% minutes.</source>
|
||||
<target>Слишком много неудачных попыток входа в систему, повторите попытку через %minutes% мин.</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
@ -23,6 +23,7 @@ class IdentityTranslatorTest extends TranslatorTest
|
||||
parent::setUp();
|
||||
|
||||
$this->defaultLocale = \Locale::getDefault();
|
||||
\Locale::setDefault('en');
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
|
@ -166,7 +166,6 @@ class VarCloner extends AbstractCloner
|
||||
break;
|
||||
|
||||
case \is_object($v):
|
||||
case $v instanceof \__PHP_Incomplete_Class:
|
||||
if (empty($objRefs[$h = spl_object_id($v)])) {
|
||||
$stub = new Stub();
|
||||
$stub->type = Stub::TYPE_OBJECT;
|
||||
|
@ -60,7 +60,7 @@ class Exporter
|
||||
$value = self::prepare($value, $objectsPool, $refsPool, $objectsCount, $valueIsStatic);
|
||||
}
|
||||
goto handle_value;
|
||||
} elseif (!\is_object($value) && !$value instanceof \__PHP_Incomplete_Class) {
|
||||
} elseif (!\is_object($value) || $value instanceof \UnitEnum) {
|
||||
goto handle_value;
|
||||
}
|
||||
|
||||
@ -188,7 +188,7 @@ class Exporter
|
||||
public static function export($value, string $indent = '')
|
||||
{
|
||||
switch (true) {
|
||||
case \is_int($value) || \is_float($value): return var_export($value, true);
|
||||
case \is_int($value) || \is_float($value) || $value instanceof \UnitEnum: return var_export($value, true);
|
||||
case [] === $value: return '[]';
|
||||
case false === $value: return 'false';
|
||||
case true === $value: return 'true';
|
||||
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\VarExporter\Tests\Fixtures;
|
||||
|
||||
enum FooUnitEnum
|
||||
{
|
||||
case Bar;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
Symfony\Component\VarExporter\Tests\Fixtures\FooUnitEnum::Bar,
|
||||
];
|
@ -16,6 +16,7 @@ use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
|
||||
use Symfony\Component\VarExporter\Exception\ClassNotFoundException;
|
||||
use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException;
|
||||
use Symfony\Component\VarExporter\Internal\Registry;
|
||||
use Symfony\Component\VarExporter\Tests\Fixtures\FooUnitEnum;
|
||||
use Symfony\Component\VarExporter\VarExporter;
|
||||
|
||||
class VarExporterTest extends TestCase
|
||||
@ -209,6 +210,10 @@ class VarExporterTest extends TestCase
|
||||
yield ['private-constructor', PrivateConstructor::create('bar')];
|
||||
|
||||
yield ['php74-serializable', new Php74Serializable()];
|
||||
|
||||
if (\PHP_VERSION_ID >= 80100) {
|
||||
yield ['unit-enum', [FooUnitEnum::Bar], true];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ final class VarExporter
|
||||
{
|
||||
$isStaticValue = true;
|
||||
|
||||
if (!\is_object($value) && !(\is_array($value) && $value) && !$value instanceof \__PHP_Incomplete_Class && !\is_resource($value)) {
|
||||
if (!\is_object($value) && !(\is_array($value) && $value) && !\is_resource($value) || $value instanceof \UnitEnum) {
|
||||
return Exporter::export($value);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user