Merge branch '4.4'

* 4.4:
  [travis] Fix build-packages script
  Add types to constructors and private/final/internal methods (Batch III)
  [HttpClient] Async HTTPlug client
  [Messenger] Allow to configure the db index on Redis transport
  [HttpClient] bugfix exploding values of headers
  [VarDumper] Made all casters final
  [VarDumper] Added a support for casting Ramsey/Uuid
  Remove useless testCanCheckIfTerminalIsInteractive test case
  [Validator] Add the missing translations for the Thai (\"th\") locale
  [Routing] gracefully handle docref_root ini setting
  [Validator] Fix ValidValidator group cascading usage
This commit is contained in:
Nicolas Grekas 2019-10-07 14:45:39 +02:00
commit 4e5c6ba0d3
71 changed files with 685 additions and 153 deletions

View File

@ -47,7 +47,7 @@ foreach ($dirs as $k => $dir) {
if (isset($preferredInstall[$package->name]) && 'source' === $preferredInstall[$package->name]) {
passthru("cd $dir && tar -cf package.tar --exclude='package.tar' *");
} else {
passthru("cd $dir && git init && git add . && git commit -m - && git archive -o package.tar HEAD && rm .git/ -Rf");
passthru("cd $dir && git init && git add . && git commit --author \"Symfony <>\" -m - && git archive -o package.tar HEAD && rm .git/ -Rf");
}
if (!isset($package->extra->{'branch-alias'}->{'dev-master'})) {

View File

@ -108,6 +108,7 @@
"doctrine/orm": "~2.4,>=2.4.5",
"doctrine/reflection": "~1.0",
"doctrine/doctrine-bundle": "^1.5|^2.0",
"guzzlehttp/promises": "^1.3.1",
"masterminds/html5": "^2.6",
"monolog/monolog": "^1.25.1|^2",
"nyholm/psr7": "^1.0",

View File

@ -68,7 +68,7 @@ class DbalLogger implements SQLLogger
$this->logger->debug($message, $params);
}
private function normalizeParams(array $params)
private function normalizeParams(array $params): array
{
foreach ($params as $index => $param) {
// normalize recursively

View File

@ -12,6 +12,9 @@
namespace Symfony\Bridge\Doctrine\Security\User;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\Persistence\ObjectRepository;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
@ -124,17 +127,17 @@ class EntityUserProvider implements UserProviderInterface, PasswordUpgraderInter
}
}
private function getObjectManager()
private function getObjectManager(): ObjectManager
{
return $this->registry->getManager($this->managerName);
}
private function getRepository()
private function getRepository(): ObjectRepository
{
return $this->getObjectManager()->getRepository($this->classOrAlias);
}
private function getClass()
private function getClass(): string
{
if (null === $this->class) {
$class = $this->classOrAlias;
@ -149,7 +152,7 @@ class EntityUserProvider implements UserProviderInterface, PasswordUpgraderInter
return $this->class;
}
private function getClassMetadata()
private function getClassMetadata(): ClassMetadata
{
return $this->getObjectManager()->getClassMetadata($this->classOrAlias);
}

View File

@ -11,10 +11,11 @@
namespace Symfony\Bridge\Doctrine\Tests\Security\User;
use Doctrine\ORM\EntityRepository;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\ORM\Tools\SchemaTool;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Doctrine\Security\User\EntityUserProvider;
use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper;
use Symfony\Bridge\Doctrine\Tests\Fixtures\User;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
@ -60,9 +61,7 @@ class EntityUserProviderTest extends TestCase
{
$user = new User(1, 1, 'user1');
$repository = $this->getMockBuilder('Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface')
->disableOriginalConstructor()
->getMock();
$repository = $this->createMock([ObjectRepository::class, UserLoaderInterface::class]);
$repository
->expects($this->once())
->method('loadUserByUsername')
@ -148,7 +147,7 @@ class EntityUserProviderTest extends TestCase
public function testLoadUserByUserNameShouldLoadUserWhenProperInterfaceProvided()
{
$repository = $this->getMockBuilder('\Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface')->getMock();
$repository = $this->createMock([ObjectRepository::class, UserLoaderInterface::class]);
$repository->expects($this->once())
->method('loadUserByUsername')
->with('name')
@ -167,7 +166,7 @@ class EntityUserProviderTest extends TestCase
public function testLoadUserByUserNameShouldDeclineInvalidInterface()
{
$this->expectException('InvalidArgumentException');
$repository = $this->getMockBuilder(EntityRepository::class)->disableOriginalConstructor()->getMock();
$repository = $this->createMock(ObjectRepository::class);
$provider = new EntityUserProvider(
$this->getManager($this->getObjectManager($repository)),
@ -181,7 +180,7 @@ class EntityUserProviderTest extends TestCase
{
$user = new User(1, 1, 'user1');
$repository = $this->getMockBuilder(PasswordUpgraderInterface::class)->getMock();
$repository = $this->createMock([ObjectRepository::class, PasswordUpgraderInterface::class]);
$repository->expects($this->once())
->method('upgradePassword')
->with($user, 'foobar');

View File

@ -143,7 +143,7 @@ class ConsoleFormatter implements FormatterInterface
/**
* @internal
*/
public function castObject($v, array $a, Stub $s, bool $isNested)
public function castObject($v, array $a, Stub $s, bool $isNested): array
{
if ($this->options['multiline']) {
return $a;
@ -157,7 +157,7 @@ class ConsoleFormatter implements FormatterInterface
return $a;
}
private function replacePlaceHolder(array $record)
private function replacePlaceHolder(array $record): array
{
$message = $record['message'];

View File

@ -101,7 +101,7 @@ class ServerLogHandler extends AbstractHandler
return $socket;
}
private function formatRecord(array $record)
private function formatRecord(array $record): string
{
if ($this->processors) {
foreach ($this->processors as $processor) {

View File

@ -350,7 +350,7 @@ EOF
return null;
}
private function getPrettyMetadata(string $type, $entity, bool $decorated)
private function getPrettyMetadata(string $type, $entity, bool $decorated): ?string
{
if ('tests' === $type) {
return '';

View File

@ -112,7 +112,7 @@ EOF
return $template;
}
private function getFilesInfo(array $filenames)
private function getFilesInfo(array $filenames): array
{
$filesInfo = [];
foreach ($filenames as $filename) {

View File

@ -65,7 +65,7 @@ class UndefinedCallableHandler
'workflow' => 'enable "framework.workflows"',
];
public static function onUndefinedFilter(string $name)
public static function onUndefinedFilter(string $name): bool
{
if (!isset(self::$filterComponents[$name])) {
return false;
@ -76,7 +76,7 @@ class UndefinedCallableHandler
return true;
}
public static function onUndefinedFunction(string $name)
public static function onUndefinedFunction(string $name): bool
{
if (!isset(self::$functionComponents[$name])) {
return false;
@ -87,7 +87,7 @@ class UndefinedCallableHandler
return true;
}
private static function onUndefined($name, $type, $component)
private static function onUndefined(string $name, string $type, string $component)
{
if (class_exists(FullStack::class) && isset(self::$fullStackEnable[$component])) {
throw new SyntaxError(sprintf('Did you forget to %s? Unknown %s "%s".', self::$fullStackEnable[$component], $type, $name));

View File

@ -233,7 +233,7 @@ EOF
return $this->containerBuilder = $container;
}
private function findProperServiceName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $builder, string $name, bool $showHidden)
private function findProperServiceName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $builder, string $name, bool $showHidden): string
{
$name = ltrim($name, '\\');
@ -253,7 +253,7 @@ EOF
return $io->choice('Select one of the following services to display its information', $matchingServices);
}
private function findServiceIdsContaining(ContainerBuilder $builder, string $name, bool $showHidden)
private function findServiceIdsContaining(ContainerBuilder $builder, string $name, bool $showHidden): array
{
$serviceIds = $builder->getServiceIds();
$foundServiceIds = $foundServiceIdsIgnoringBackslashes = [];
@ -275,7 +275,7 @@ EOF
/**
* @internal
*/
public function filterToServiceTypes($serviceId)
public function filterToServiceTypes(string $serviceId): bool
{
// filter out things that could not be valid class names
if (!preg_match('/(?(DEFINE)(?<V>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^(?&V)(?:\\\\(?&V))*+(?: \$(?&V))?$/', $serviceId)) {

View File

@ -62,7 +62,7 @@ abstract class AbstractController implements ServiceSubscriberInterface
* @internal
* @required
*/
public function setContainer(ContainerInterface $container)
public function setContainer(ContainerInterface $container): ?ContainerInterface
{
$previous = $this->container;
$this->container = $container;

View File

@ -187,7 +187,7 @@ EOF
return base64_encode(random_bytes(30));
}
private function getUserClass(InputInterface $input, SymfonyStyle $io)
private function getUserClass(InputInterface $input, SymfonyStyle $io): string
{
if (null !== $userClass = $input->getArgument('user-class')) {
return $userClass;

View File

@ -92,7 +92,7 @@ class GuardAuthenticationFactory implements SecurityFactoryInterface
return [$providerId, $listenerId, $entryPointId];
}
private function determineEntryPoint(?string $defaultEntryPointId, array $config)
private function determineEntryPoint(?string $defaultEntryPointId, array $config): string
{
if ($defaultEntryPointId) {
// explode if they've configured the entry_point, but there is already one

View File

@ -560,7 +560,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
}
// Parses user providers and returns an array of their ids
private function createUserProviders(array $config, ContainerBuilder $container)
private function createUserProviders(array $config, ContainerBuilder $container): array
{
$providerIds = [];
foreach ($config['providers'] as $name => $provider) {
@ -572,7 +572,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
}
// Parses a <provider> tag and returns the id for the related user provider service
private function createUserDaoProvider(string $name, array $provider, ContainerBuilder $container)
private function createUserDaoProvider(string $name, array $provider, ContainerBuilder $container): string
{
$name = $this->getUserProviderId($name);
@ -611,12 +611,12 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
throw new InvalidConfigurationException(sprintf('Unable to create definition for "%s" user provider', $name));
}
private function getUserProviderId(string $name)
private function getUserProviderId(string $name): string
{
return 'security.user.provider.concrete.'.strtolower($name);
}
private function createExceptionListener(ContainerBuilder $container, array $config, string $id, ?string $defaultEntryPoint, bool $stateless)
private function createExceptionListener(ContainerBuilder $container, array $config, string $id, ?string $defaultEntryPoint, bool $stateless): string
{
$exceptionListenerId = 'security.exception_listener.'.$id;
$listener = $container->setDefinition($exceptionListenerId, new ChildDefinition('security.exception_listener'));
@ -634,7 +634,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
return $exceptionListenerId;
}
private function createSwitchUserListener(ContainerBuilder $container, string $id, array $config, string $defaultProvider, bool $stateless)
private function createSwitchUserListener(ContainerBuilder $container, string $id, array $config, string $defaultProvider, bool $stateless): string
{
$userProvider = isset($config['provider']) ? $this->getUserProviderId($config['provider']) : $defaultProvider;
@ -654,7 +654,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
return $switchUserListenerId;
}
private function createExpression(ContainerBuilder $container, string $expression)
private function createExpression(ContainerBuilder $container, string $expression): Reference
{
if (isset($this->expressions[$id = '.security.expression.'.ContainerBuilder::hash($expression)])) {
return $this->expressions[$id];
@ -673,7 +673,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface
return $this->expressions[$id] = new Reference($id);
}
private function createRequestMatcher(ContainerBuilder $container, string $path = null, string $host = null, int $port = null, array $methods = [], array $ips = null, array $attributes = [])
private function createRequestMatcher(ContainerBuilder $container, string $path = null, string $host = null, int $port = null, array $methods = [], array $ips = null, array $attributes = []): Reference
{
if ($methods) {
$methods = array_map('strtoupper', (array) $methods);

View File

@ -42,7 +42,7 @@ EOF
;
}
protected function findFiles(string $filename)
protected function findFiles(string $filename): iterable
{
if (0 === strpos($filename, '@')) {
$dir = $this->getApplication()->getKernel()->locateResource($filename);

View File

@ -150,7 +150,7 @@ class TwigExtension extends Extension
}
}
private function getBundleTemplatePaths(ContainerBuilder $container, array $config)
private function getBundleTemplatePaths(ContainerBuilder $container, array $config): array
{
$bundleHierarchy = [];
foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) {
@ -170,7 +170,7 @@ class TwigExtension extends Extension
return $bundleHierarchy;
}
private function normalizeBundleName(string $name)
private function normalizeBundleName(string $name): string
{
if ('Bundle' === substr($name, -6)) {
$name = substr($name, 0, -6);

View File

@ -391,7 +391,7 @@ class ProfilerController
$this->profiler->disable();
}
private function renderWithCspNonces(Request $request, string $template, array $variables, int $code = 200, array $headers = ['Content-Type' => 'text/html'])
private function renderWithCspNonces(Request $request, string $template, array $variables, int $code = 200, array $headers = ['Content-Type' => 'text/html']): Response
{
$response = new Response('', $code, $headers);

View File

@ -20,7 +20,7 @@ namespace Symfony\Bundle\WebProfilerBundle\Csp;
*/
class NonceGenerator
{
public function generate()
public function generate(): string
{
return bin2hex(random_bytes(16));
}

View File

@ -1641,23 +1641,6 @@ class ApplicationTest extends TestCase
$this->assertStringContainsString('The foo:bar command', $tester->getDisplay());
}
/**
* @requires function posix_isatty
*/
public function testCanCheckIfTerminalIsInteractive()
{
$application = new CustomDefaultCommandApplication();
$application->setAutoExit(false);
$tester = new ApplicationTester($application);
$tester->run(['command' => 'help']);
$this->assertFalse($tester->getInput()->hasParameterOption(['--no-interaction', '-n']));
$inputStream = $tester->getInput()->getStream();
$this->assertEquals($tester->getInput()->isInteractive(), @posix_isatty($inputStream));
}
public function testRunLazyCommandService()
{
$container = new ContainerBuilder();

View File

@ -5,7 +5,7 @@ CHANGELOG
-----
* added `StreamWrapper`
* added `HttplugClient`
* added `HttplugClient` with support for sync and async requests
* added `max_duration` option
* added support for NTLM authentication
* added `$response->toStream()` to cast responses to regular PHP streams

View File

@ -76,17 +76,20 @@ class CachingHttpClient implements HttpClientInterface
$request = Request::create($url, $method);
$request->attributes->set('http_client_options', $options);
foreach ($options['headers'] as $name => $values) {
foreach ($options['normalized_headers'] as $name => $values) {
if ('cookie' !== $name) {
$request->headers->set($name, $values);
foreach ($values as $value) {
$request->headers->set($name, substr($value, 2 + \strlen($name)), false);
}
continue;
}
foreach ($values as $cookies) {
foreach (explode('; ', $cookies) as $cookie) {
foreach (explode('; ', substr($cookies, \strlen('Cookie: '))) as $cookie) {
if ('' !== $cookie) {
$cookie = explode('=', $cookie, 2);
$request->cookies->set($cookie[0], $cookie[1] ?? null);
$request->cookies->set($cookie[0], $cookie[1] ?? '');
}
}
}

View File

@ -199,10 +199,21 @@ trait HttpClientTrait
$normalizedHeaders = [];
foreach ($headers as $name => $values) {
if (\is_object($values) && method_exists('__toString')) {
$values = (string) $values;
}
if (\is_int($name)) {
if (!\is_string($values)) {
throw new InvalidArgumentException(sprintf('Invalid value for header "%s": expected string, %s given.', $name, \gettype($values)));
}
[$name, $values] = explode(':', $values, 2);
$values = [ltrim($values)];
} elseif (!is_iterable($values)) {
if (\is_object($values)) {
throw new InvalidArgumentException(sprintf('Invalid value for header "%s": expected string, %s given.', $name, \get_class($values)));
}
$values = (array) $values;
}

View File

@ -11,31 +11,38 @@
namespace Symfony\Component\HttpClient;
use GuzzleHttp\Promise\Promise as GuzzlePromise;
use Http\Client\Exception\NetworkException;
use Http\Client\Exception\RequestException;
use Http\Client\HttpClient;
use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient as HttplugInterface;
use Http\Message\RequestFactory;
use Http\Message\StreamFactory;
use Http\Message\UriFactory;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Client\RequestExceptionInterface;
use Http\Promise\Promise;
use Http\Promise\RejectedPromise;
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7\Request;
use Nyholm\Psr7\Uri;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\HttpClient\Response\HttplugPromise;
use Symfony\Component\HttpClient\Response\ResponseTrait;
use Symfony\Component\HttpClient\Response\StreamWrapper;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
if (!interface_exists(HttpClient::class)) {
if (!interface_exists(HttplugInterface::class)) {
throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/httplug" package is not installed. Try running "composer require php-http/httplug".');
}
if (!interface_exists(ClientInterface::class)) {
throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "psr/http-client" package is not installed. Try running "composer require psr/http-client".');
}
if (!interface_exists(RequestFactory::class)) {
throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/message-factory" package is not installed. Try running "composer require nyholm/psr7".');
}
@ -43,42 +50,166 @@ if (!interface_exists(RequestFactory::class)) {
/**
* An adapter to turn a Symfony HttpClientInterface into an Httplug client.
*
* Run "composer require psr/http-client" to install the base ClientInterface. Run
* "composer require nyholm/psr7" to install an efficient implementation of response
* Run "composer require nyholm/psr7" to install an efficient implementation of response
* and stream factories with flex-provided autowiring aliases.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class HttplugClient implements HttpClient, RequestFactory, StreamFactory, UriFactory
final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestFactory, StreamFactory, UriFactory
{
private $client;
private $responseFactory;
private $streamFactory;
private $promisePool = [];
private $pendingResponse;
public function __construct(HttpClientInterface $client = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null)
{
$this->client = new Psr18Client($client, $responseFactory, $streamFactory);
$this->client = $client ?? HttpClient::create();
$this->responseFactory = $responseFactory;
$this->streamFactory = $streamFactory ?? ($responseFactory instanceof StreamFactoryInterface ? $responseFactory : null);
$this->promisePool = new \SplObjectStorage();
if (null !== $this->responseFactory && null !== $this->streamFactory) {
return;
}
if (!class_exists(Psr17Factory::class)) {
throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".');
}
$psr17Factory = new Psr17Factory();
$this->responseFactory = $this->responseFactory ?? $psr17Factory;
$this->streamFactory = $this->streamFactory ?? $psr17Factory;
}
/**
* {@inheritdoc}
*/
public function sendRequest(RequestInterface $request): ResponseInterface
public function sendRequest(RequestInterface $request): Psr7ResponseInterface
{
try {
return $this->client->sendRequest($request);
} catch (RequestExceptionInterface $e) {
throw new RequestException($e->getMessage(), $request, $e);
} catch (NetworkExceptionInterface $e) {
return $this->createPsr7Response($this->sendPsr7Request($request));
} catch (TransportExceptionInterface $e) {
throw new NetworkException($e->getMessage(), $request, $e);
}
}
/**
* {@inheritdoc}
*
* @return HttplugPromise
*/
public function sendAsyncRequest(RequestInterface $request): Promise
{
if (!class_exists(GuzzlePromise::class)) {
throw new \LogicException(sprintf('You cannot use "%s()" as the "guzzlehttp/promises" package is not installed. Try running "composer require guzzlehttp/promises".', __METHOD__));
}
try {
$response = $this->sendPsr7Request($request, true);
} catch (NetworkException $e) {
return new RejectedPromise($e);
}
$cancel = function () use ($response) {
$response->cancel();
unset($this->promisePool[$response]);
};
$promise = new GuzzlePromise(function () use ($response) {
$this->pendingResponse = $response;
$this->wait();
}, $cancel);
$this->promisePool[$response] = [$request, $promise];
return new HttplugPromise($promise, $cancel);
}
/**
* Resolve pending promises that complete before the timeouts are reached.
*
* When $maxDuration is null and $idleTimeout is reached, promises are rejected.
*
* @return int The number of remaining pending promises
*/
public function wait(float $maxDuration = null, float $idleTimeout = null): int
{
$pendingResponse = $this->pendingResponse;
$this->pendingResponse = null;
if (null !== $maxDuration) {
$startTime = microtime(true);
$idleTimeout = max(0.0, min($maxDuration / 5, $idleTimeout ?? $maxDuration));
$remainingDuration = $maxDuration;
}
do {
foreach ($this->client->stream($this->promisePool, $idleTimeout) as $response => $chunk) {
try {
if (null !== $maxDuration && $chunk->isTimeout()) {
goto check_duration;
}
if ($chunk->isFirst()) {
// Deactivate throwing on 3/4/5xx
$response->getStatusCode();
}
if (!$chunk->isLast()) {
goto check_duration;
}
if ([$request, $promise] = $this->promisePool[$response] ?? null) {
unset($this->promisePool[$response]);
$promise->resolve($this->createPsr7Response($response, true));
}
} catch (\Exception $e) {
if ([$request, $promise] = $this->promisePool[$response] ?? null) {
unset($this->promisePool[$response]);
if ($e instanceof TransportExceptionInterface) {
$e = new NetworkException($e->getMessage(), $request, $e);
}
$promise->reject($e);
}
}
if ($pendingResponse === $response) {
return \count($this->promisePool);
}
check_duration:
if (null !== $maxDuration && $idleTimeout && $idleTimeout > $remainingDuration = max(0.0, $maxDuration - microtime(true) + $startTime)) {
$idleTimeout = $remainingDuration / 5;
break;
}
}
if (!$count = \count($this->promisePool)) {
return 0;
}
} while (null !== $maxDuration && 0 < $remainingDuration);
return $count;
}
/**
* {@inheritdoc}
*/
public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1'): RequestInterface
{
$request = $this->client
->createRequest($method, $uri)
if ($this->responseFactory instanceof RequestFactoryInterface) {
$request = $this->responseFactory->createRequest($method, $uri);
} elseif (!class_exists(Request::class)) {
throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__));
} else {
$request = new Request($method, $uri);
}
$request = $request
->withProtocolVersion($protocolVersion)
->withBody($this->createStream($body))
;
@ -100,27 +231,84 @@ final class HttplugClient implements HttpClient, RequestFactory, StreamFactory,
}
if (\is_string($body ?? '')) {
$body = $this->client->createStream($body ?? '');
if ($body->isSeekable()) {
$body->seek(0);
}
return $body;
$stream = $this->streamFactory->createStream($body ?? '');
} elseif (\is_resource($body)) {
$stream = $this->streamFactory->createStreamFromResource($body);
} else {
throw new \InvalidArgumentException(sprintf('%s() expects string, resource or StreamInterface, %s given.', __METHOD__, \gettype($body)));
}
if (\is_resource($body)) {
return $this->client->createStreamFromResource($body);
if ($stream->isSeekable()) {
$stream->seek(0);
}
throw new \InvalidArgumentException(sprintf('%s() expects string, resource or StreamInterface, %s given.', __METHOD__, \gettype($body)));
return $stream;
}
/**
* {@inheritdoc}
*/
public function createUri($uri = ''): UriInterface
public function createUri($uri): UriInterface
{
return $uri instanceof UriInterface ? $uri : $this->client->createUri($uri);
if ($uri instanceof UriInterface) {
return $uri;
}
if ($this->responseFactory instanceof UriFactoryInterface) {
return $this->responseFactory->createUri($uri);
}
if (!class_exists(Uri::class)) {
throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__));
}
return new Uri($uri);
}
private function sendPsr7Request(RequestInterface $request, bool $buffer = null): ResponseInterface
{
try {
$body = $request->getBody();
if ($body->isSeekable()) {
$body->seek(0);
}
return $this->client->request($request->getMethod(), (string) $request->getUri(), [
'headers' => $request->getHeaders(),
'body' => $body->getContents(),
'http_version' => '1.0' === $request->getProtocolVersion() ? '1.0' : null,
'buffer' => $buffer,
]);
} catch (\InvalidArgumentException $e) {
throw new RequestException($e->getMessage(), $request, $e);
} catch (TransportExceptionInterface $e) {
throw new NetworkException($e->getMessage(), $request, $e);
}
}
private function createPsr7Response(ResponseInterface $response, bool $buffer = false): Psr7ResponseInterface
{
$psrResponse = $this->responseFactory->createResponse($response->getStatusCode());
foreach ($response->getHeaders(false) as $name => $values) {
foreach ($values as $value) {
$psrResponse = $psrResponse->withAddedHeader($name, $value);
}
}
if (isset(class_uses($response)[ResponseTrait::class])) {
$body = $this->streamFactory->createStreamFromResource($response->toStream(false));
} elseif (!$buffer) {
$body = $this->streamFactory->createStreamFromResource(StreamWrapper::createResource($response, $this->client));
} else {
$body = $this->streamFactory->createStream($response->getContent(false));
}
if ($body->isSeekable()) {
$body->seek(0);
}
return $psrResponse->withBody($body);
}
}

View File

@ -0,0 +1,73 @@
<?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\HttpClient\Response;
use GuzzleHttp\Promise\PromiseInterface as GuzzlePromiseInterface;
use Http\Promise\Promise as HttplugPromiseInterface;
use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*
* @internal
*/
final class HttplugPromise implements HttplugPromiseInterface
{
private $promise;
private $cancel;
public function __construct(GuzzlePromiseInterface $promise, callable $cancel = null)
{
$this->promise = $promise;
$this->cancel = $cancel;
}
public function then(callable $onFulfilled = null, callable $onRejected = null): self
{
return new self($this->promise->then($onFulfilled, $onRejected));
}
public function cancel(): void
{
$this->promise->cancel();
}
/**
* {@inheritdoc}
*/
public function getState(): string
{
return $this->promise->getState();
}
/**
* {@inheritdoc}
*
* @return Psr7ResponseInterface|mixed
*/
public function wait($unwrap = true)
{
return $this->promise->wait($unwrap);
}
public function __destruct()
{
if ($this->cancel) {
($this->cancel)();
}
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
}

View File

@ -0,0 +1,42 @@
<?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\HttpClient\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpClient\CachingHttpClient;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
use Symfony\Component\HttpKernel\HttpCache\Store;
class CachingHttpClientTest extends TestCase
{
public function testRequestHeaders()
{
$options = [
'headers' => [
'Application-Name' => 'test1234',
'Test-Name-Header' => 'test12345',
],
];
$mockClient = new MockHttpClient();
$store = new Store(sys_get_temp_dir().'/sf_http_cache');
$client = new CachingHttpClient($mockClient, $store, $options);
$response = $client->request('GET', 'http://example.com/foo-bar');
rmdir(sys_get_temp_dir().'/sf_http_cache');
self::assertInstanceOf(MockResponse::class, $response);
self::assertSame($response->getRequestOptions()['normalized_headers']['application-name'][0], 'Application-Name: test1234');
self::assertSame($response->getRequestOptions()['normalized_headers']['test-name-header'][0], 'Test-Name-Header: test12345');
}
}

View File

@ -13,7 +13,9 @@ namespace Symfony\Component\HttpClient\Tests;
use Http\Client\Exception\NetworkException;
use Http\Client\Exception\RequestException;
use Http\Promise\Promise;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpClient\HttplugClient;
use Symfony\Component\HttpClient\NativeHttpClient;
use Symfony\Contracts\HttpClient\Test\TestHttpServer;
@ -41,6 +43,38 @@ class HttplugClientTest extends TestCase
$this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']);
}
public function testSendAsyncRequest()
{
$client = new HttplugClient(new NativeHttpClient());
$promise = $client->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057'));
$successCallableCalled = false;
$failureCallableCalled = false;
$promise->then(function (ResponseInterface $response) use (&$successCallableCalled) {
$successCallableCalled = true;
return $response;
}, function (\Exception $exception) use (&$failureCallableCalled) {
$failureCallableCalled = true;
throw $exception;
});
$this->assertEquals(Promise::PENDING, $promise->getState());
$response = $promise->wait(true);
$this->assertTrue($successCallableCalled, '$promise->then() was never called.');
$this->assertFalse($failureCallableCalled, 'Failure callable should not be called when request is successful.');
$this->assertEquals(Promise::FULFILLED, $promise->getState());
$this->assertSame(200, $response->getStatusCode());
$this->assertSame('application/json', $response->getHeaderLine('content-type'));
$body = json_decode((string) $response->getBody(), true);
$this->assertSame('HTTP/1.1', $body['SERVER_PROTOCOL']);
}
public function testPostRequest()
{
$client = new HttplugClient(new NativeHttpClient());
@ -62,6 +96,32 @@ class HttplugClientTest extends TestCase
$client->sendRequest($client->createRequest('GET', 'http://localhost:8058'));
}
public function testAsyncNetworkException()
{
$client = new HttplugClient(new NativeHttpClient());
$promise = $client->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8058'));
$successCallableCalled = false;
$failureCallableCalled = false;
$promise->then(function (ResponseInterface $response) use (&$successCallableCalled) {
$successCallableCalled = true;
return $response;
}, function (\Exception $exception) use (&$failureCallableCalled) {
$failureCallableCalled = true;
throw $exception;
});
$promise->wait(false);
$this->assertFalse($successCallableCalled, 'Success callable should not be called when request fails.');
$this->assertTrue($failureCallableCalled, 'Failure callable was never called.');
$this->assertEquals(Promise::REJECTED, $promise->getState());
$this->expectException(NetworkException::class);
$promise->wait(true);
}
public function testRequestException()
{
$client = new HttplugClient(new NativeHttpClient());

View File

@ -26,6 +26,7 @@
"symfony/polyfill-php73": "^1.11"
},
"require-dev": {
"guzzlehttp/promises": "^1.3.1",
"nyholm/psr7": "^1.0",
"php-http/httplug": "^1.0|^2.0",
"psr/http-client": "^1.0",

View File

@ -16,6 +16,7 @@ CHANGELOG
* `InMemoryTransport` handle acknowledged and rejected messages.
* Made all dispatched worker event classes final.
* Added support for `from_transport` attribute on `messenger.message_handler` tag.
* Added support for passing `dbindex` as a query parameter to the redis transport DSN.
4.3.0
-----

View File

@ -104,6 +104,15 @@ class ConnectionTest extends TestCase
Connection::fromDsn('redis://password@localhost/queue', [], $redis);
}
public function testDbIndex()
{
$redis = new \Redis();
Connection::fromDsn('redis://password@localhost/queue?dbindex=2', [], $redis);
$this->assertSame(2, $redis->getDbNum());
}
public function testFirstGetPendingMessagesThenNewMessages()
{
$redis = $this->getMockBuilder(\Redis::class)->disableOriginalConstructor()->getMock();

View File

@ -32,6 +32,7 @@ class Connection
'consumer' => 'consumer',
'auto_setup' => true,
'stream_max_entries' => 0, // any value higher than 0 defines an approximate maximum number of stream entries
'dbindex' => 0,
];
private $connection;
@ -56,6 +57,10 @@ class Connection
$this->connection->auth($connectionCredentials['auth']);
}
if (($dbIndex = $configuration['dbindex'] ?? self::DEFAULT_OPTIONS['dbindex']) && !$this->connection->select($dbIndex)) {
throw new InvalidArgumentException(sprintf('Redis connection failed: %s', $redis->getLastError()));
}
$this->stream = $configuration['stream'] ?? self::DEFAULT_OPTIONS['stream'];
$this->group = $configuration['group'] ?? self::DEFAULT_OPTIONS['group'];
$this->consumer = $configuration['consumer'] ?? self::DEFAULT_OPTIONS['consumer'];
@ -97,12 +102,19 @@ class Connection
unset($redisOptions['stream_max_entries']);
}
$dbIndex = null;
if (\array_key_exists('dbindex', $redisOptions)) {
$dbIndex = filter_var($redisOptions['dbindex'], FILTER_VALIDATE_INT);
unset($redisOptions['dbindex']);
}
return new self([
'stream' => $stream,
'group' => $group,
'consumer' => $consumer,
'auto_setup' => $autoSetup,
'stream_max_entries' => $maxEntries,
'dbindex' => $dbIndex,
], $connectionCredentials, $redisOptions, $redis);
}

View File

@ -91,7 +91,7 @@ EOF;
while (true) {
try {
$this->signalingException = new \RuntimeException('preg_match(): Compilation failed: regular expression is too large');
$this->signalingException = new \RuntimeException('Compilation failed: regular expression is too large');
$compiledRoutes = array_merge($compiledRoutes, $this->compileDynamicRoutes($dynamicRoutes, $matchHost, $chunkLimit, $conditions));
break;
@ -349,7 +349,7 @@ EOF;
$state->markTail = 0;
// if the regex is too large, throw a signaling exception to recompute with smaller chunk size
set_error_handler(function ($type, $message) { throw 0 === strpos($message, $this->signalingException->getMessage()) ? $this->signalingException : new \ErrorException($message); });
set_error_handler(function ($type, $message) { throw false !== strpos($message, $this->signalingException->getMessage()) ? $this->signalingException : new \ErrorException($message); });
try {
preg_match($state->regex, '');
} finally {

View File

@ -197,6 +197,6 @@ class StaticPrefixCollection
public static function handleError($type, $msg)
{
return 0 === strpos($msg, 'preg_match(): Compilation failed: lookbehind assertion is not fixed length');
return false !== strpos($msg, 'Compilation failed: lookbehind assertion is not fixed length');
}
}

View File

@ -91,7 +91,7 @@ abstract class AbstractComparisonValidator extends ConstraintValidator
}
}
private function getPropertyAccessor()
private function getPropertyAccessor(): PropertyAccessorInterface
{
if (null === $this->propertyAccessor) {
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();

View File

@ -50,7 +50,7 @@ class ExpressionValidator extends ConstraintValidator
}
}
private function getExpressionLanguage()
private function getExpressionLanguage(): ExpressionLanguage
{
if (null === $this->expressionLanguage) {
$this->expressionLanguage = new ExpressionLanguage();

View File

@ -208,7 +208,7 @@ class FileValidator extends ConstraintValidator
* Convert the limit to the smallest possible number
* (i.e. try "MB", then "kB", then "bytes").
*/
private function factorizeSizes(int $size, int $limit, bool $binaryFormat)
private function factorizeSizes(int $size, int $limit, bool $binaryFormat): array
{
if ($binaryFormat) {
$coef = self::MIB_BYTES;

View File

@ -225,7 +225,7 @@ class IbanValidator extends ConstraintValidator
}
}
private static function toBigInt($string)
private static function toBigInt(string $string): string
{
$chars = str_split($string);
$bigInt = '';
@ -245,7 +245,7 @@ class IbanValidator extends ConstraintValidator
return $bigInt;
}
private static function bigModulo97($bigInt)
private static function bigModulo97(string $bigInt): int
{
$parts = str_split($bigInt, 7);
$rest = 0;

View File

@ -33,6 +33,6 @@ class ValidValidator extends ConstraintValidator
$this->context
->getValidator()
->inContext($this->context)
->validate($value, null, [$this->context->getGroup()]);
->validate($value, null, $this->context->getGroup());
}
}

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Validator\Context;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
use Symfony\Component\Validator\Mapping\MemberMetadata;
use Symfony\Component\Validator\Mapping\MetadataInterface;
@ -21,6 +22,7 @@ use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
use Symfony\Component\Validator\Util\PropertyPath;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Validator\Violation\ConstraintViolationBuilder;
use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
@ -188,7 +190,7 @@ class ExecutionContext implements ExecutionContextInterface
/**
* {@inheritdoc}
*/
public function buildViolation(string $message, array $parameters = [])
public function buildViolation(string $message, array $parameters = []): ConstraintViolationBuilderInterface
{
return new ConstraintViolationBuilder(
$this->violations,
@ -206,7 +208,7 @@ class ExecutionContext implements ExecutionContextInterface
/**
* {@inheritdoc}
*/
public function getViolations()
public function getViolations(): ConstraintViolationListInterface
{
return $this->violations;
}
@ -214,7 +216,7 @@ class ExecutionContext implements ExecutionContextInterface
/**
* {@inheritdoc}
*/
public function getValidator()
public function getValidator(): ValidatorInterface
{
return $this->validator;
}
@ -246,7 +248,7 @@ class ExecutionContext implements ExecutionContextInterface
/**
* {@inheritdoc}
*/
public function getMetadata()
public function getMetadata(): ?MetadataInterface
{
return $this->metadata;
}
@ -254,12 +256,12 @@ class ExecutionContext implements ExecutionContextInterface
/**
* {@inheritdoc}
*/
public function getGroup()
public function getGroup(): ?string
{
return $this->group;
}
public function getConstraint()
public function getConstraint(): ?Constraint
{
return $this->constraint;
}
@ -267,7 +269,7 @@ class ExecutionContext implements ExecutionContextInterface
/**
* {@inheritdoc}
*/
public function getClassName()
public function getClassName(): ?string
{
return $this->metadata instanceof MemberMetadata || $this->metadata instanceof ClassMetadataInterface ? $this->metadata->getClassName() : null;
}
@ -275,7 +277,7 @@ class ExecutionContext implements ExecutionContextInterface
/**
* {@inheritdoc}
*/
public function getPropertyName()
public function getPropertyName(): ?string
{
return $this->metadata instanceof PropertyMetadataInterface ? $this->metadata->getPropertyName() : null;
}
@ -283,7 +285,7 @@ class ExecutionContext implements ExecutionContextInterface
/**
* {@inheritdoc}
*/
public function getPropertyPath(string $subPath = '')
public function getPropertyPath(string $subPath = ''): string
{
return PropertyPath::append($this->propertyPath, $subPath);
}
@ -303,7 +305,7 @@ class ExecutionContext implements ExecutionContextInterface
/**
* {@inheritdoc}
*/
public function isGroupValidated(string $cacheKey, string $groupHash)
public function isGroupValidated(string $cacheKey, string $groupHash): bool
{
return isset($this->validatedObjects[$cacheKey][$groupHash]);
}
@ -319,7 +321,7 @@ class ExecutionContext implements ExecutionContextInterface
/**
* {@inheritdoc}
*/
public function isConstraintValidated(string $cacheKey, string $constraintHash)
public function isConstraintValidated(string $cacheKey, string $constraintHash): bool
{
return isset($this->validatedConstraints[$cacheKey.':'.$constraintHash]);
}
@ -335,7 +337,7 @@ class ExecutionContext implements ExecutionContextInterface
/**
* {@inheritdoc}
*/
public function isObjectInitialized(string $cacheKey)
public function isObjectInitialized(string $cacheKey): bool
{
return isset($this->initializedObjects[$cacheKey]);
}

View File

@ -204,7 +204,7 @@
</trans-unit>
<trans-unit id="54">
<source>This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more.</source>
<target>คอเล็กชั่นนี้ควรจะประกอบไปด้วยอ่างน้อย {{ limit }} สมาชิก</target>
<target>คอเล็กชั่นนี้ควรจะประกอบไปด้วยอ่างน้อย {{ limit }} สมาชิก</target>
</trans-unit>
<trans-unit id="55">
<source>This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less.</source>
@ -298,6 +298,74 @@
<source>The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed.</source>
<target>ภาพนี้เป็นแนวตั้ง ({{ width }}x{{ height }}px) ไม่อนุญาตภาพที่เป็นแนวตั้ง</target>
</trans-unit>
<trans-unit id="78">
<source>An empty file is not allowed.</source>
<target>ไม่อนุญาตให้ใช้ไฟล์ว่าง</target>
</trans-unit>
<trans-unit id="79">
<source>The host could not be resolved.</source>
<target>ไม่สามารถแก้ไขชื่อโฮสต์</target>
</trans-unit>
<trans-unit id="80">
<source>This value does not match the expected {{ charset }} charset.</source>
<target>ค่านี้ไม่ตรงกับการเข้ารหัส {{ charset }}</target>
</trans-unit>
<trans-unit id="81">
<source>This is not a valid Business Identifier Code (BIC).</source>
<target>นี่ไม่ถูกต้องตามรหัสสำหรับระบุธุรกิจนี้ (BIC)</target>
</trans-unit>
<trans-unit id="82">
<source>Error</source>
<target>เกิดข้อผิดพลาด</target>
</trans-unit>
<trans-unit id="83">
<source>This is not a valid UUID.</source>
<target>นี่ไม่ใช่ UUID ที่ถูกต้อง</target>
</trans-unit>
<trans-unit id="84">
<source>This value should be a multiple of {{ compared_value }}.</source>
<target>ค่านี้ควรเป็น {{ compared_value }} หลายตัว</target>
</trans-unit>
<trans-unit id="85">
<source>This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}.</source>
<target>รหัสสำหรับระบุธุรกิจนี้ (BIC) ไม่เกี่ยวข้องกับ IBAN {{ iban }}</target>
</trans-unit>
<trans-unit id="86">
<source>This value should be valid JSON.</source>
<target>ค่านี้ควรอยู่ในรูปแบบ JSON ที่ถูกต้อง</target>
</trans-unit>
<trans-unit id="87">
<source>This collection should contain only unique elements.</source>
<target>คอเล็กชั่นนี้ควรมีเฉพาะสมาชิกที่ไม่ซ้ำกันเท่านั้น</target>
</trans-unit>
<trans-unit id="88">
<source>This value should be positive.</source>
<target>ค่านี้ควรเป็นค่าบวก</target>
</trans-unit>
<trans-unit id="89">
<source>This value should be either positive or zero.</source>
<target>ค่านี้ควรเป็นค่าบวกหรือค่าศูนย์</target>
</trans-unit>
<trans-unit id="90">
<source>This value should be negative.</source>
<target>ค่านี้ควรเป็นค่าลบ</target>
</trans-unit>
<trans-unit id="91">
<source>This value should be either negative or zero.</source>
<target>ค่านี้ควรเป็นค่าลบหรือค่าศูนย์</target>
</trans-unit>
<trans-unit id="92">
<source>This value is not a valid timezone.</source>
<target>ค่าเขตเวลาไม่ถูกต้อง</target>
</trans-unit>
<trans-unit id="93">
<source>This password has been leaked in a data breach, it must not be used. Please use another password.</source>
<target>รหัสผ่านนี้ได้เคยรั่วไหลออกไปโดยถูกการละเมิดข้อมูล ซึ่งไม่ควรนำกลับมาใช้ กรุณาใช้รหัสผ่านอื่น</target>
</trans-unit>
<trans-unit id="94">
<source>This value should be between {{ min }} and {{ max }}.</source>
<target>ค่านี้ควรอยู่ระหว่าง {{ min }} ถึง {{ max }}</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -234,7 +234,7 @@ class ConstraintViolationAssertion
private $constraint;
private $cause;
public function __construct(ExecutionContextInterface $context, $message, Constraint $constraint = null, array $assertions = [])
public function __construct(ExecutionContextInterface $context, string $message, Constraint $constraint = null, array $assertions = [])
{
$this->context = $context;
$this->message = $message;
@ -242,14 +242,14 @@ class ConstraintViolationAssertion
$this->assertions = $assertions;
}
public function atPath($path)
public function atPath(string $path)
{
$this->propertyPath = $path;
return $this;
}
public function setParameter($key, $value)
public function setParameter(string $key, $value)
{
$this->parameters[$key] = $value;
@ -277,14 +277,14 @@ class ConstraintViolationAssertion
return $this;
}
public function setPlural($number)
public function setPlural(int $number)
{
$this->plural = $number;
return $this;
}
public function setCode($code)
public function setCode(string $code)
{
$this->code = $code;
@ -298,7 +298,7 @@ class ConstraintViolationAssertion
return $this;
}
public function buildNextViolation($message)
public function buildNextViolation(string $message): self
{
$assertions = $this->assertions;
$assertions[] = $this;
@ -326,7 +326,7 @@ class ConstraintViolationAssertion
}
}
private function getViolation()
private function getViolation(): ConstraintViolation
{
return new ConstraintViolation(
$this->message,

View File

@ -31,6 +31,9 @@ final class Validation
return self::createValidatorBuilder()->getValidator();
}
/**
* Creates a configurable builder for validator objects.
*/
public static function createValidatorBuilder(): ValidatorBuilder
{
return new ValidatorBuilder();

View File

@ -8,6 +8,8 @@ CHANGELOG
to configure casters & flags to use in tests
* added `ImagineCaster` and infrastructure to dump images
* added the stamps of a message after it is dispatched in `TraceableMessageBus` and `MessengerDataCollector` collected data
* added `UuidCaster`
* made all casters final
4.3.0
-----

View File

@ -17,6 +17,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts Amqp related classes to array representation.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @final since Symfony 4.4
*/
class AmqpCaster
{

View File

@ -17,6 +17,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts DOM related classes to array representation.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/
class DOMCaster
{

View File

@ -17,6 +17,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts DateTimeInterface related classes to array representation.
*
* @author Dany Maillard <danymaillard93b@gmail.com>
*
* @final since Symfony 4.4
*/
class DateCaster
{
@ -52,7 +54,7 @@ class DateCaster
return $filter & Caster::EXCLUDE_VERBOSE ? $i : $i + $a;
}
private static function formatInterval(\DateInterval $i)
private static function formatInterval(\DateInterval $i): string
{
$format = '%R ';

View File

@ -20,6 +20,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts Doctrine related classes to array representation.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/
class DoctrineCaster
{

View File

@ -20,6 +20,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts Ds extension classes to array representation.
*
* @author Jáchym Toušek <enumag@gmail.com>
*
* @final since Symfony 4.4
*/
class DsCaster
{

View File

@ -19,6 +19,8 @@ use Symfony\Component\VarDumper\Exception\ThrowingCasterException;
* Casts common Exception classes to array representation.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/
class ExceptionCaster
{

View File

@ -18,6 +18,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
*
* @author Hamza Amrouche <hamza.simperfit@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/
class GmpCaster
{

View File

@ -17,9 +17,9 @@ use Symfony\Component\VarDumper\Cloner\Stub;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class ImagineCaster
final class ImagineCaster
{
public static function castImage(ImageInterface $c, array $a, Stub $stub, bool $isNested)
public static function castImage(ImageInterface $c, array $a, Stub $stub, bool $isNested): array
{
$imgData = $c->get('png');
if (\strlen($imgData) > 1 * 1000 * 1000) {

View File

@ -16,6 +16,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
/**
* @author Nicolas Grekas <p@tchwork.com>
* @author Jan Schädlich <jan.schaedlich@sensiolabs.de>
*
* @final since Symfony 4.4
*/
class IntlCaster
{

View File

@ -15,6 +15,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
/**
* @author Jan Schädlich <jan.schaedlich@sensiolabs.de>
*
* @final since Symfony 4.4
*/
class MemcachedCaster
{
@ -33,7 +35,7 @@ class MemcachedCaster
return $a;
}
private static function getNonDefaultOptions(\Memcached $c)
private static function getNonDefaultOptions(\Memcached $c): array
{
self::$defaultOptions = self::$defaultOptions ?? self::discoverDefaultOptions();
self::$optionConstants = self::$optionConstants ?? self::getOptionConstants();
@ -48,7 +50,7 @@ class MemcachedCaster
return $nonDefaultOptions;
}
private static function discoverDefaultOptions()
private static function discoverDefaultOptions(): array
{
$defaultMemcached = new \Memcached();
$defaultMemcached->addServer('127.0.0.1', 11211);
@ -63,7 +65,7 @@ class MemcachedCaster
return $defaultOptions;
}
private static function getOptionConstants()
private static function getOptionConstants(): array
{
$reflectedMemcached = new \ReflectionClass(\Memcached::class);

View File

@ -17,6 +17,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts PDO related classes to array representation.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/
class PdoCaster
{

View File

@ -17,6 +17,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts pqsql resources to array representation.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/
class PgSqlCaster
{

View File

@ -16,6 +16,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/
class ProxyManagerCaster
{

View File

@ -17,6 +17,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts Redis class from ext-redis to array representation.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/
class RedisCaster
{

View File

@ -17,6 +17,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts Reflector related classes to array representation.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/
class ReflectionCaster
{
@ -361,7 +363,7 @@ class ReflectionCaster
return $signature;
}
private static function addExtra(&$a, \Reflector $c)
private static function addExtra(array &$a, \Reflector $c)
{
$x = isset($a[Caster::PREFIX_VIRTUAL.'extra']) ? $a[Caster::PREFIX_VIRTUAL.'extra']->value : [];
@ -377,7 +379,7 @@ class ReflectionCaster
}
}
private static function addMap(&$a, \Reflector $c, $map, $prefix = Caster::PREFIX_VIRTUAL)
private static function addMap(array &$a, \Reflector $c, array $map, string $prefix = Caster::PREFIX_VIRTUAL)
{
foreach ($map as $k => $m) {
if (method_exists($c, $m) && false !== ($m = $c->$m()) && null !== $m) {

View File

@ -17,6 +17,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts common resource types to array representation.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/
class ResourceCaster
{

View File

@ -17,6 +17,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts SPL related classes to array representation.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/
class SplCaster
{

View File

@ -14,6 +14,9 @@ namespace Symfony\Component\VarDumper\Caster;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\VarDumper\Cloner\Stub;
/**
* @final since Symfony 4.4
*/
class SymfonyCaster
{
private static $requestGetters = [

View File

@ -0,0 +1,30 @@
<?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\VarDumper\Caster;
use Ramsey\Uuid\UuidInterface;
use Symfony\Component\VarDumper\Cloner\Stub;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
final class UuidCaster
{
public static function castRamseyUuid(UuidInterface $c, array $a, Stub $stub, bool $isNested): array
{
$a += [
Caster::PREFIX_VIRTUAL.'uuid' => (string) $c,
];
return $a;
}
}

View File

@ -16,6 +16,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts XmlReader class to array representation.
*
* @author Baptiste Clavié <clavie.b@gmail.com>
*
* @final since Symfony 4.4
*/
class XmlReaderCaster
{

View File

@ -17,6 +17,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts XML resources to array representation.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/
class XmlResourceCaster
{

View File

@ -89,6 +89,8 @@ abstract class AbstractCloner implements ClonerInterface
'Imagine\Image\ImageInterface' => ['Symfony\Component\VarDumper\Caster\ImagineCaster', 'castImage'],
'Ramsey\Uuid\UuidInterface' => ['Symfony\Component\VarDumper\Caster\UuidCaster', 'castRamseyUuid'],
'ProxyManager\Proxy\ProxyInterface' => ['Symfony\Component\VarDumper\Caster\ProxyManagerCaster', 'castProxy'],
'PHPUnit_Framework_MockObject_MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
'PHPUnit\Framework\MockObject\MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],

View File

@ -76,7 +76,7 @@ trait VarDumperTestTrait
return rtrim($dumper->dump($data, true));
}
private function prepareExpectation($expected, int $filter)
private function prepareExpectation($expected, int $filter): string
{
if (!\is_string($expected)) {
$expected = $this->getDump($expected, null, $filter);

View File

@ -61,7 +61,7 @@ class GraphvizDumper implements DumperInterface
/**
* @internal
*/
protected function findPlaces(Definition $definition, Marking $marking = null)
protected function findPlaces(Definition $definition, Marking $marking = null): array
{
$workflowMetadata = $definition->getMetadataStore();
@ -96,7 +96,7 @@ class GraphvizDumper implements DumperInterface
/**
* @internal
*/
protected function findTransitions(Definition $definition)
protected function findTransitions(Definition $definition): array
{
$workflowMetadata = $definition->getMetadataStore();
@ -124,7 +124,7 @@ class GraphvizDumper implements DumperInterface
/**
* @internal
*/
protected function addPlaces(array $places)
protected function addPlaces(array $places): string
{
$code = '';
@ -145,7 +145,7 @@ class GraphvizDumper implements DumperInterface
/**
* @internal
*/
protected function addTransitions(array $transitions)
protected function addTransitions(array $transitions): string
{
$code = '';
@ -159,7 +159,7 @@ class GraphvizDumper implements DumperInterface
/**
* @internal
*/
protected function findEdges(Definition $definition)
protected function findEdges(Definition $definition): array
{
$workflowMetadata = $definition->getMetadataStore();
@ -192,7 +192,7 @@ class GraphvizDumper implements DumperInterface
/**
* @internal
*/
protected function addEdges(array $edges)
protected function addEdges(array $edges): string
{
$code = '';
@ -216,7 +216,7 @@ class GraphvizDumper implements DumperInterface
/**
* @internal
*/
protected function startDot(array $options)
protected function startDot(array $options): string
{
return sprintf("digraph workflow {\n %s\n node [%s];\n edge [%s];\n\n",
$this->addOptions($options['graph']),
@ -228,7 +228,7 @@ class GraphvizDumper implements DumperInterface
/**
* @internal
*/
protected function endDot()
protected function endDot(): string
{
return "}\n";
}
@ -236,7 +236,7 @@ class GraphvizDumper implements DumperInterface
/**
* @internal
*/
protected function dotize(string $id)
protected function dotize(string $id): string
{
return hash('sha1', $id);
}

View File

@ -44,7 +44,7 @@ class StateMachineGraphvizDumper extends GraphvizDumper
/**
* @internal
*/
protected function findEdges(Definition $definition)
protected function findEdges(Definition $definition): array
{
$workflowMetadata = $definition->getMetadataStore();
@ -82,7 +82,7 @@ class StateMachineGraphvizDumper extends GraphvizDumper
/**
* @internal
*/
protected function addEdges(array $edges)
protected function addEdges(array $edges): string
{
$code = '';

View File

@ -249,7 +249,7 @@ class Workflow implements WorkflowInterface
return $this->definition->getMetadataStore();
}
private function buildTransitionBlockerListForTransition(object $subject, Marking $marking, Transition $transition)
private function buildTransitionBlockerListForTransition(object $subject, Marking $marking, Transition $transition): TransitionBlockerList
{
foreach ($transition->getFroms() as $place) {
if (!$marking->has($place)) {

View File

@ -130,7 +130,7 @@ EOF
return ['file' => $file, 'valid' => true];
}
private function display(SymfonyStyle $io, array $files)
private function display(SymfonyStyle $io, array $files): int
{
switch ($this->format) {
case 'txt':
@ -142,7 +142,7 @@ EOF
}
}
private function displayTxt(SymfonyStyle $io, array $filesInfo)
private function displayTxt(SymfonyStyle $io, array $filesInfo): int
{
$countFiles = \count($filesInfo);
$erroredFiles = 0;
@ -171,7 +171,7 @@ EOF
return min($erroredFiles, 1);
}
private function displayJson(SymfonyStyle $io, array $filesInfo)
private function displayJson(SymfonyStyle $io, array $filesInfo): int
{
$errors = 0;
@ -191,7 +191,7 @@ EOF
return min($errors, 1);
}
private function getFiles(string $fileOrDirectory)
private function getFiles(string $fileOrDirectory): iterable
{
if (is_file($fileOrDirectory)) {
yield new \SplFileInfo($fileOrDirectory);
@ -218,7 +218,7 @@ EOF
return $yaml;
}
private function getParser()
private function getParser(): Parser
{
if (!$this->parser) {
$this->parser = new Parser();
@ -227,7 +227,7 @@ EOF
return $this->parser;
}
private function getDirectoryIterator(string $directory)
private function getDirectoryIterator(string $directory): iterable
{
$default = function ($directory) {
return new \RecursiveIteratorIterator(
@ -243,7 +243,7 @@ EOF
return $default($directory);
}
private function isReadable(string $fileOrDirectory)
private function isReadable(string $fileOrDirectory): bool
{
$default = function ($fileOrDirectory) {
return is_readable($fileOrDirectory);

View File

@ -1134,7 +1134,7 @@ class Parser
throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
}
private function parseQuotedString($yaml)
private function parseQuotedString(string $yaml): ?string
{
if ('' === $yaml || ('"' !== $yaml[0] && "'" !== $yaml[0])) {
throw new \InvalidArgumentException(sprintf('"%s" is not a quoted string.', $yaml));
@ -1223,7 +1223,7 @@ class Parser
return implode("\n", $lines);
}
private function lexInlineSequence($yaml)
private function lexInlineSequence(string $yaml): string
{
if ('' === $yaml || '[' !== $yaml[0]) {
throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml));