Merge branch '4.4'
* 4.4: (33 commits) [DI] fix processing of regular parameter bags by MergeExtensionConfigurationPass [FrameworkBundle] reset cache pools between requests [HttpFoundation] Accept must take the lead for Request::getPreferredFormat() [FrameworkBundle] Allow to use the BrowserKit assertions with Panther and API Platform's test client Use ConnectionRegistry instead of RegistryInterface. Fixes windows error Improving the request/response format autodetection [Messager] Simplified MessageBus::__construct() [WIP][Mailer] Overwrite envelope sender and recipients from config [Messenger] Added more test for MessageBus [Mime] Updated some PHPDoc contents [PropertyAccess] Adds entries to CHANGELOG and UPGRADE fixed typo [FrameworkBundle] Simplified some code in the DI configuration [Filesystem] added missing deprecations to UPGRADE-4.3.md [Filesystem] depreacte calling isAbsolutePath with a null Fix authentication for redis transport only decorate when an event dispatcher was passed [Messenger] Added support for auto trimming of redis streams [FrmaeworkBundle] More simplifications in the DI configuration ...
This commit is contained in:
commit
155cfb273f
|
@ -57,6 +57,12 @@ EventDispatcher
|
|||
* The signature of the `EventDispatcherInterface::dispatch()` method should be updated to `dispatch($event, string $eventName = null)`, not doing so is deprecated
|
||||
* The `Event` class has been deprecated, use `Symfony\Contracts\EventDispatcher\Event` instead
|
||||
|
||||
Filesystem
|
||||
----------
|
||||
|
||||
* Support for passing arrays to `Filesystem::dumpFile()` is deprecated.
|
||||
* Support for passing arrays to `Filesystem::appendToFile()` is deprecated.
|
||||
|
||||
Form
|
||||
----
|
||||
|
||||
|
@ -71,6 +77,7 @@ Form
|
|||
FrameworkBundle
|
||||
---------------
|
||||
|
||||
* Deprecated the `framework.templating` option, use Twig instead.
|
||||
* Not passing the project directory to the constructor of the `AssetsInstallCommand` is deprecated. This argument will
|
||||
be mandatory in 5.0.
|
||||
* Deprecated the "Psr\SimpleCache\CacheInterface" / "cache.app.simple" service, use "Symfony\Contracts\Cache\CacheInterface" / "cache.app" instead.
|
||||
|
|
|
@ -26,7 +26,7 @@ DependencyInjection
|
|||
services:
|
||||
App\Handler:
|
||||
tags: ['app.handler']
|
||||
|
||||
|
||||
App\HandlerCollection:
|
||||
arguments: [!tagged app.handler]
|
||||
```
|
||||
|
@ -36,11 +36,16 @@ DependencyInjection
|
|||
services:
|
||||
App\Handler:
|
||||
tags: ['app.handler']
|
||||
|
||||
|
||||
App\HandlerCollection:
|
||||
arguments: [!tagged_iterator app.handler]
|
||||
```
|
||||
|
||||
Filesystem
|
||||
----------
|
||||
|
||||
* Support for passing a `null` value to `Filesystem::isAbsolutePath()` is deprecated.
|
||||
|
||||
Form
|
||||
----
|
||||
|
||||
|
@ -60,6 +65,11 @@ HttpClient
|
|||
|
||||
* Added method `cancel()` to `ResponseInterface`
|
||||
|
||||
HttpFoundation
|
||||
--------------
|
||||
|
||||
* `ApacheRequest` is deprecated, use `Request` class instead.
|
||||
|
||||
HttpKernel
|
||||
----------
|
||||
|
||||
|
@ -76,6 +86,11 @@ MonologBridge
|
|||
|
||||
* The `RouteProcessor` has been marked final.
|
||||
|
||||
PropertyAccess
|
||||
--------------
|
||||
|
||||
* Deprecated passing `null` as 2nd argument of `PropertyAccessor::createCache()` method (`$defaultLifetime`), pass `0` instead.
|
||||
|
||||
Security
|
||||
--------
|
||||
|
||||
|
@ -84,11 +99,19 @@ Security
|
|||
TwigBridge
|
||||
----------
|
||||
|
||||
* Deprecated to pass `$rootDir` and `$fileLinkFormatter` as 5th and 6th argument respectively to the
|
||||
* Deprecated to pass `$rootDir` and `$fileLinkFormatter` as 5th and 6th argument respectively to the
|
||||
`DebugCommand::__construct()` method, swap the variables position.
|
||||
|
||||
Validator
|
||||
---------
|
||||
|
||||
* Deprecated passing an `ExpressionLanguage` instance as the second argument of `ExpressionValidator::__construct()`.
|
||||
* Deprecated using anything else than a `string` as the code of a `ConstraintViolation`, a `string` type-hint will
|
||||
be added to the constructor of the `ConstraintViolation` class and to the `ConstraintViolationBuilder::setCode()`
|
||||
method in 5.0.
|
||||
* Deprecated passing an `ExpressionLanguage` instance as the second argument of `ExpressionValidator::__construct()`.
|
||||
Pass it as the first argument instead.
|
||||
* The `Length` constraint expects the `allowEmptyString` option to be defined
|
||||
when the `min` option is used.
|
||||
Set it to `true` to keep the current behavior and `false` to reject empty strings.
|
||||
In 5.0, it'll become optional and will default to `false`.
|
||||
|
|
|
@ -101,7 +101,7 @@ DependencyInjection
|
|||
services:
|
||||
App\Handler:
|
||||
tags: ['app.handler']
|
||||
|
||||
|
||||
App\HandlerCollection:
|
||||
arguments: [!tagged_iterator app.handler]
|
||||
```
|
||||
|
@ -114,7 +114,6 @@ DoctrineBridge
|
|||
* Passing an `IdReader` to the `DoctrineChoiceLoader` when the query cannot be optimized with single id field will throw an exception, pass `null` instead
|
||||
* Not passing an `IdReader` to the `DoctrineChoiceLoader` when the query can be optimized with single id field will not apply any optimization
|
||||
|
||||
|
||||
DomCrawler
|
||||
----------
|
||||
|
||||
|
@ -135,6 +134,7 @@ EventDispatcher
|
|||
Filesystem
|
||||
----------
|
||||
|
||||
* The `Filesystem::isAbsolutePath()` method no longer supports `null` in the `$file` argument.
|
||||
* The `Filesystem::dumpFile()` method no longer supports arrays in the `$content` argument.
|
||||
* The `Filesystem::appendToFile()` method no longer supports arrays in the `$content` argument.
|
||||
|
||||
|
@ -207,8 +207,8 @@ Form
|
|||
FrameworkBundle
|
||||
---------------
|
||||
|
||||
* Removed the `framework.templating` option, use Twig instead.
|
||||
* The project dir argument of the constructor of `AssetsInstallCommand` is required.
|
||||
|
||||
* Removed support for `bundle:controller:action` syntax to reference controllers. Use `serviceOrFqcn::method`
|
||||
instead where `serviceOrFqcn` is either the service ID when using controllers as services or the FQCN of the controller.
|
||||
|
||||
|
@ -269,6 +269,7 @@ HttpFoundation
|
|||
use `Symfony\Component\Mime\FileBinaryMimeTypeGuesser` instead.
|
||||
* The `FileinfoMimeTypeGuesser` class has been removed,
|
||||
use `Symfony\Component\Mime\FileinfoMimeTypeGuesser` instead.
|
||||
* `ApacheRequest` has been removed, use the `Request` class instead.
|
||||
|
||||
HttpKernel
|
||||
----------
|
||||
|
@ -476,6 +477,8 @@ TwigBridge
|
|||
Validator
|
||||
--------
|
||||
|
||||
* Removed support for non-string codes of a `ConstraintViolation`. A `string` type-hint was added to the constructor of
|
||||
the `ConstraintViolation` class and to the `ConstraintViolationBuilder::setCode()` method.
|
||||
* An `ExpressionLanguage` instance or null must be passed as the first argument of `ExpressionValidator::__construct()`
|
||||
* The `checkMX` and `checkHost` options of the `Email` constraint were removed
|
||||
* The `Email::__construct()` 'strict' property has been removed. Use 'mode'=>"strict" instead.
|
||||
|
@ -540,7 +543,6 @@ Workflow
|
|||
property: state
|
||||
```
|
||||
|
||||
|
||||
* Support for using a workflow with a single state marking is dropped. Use a state machine instead.
|
||||
|
||||
Before:
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
namespace Symfony\Bridge\Doctrine\Tests\Fixtures;
|
||||
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* Class BaseUser.
|
||||
*/
|
||||
|
@ -46,4 +49,15 @@ class BaseUser
|
|||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public static function loadValidatorMetadata(ClassMetadata $metadata): void
|
||||
{
|
||||
$allowEmptyString = property_exists(Assert\Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
|
||||
|
||||
$metadata->addPropertyConstraint('username', new Assert\Length([
|
||||
'min' => 2,
|
||||
'max' => 120,
|
||||
'groups' => ['Registration'],
|
||||
] + $allowEmptyString));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures;
|
|||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
|
@ -36,13 +37,11 @@ class DoctrineLoaderEntity extends DoctrineLoaderParentEntity
|
|||
|
||||
/**
|
||||
* @ORM\Column(length=20)
|
||||
* @Assert\Length(min=5)
|
||||
*/
|
||||
public $mergedMaxLength;
|
||||
|
||||
/**
|
||||
* @ORM\Column(length=20)
|
||||
* @Assert\Length(min=1, max=10)
|
||||
*/
|
||||
public $alreadyMappedMaxLength;
|
||||
|
||||
|
@ -69,4 +68,12 @@ class DoctrineLoaderEntity extends DoctrineLoaderParentEntity
|
|||
|
||||
/** @ORM\Column(type="simple_array", length=100) */
|
||||
public $simpleArrayField = [];
|
||||
|
||||
public static function loadValidatorMetadata(ClassMetadata $metadata): void
|
||||
{
|
||||
$allowEmptyString = property_exists(Assert\Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
|
||||
|
||||
$metadata->addPropertyConstraint('mergedMaxLength', new Assert\Length(['min' => 5] + $allowEmptyString));
|
||||
$metadata->addPropertyConstraint('alreadyMappedMaxLength', new Assert\Length(['min' => 1, 'max' => 10] + $allowEmptyString));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,6 @@
|
|||
<constraint name="NotBlank">
|
||||
<option name="groups">Registration</option>
|
||||
</constraint>
|
||||
<constraint name="Length">
|
||||
<option name="min">2</option>
|
||||
<option name="max">120</option>
|
||||
<option name="groups">Registration</option>
|
||||
</constraint>
|
||||
</property>
|
||||
</class>
|
||||
</constraint-mapping>
|
||||
|
|
|
@ -40,6 +40,7 @@ class DoctrineLoaderTest extends TestCase
|
|||
}
|
||||
|
||||
$validator = Validation::createValidatorBuilder()
|
||||
->addMethodMapping('loadValidatorMetadata')
|
||||
->enableAnnotationMapping()
|
||||
->addLoader(new DoctrineLoader(DoctrineTestHelper::createTestEntityManager(), '{^Symfony\\\\Bridge\\\\Doctrine\\\\Tests\\\\Fixtures\\\\DoctrineLoader}'))
|
||||
->getValidator()
|
||||
|
@ -142,6 +143,7 @@ class DoctrineLoaderTest extends TestCase
|
|||
}
|
||||
|
||||
$validator = Validation::createValidatorBuilder()
|
||||
->addMethodMapping('loadValidatorMetadata')
|
||||
->enableAnnotationMapping()
|
||||
->addXmlMappings([__DIR__.'/../Resources/validator/BaseUser.xml'])
|
||||
->addLoader(
|
||||
|
|
|
@ -131,7 +131,7 @@ if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__
|
|||
$prevRoot = getenv('COMPOSER_ROOT_VERSION');
|
||||
putenv("COMPOSER_ROOT_VERSION=$PHPUNIT_VERSION.99");
|
||||
// --no-suggest is not in the list to keep compat with composer 1.0, which is shipped with Ubuntu 16.04LTS
|
||||
$exit = proc_close(proc_open("$COMPOSER install --no-dev --prefer-dist --no-progress --ansi", array(), $p, getcwd(), null, array('bypass_shell' => true)));
|
||||
$exit = proc_close(proc_open("$COMPOSER install --no-dev --prefer-dist --no-progress --ansi", array(), $p));
|
||||
putenv('COMPOSER_ROOT_VERSION'.(false !== $prevRoot ? '='.$prevRoot : ''));
|
||||
if ($exit) {
|
||||
exit($exit);
|
||||
|
|
|
@ -29,6 +29,7 @@ CHANGELOG
|
|||
4.3.0
|
||||
-----
|
||||
|
||||
* Deprecated the `framework.templating` option, use Twig instead.
|
||||
* Added `WebTestAssertionsTrait` (included by default in `WebTestCase`)
|
||||
* Renamed `Client` to `KernelBrowser`
|
||||
* Not passing the project directory to the constructor of the `AssetsInstallCommand` is deprecated. This argument will
|
||||
|
|
|
@ -297,10 +297,7 @@ class Configuration implements ConfigurationInterface
|
|||
->cannotBeEmpty()
|
||||
->end()
|
||||
->arrayNode('initial_marking')
|
||||
->beforeNormalization()
|
||||
->ifTrue(function ($v) { return !\is_array($v); })
|
||||
->then(function ($v) { return [$v]; })
|
||||
->end()
|
||||
->beforeNormalization()->castToArray()->end()
|
||||
->defaultValue([])
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
|
@ -533,10 +530,7 @@ class Configuration implements ConfigurationInterface
|
|||
->ifTrue(function ($v) { return \is_array($v) && isset($v['mime_type']); })
|
||||
->then(function ($v) { return $v['mime_type']; })
|
||||
->end()
|
||||
->beforeNormalization()
|
||||
->ifTrue(function ($v) { return !\is_array($v); })
|
||||
->then(function ($v) { return [$v]; })
|
||||
->end()
|
||||
->beforeNormalization()->castToArray()->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
|
@ -562,10 +556,7 @@ class Configuration implements ConfigurationInterface
|
|||
->scalarNode('base_path')->defaultValue('')->end()
|
||||
->arrayNode('base_urls')
|
||||
->requiresAtLeastOneElement()
|
||||
->beforeNormalization()
|
||||
->ifTrue(function ($v) { return !\is_array($v); })
|
||||
->then(function ($v) { return [$v]; })
|
||||
->end()
|
||||
->beforeNormalization()->castToArray()->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
|
@ -607,10 +598,7 @@ class Configuration implements ConfigurationInterface
|
|||
->scalarNode('base_path')->defaultValue('')->end()
|
||||
->arrayNode('base_urls')
|
||||
->requiresAtLeastOneElement()
|
||||
->beforeNormalization()
|
||||
->ifTrue(function ($v) { return !\is_array($v); })
|
||||
->then(function ($v) { return [$v]; })
|
||||
->end()
|
||||
->beforeNormalization()->castToArray()->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
|
@ -684,10 +672,7 @@ class Configuration implements ConfigurationInterface
|
|||
->defaultValue(['loadValidatorMetadata'])
|
||||
->prototype('scalar')->end()
|
||||
->treatFalseLike([])
|
||||
->validate()
|
||||
->ifTrue(function ($v) { return !\is_array($v); })
|
||||
->then(function ($v) { return (array) $v; })
|
||||
->end()
|
||||
->validate()->castToArray()->end()
|
||||
->end()
|
||||
->scalarNode('translation_domain')->defaultValue('validators')->end()
|
||||
->enumNode('email_validation_mode')->values(['html5', 'loose', 'strict'])->end()
|
||||
|
@ -1061,9 +1046,14 @@ class Configuration implements ConfigurationInterface
|
|||
->end()
|
||||
->arrayNode('retry_strategy')
|
||||
->addDefaultsIfNotSet()
|
||||
->validate()
|
||||
->ifTrue(function ($v) { return null !== $v['service'] && (isset($v['max_retries']) || isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay'])); })
|
||||
->thenInvalid('"service" cannot be used along with the other retry_strategy options.')
|
||||
->beforeNormalization()
|
||||
->always(function ($v) {
|
||||
if (isset($v['service']) && (isset($v['max_retries']) || isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay']))) {
|
||||
throw new \InvalidArgumentException('The "service" cannot be used along with the other "retry_strategy" options.');
|
||||
}
|
||||
|
||||
return $v;
|
||||
})
|
||||
->end()
|
||||
->children()
|
||||
->scalarNode('service')->defaultNull()->info('Service id to override the retry strategy entirely')->end()
|
||||
|
@ -1280,6 +1270,9 @@ class Configuration implements ConfigurationInterface
|
|||
->scalarNode('auth_bearer')
|
||||
->info('A token enabling HTTP Bearer authorization.')
|
||||
->end()
|
||||
->scalarNode('auth_ntlm')
|
||||
->info('A "username:password" pair to use Microsoft NTLM authentication (requires the cURL extension).')
|
||||
->end()
|
||||
->arrayNode('query')
|
||||
->info('Associative array of query string values merged with the base URI.')
|
||||
->useAttributeAsKey('key')
|
||||
|
@ -1391,6 +1384,22 @@ class Configuration implements ConfigurationInterface
|
|||
->{!class_exists(FullStack::class) && class_exists(Mailer::class) ? 'canBeDisabled' : 'canBeEnabled'}()
|
||||
->children()
|
||||
->scalarNode('dsn')->defaultValue('smtp://null')->end()
|
||||
->arrayNode('envelope')
|
||||
->info('Mailer Envelope configuration')
|
||||
->children()
|
||||
->scalarNode('sender')->end()
|
||||
->arrayNode('recipients')
|
||||
->performNoDeepMerging()
|
||||
->beforeNormalization()
|
||||
->ifArray()
|
||||
->then(function ($v) {
|
||||
return array_filter(array_values($v));
|
||||
})
|
||||
->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
|
|
|
@ -1763,6 +1763,13 @@ class FrameworkExtension extends Extension
|
|||
|
||||
$loader->load('mailer.xml');
|
||||
$container->getDefinition('mailer.default_transport')->setArgument(0, $config['dsn']);
|
||||
|
||||
$recipients = $config['envelope']['recipients'] ?? null;
|
||||
$sender = $config['envelope']['sender'] ?? null;
|
||||
|
||||
$envelopeListener = $container->getDefinition('mailer.envelope_listener');
|
||||
$envelopeListener->setArgument(0, $sender);
|
||||
$envelopeListener->setArgument(1, $recipients);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<defaults public="false" />
|
||||
|
||||
<service id="cache.app" parent="cache.adapter.filesystem" public="true">
|
||||
<tag name="cache.pool" clearer="cache.app_clearer" reset="reset" />
|
||||
<tag name="cache.pool" clearer="cache.app_clearer" />
|
||||
</service>
|
||||
|
||||
<service id="cache.app.taggable" class="Symfony\Component\Cache\Adapter\TagAwareAdapter">
|
||||
|
@ -41,7 +41,7 @@
|
|||
|
||||
<service id="cache.adapter.system" class="Symfony\Component\Cache\Adapter\AdapterInterface" abstract="true">
|
||||
<factory class="Symfony\Component\Cache\Adapter\AbstractAdapter" method="createSystemCache" />
|
||||
<tag name="cache.pool" clearer="cache.system_clearer" />
|
||||
<tag name="cache.pool" clearer="cache.system_clearer" reset="reset" />
|
||||
<tag name="monolog.logger" channel="cache" />
|
||||
<argument /> <!-- namespace -->
|
||||
<argument>0</argument> <!-- default lifetime -->
|
||||
|
@ -51,7 +51,7 @@
|
|||
</service>
|
||||
|
||||
<service id="cache.adapter.apcu" class="Symfony\Component\Cache\Adapter\ApcuAdapter" abstract="true">
|
||||
<tag name="cache.pool" clearer="cache.default_clearer" />
|
||||
<tag name="cache.pool" clearer="cache.default_clearer" reset="reset" />
|
||||
<tag name="monolog.logger" channel="cache" />
|
||||
<argument /> <!-- namespace -->
|
||||
<argument>0</argument> <!-- default lifetime -->
|
||||
|
@ -62,7 +62,7 @@
|
|||
</service>
|
||||
|
||||
<service id="cache.adapter.doctrine" class="Symfony\Component\Cache\Adapter\DoctrineAdapter" abstract="true">
|
||||
<tag name="cache.pool" provider="cache.default_doctrine_provider" clearer="cache.default_clearer" />
|
||||
<tag name="cache.pool" provider="cache.default_doctrine_provider" clearer="cache.default_clearer" reset="reset" />
|
||||
<tag name="monolog.logger" channel="cache" />
|
||||
<argument /> <!-- Doctrine provider service -->
|
||||
<argument /> <!-- namespace -->
|
||||
|
@ -73,7 +73,7 @@
|
|||
</service>
|
||||
|
||||
<service id="cache.adapter.filesystem" class="Symfony\Component\Cache\Adapter\FilesystemAdapter" abstract="true">
|
||||
<tag name="cache.pool" clearer="cache.default_clearer" />
|
||||
<tag name="cache.pool" clearer="cache.default_clearer" reset="reset" />
|
||||
<tag name="monolog.logger" channel="cache" />
|
||||
<argument /> <!-- namespace -->
|
||||
<argument>0</argument> <!-- default lifetime -->
|
||||
|
@ -85,14 +85,14 @@
|
|||
</service>
|
||||
|
||||
<service id="cache.adapter.psr6" class="Symfony\Component\Cache\Adapter\ProxyAdapter" abstract="true">
|
||||
<tag name="cache.pool" provider="cache.default_psr6_provider" clearer="cache.default_clearer" />
|
||||
<tag name="cache.pool" provider="cache.default_psr6_provider" clearer="cache.default_clearer" reset="reset" />
|
||||
<argument /> <!-- PSR-6 provider service -->
|
||||
<argument /> <!-- namespace -->
|
||||
<argument>0</argument> <!-- default lifetime -->
|
||||
</service>
|
||||
|
||||
<service id="cache.adapter.redis" class="Symfony\Component\Cache\Adapter\RedisAdapter" abstract="true">
|
||||
<tag name="cache.pool" provider="cache.default_redis_provider" clearer="cache.default_clearer" />
|
||||
<tag name="cache.pool" provider="cache.default_redis_provider" clearer="cache.default_clearer" reset="reset" />
|
||||
<tag name="monolog.logger" channel="cache" />
|
||||
<argument /> <!-- Redis connection service -->
|
||||
<argument /> <!-- namespace -->
|
||||
|
@ -129,7 +129,7 @@
|
|||
</service>
|
||||
|
||||
<service id="cache.adapter.array" class="Symfony\Component\Cache\Adapter\ArrayAdapter" abstract="true">
|
||||
<tag name="cache.pool" clearer="cache.default_clearer" />
|
||||
<tag name="cache.pool" clearer="cache.default_clearer" reset="reset" />
|
||||
<tag name="monolog.logger" channel="cache" />
|
||||
<argument>0</argument> <!-- default lifetime -->
|
||||
<call method="setLogger">
|
||||
|
|
|
@ -25,5 +25,11 @@
|
|||
<argument type="service" id="mailer.default_transport" />
|
||||
<tag name="messenger.message_handler" />
|
||||
</service>
|
||||
|
||||
<service id="mailer.envelope_listener" class="Symfony\Component\Mailer\EventListener\EnvelopeListener">
|
||||
<argument /> <!-- sender -->
|
||||
<argument /> <!-- recipients -->
|
||||
<tag name="kernel.event_subscriber"/>
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
<?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\Bundle\FrameworkBundle\Test;
|
||||
|
||||
use PHPUnit\Framework\Constraint\LogicalAnd;
|
||||
use PHPUnit\Framework\Constraint\LogicalNot;
|
||||
use Symfony\Component\BrowserKit\AbstractBrowser;
|
||||
use Symfony\Component\BrowserKit\Test\Constraint as BrowserKitConstraint;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Test\Constraint as ResponseConstraint;
|
||||
|
||||
/**
|
||||
* Ideas borrowed from Laravel Dusk's assertions.
|
||||
*
|
||||
* @see https://laravel.com/docs/5.7/dusk#available-assertions
|
||||
*/
|
||||
trait BrowserKitAssertionsTrait
|
||||
{
|
||||
public static function assertResponseIsSuccessful(string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), new ResponseConstraint\ResponseIsSuccessful(), $message);
|
||||
}
|
||||
|
||||
public static function assertResponseStatusCodeSame(int $expectedCode, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), new ResponseConstraint\ResponseStatusCodeSame($expectedCode), $message);
|
||||
}
|
||||
|
||||
public static function assertResponseRedirects(string $expectedLocation = null, int $expectedCode = null, string $message = ''): void
|
||||
{
|
||||
$constraint = new ResponseConstraint\ResponseIsRedirected();
|
||||
if ($expectedLocation) {
|
||||
$constraint = LogicalAnd::fromConstraints($constraint, new ResponseConstraint\ResponseHeaderSame('Location', $expectedLocation));
|
||||
}
|
||||
if ($expectedCode) {
|
||||
$constraint = LogicalAnd::fromConstraints($constraint, new ResponseConstraint\ResponseStatusCodeSame($expectedCode));
|
||||
}
|
||||
|
||||
self::assertThat(self::getResponse(), $constraint, $message);
|
||||
}
|
||||
|
||||
public static function assertResponseHasHeader(string $headerName, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), new ResponseConstraint\ResponseHasHeader($headerName), $message);
|
||||
}
|
||||
|
||||
public static function assertResponseNotHasHeader(string $headerName, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHasHeader($headerName)), $message);
|
||||
}
|
||||
|
||||
public static function assertResponseHeaderSame(string $headerName, string $expectedValue, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue), $message);
|
||||
}
|
||||
|
||||
public static function assertResponseHeaderNotSame(string $headerName, string $expectedValue, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue)), $message);
|
||||
}
|
||||
|
||||
public static function assertResponseHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), new ResponseConstraint\ResponseHasCookie($name, $path, $domain), $message);
|
||||
}
|
||||
|
||||
public static function assertResponseNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHasCookie($name, $path, $domain)), $message);
|
||||
}
|
||||
|
||||
public static function assertResponseCookieValueSame(string $name, string $expectedValue, string $path = '/', string $domain = null, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), LogicalAnd::fromConstraints(
|
||||
new ResponseConstraint\ResponseHasCookie($name, $path, $domain),
|
||||
new ResponseConstraint\ResponseCookieValueSame($name, $expectedValue, $path, $domain)
|
||||
), $message);
|
||||
}
|
||||
|
||||
public static function assertBrowserHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getClient(), new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain), $message);
|
||||
}
|
||||
|
||||
public static function assertBrowserNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getClient(), new LogicalNot(new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain)), $message);
|
||||
}
|
||||
|
||||
public static function assertBrowserCookieValueSame(string $name, string $expectedValue, bool $raw = false, string $path = '/', string $domain = null, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getClient(), LogicalAnd::fromConstraints(
|
||||
new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain),
|
||||
new BrowserKitConstraint\BrowserCookieValueSame($name, $expectedValue, $raw, $path, $domain)
|
||||
), $message);
|
||||
}
|
||||
|
||||
public static function assertRequestAttributeValueSame(string $name, string $expectedValue, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getRequest(), new ResponseConstraint\RequestAttributeValueSame($name, $expectedValue), $message);
|
||||
}
|
||||
|
||||
public static function assertRouteSame($expectedRoute, array $parameters = [], string $message = ''): void
|
||||
{
|
||||
$constraint = new ResponseConstraint\RequestAttributeValueSame('_route', $expectedRoute);
|
||||
$constraints = [];
|
||||
foreach ($parameters as $key => $value) {
|
||||
$constraints[] = new ResponseConstraint\RequestAttributeValueSame($key, $value);
|
||||
}
|
||||
if ($constraints) {
|
||||
$constraint = LogicalAnd::fromConstraints($constraint, ...$constraints);
|
||||
}
|
||||
|
||||
self::assertThat(self::getRequest(), $constraint, $message);
|
||||
}
|
||||
|
||||
private static function getClient(AbstractBrowser $newClient = null): ?AbstractBrowser
|
||||
{
|
||||
static $client;
|
||||
|
||||
if (0 < \func_num_args()) {
|
||||
return $client = $newClient;
|
||||
}
|
||||
|
||||
if (!$client instanceof AbstractBrowser) {
|
||||
static::fail(sprintf('A client must be set to make assertions on it. Did you forget to call "%s::createClient()"?', __CLASS__));
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
private static function getResponse(): Response
|
||||
{
|
||||
if (!$response = self::getClient()->getResponse()) {
|
||||
static::fail('A client must have an HTTP Response to make assertions. Did you forget to make an HTTP request?');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private static function getRequest(): Request
|
||||
{
|
||||
if (!$request = self::getClient()->getRequest()) {
|
||||
static::fail('A client must have an HTTP Request to make assertions. Did you forget to make an HTTP request?');
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
<?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\Bundle\FrameworkBundle\Test;
|
||||
|
||||
use PHPUnit\Framework\Constraint\LogicalAnd;
|
||||
use PHPUnit\Framework\Constraint\LogicalNot;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use Symfony\Component\DomCrawler\Test\Constraint as DomCrawlerConstraint;
|
||||
|
||||
/**
|
||||
* Ideas borrowed from Laravel Dusk's assertions.
|
||||
*
|
||||
* @see https://laravel.com/docs/5.7/dusk#available-assertions
|
||||
*/
|
||||
trait DomCrawlerAssertionsTrait
|
||||
{
|
||||
public static function assertSelectorExists(string $selector, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getCrawler(), new DomCrawlerConstraint\CrawlerSelectorExists($selector), $message);
|
||||
}
|
||||
|
||||
public static function assertSelectorNotExists(string $selector, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getCrawler(), new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorExists($selector)), $message);
|
||||
}
|
||||
|
||||
public static function assertSelectorTextContains(string $selector, string $text, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints(
|
||||
new DomCrawlerConstraint\CrawlerSelectorExists($selector),
|
||||
new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text)
|
||||
), $message);
|
||||
}
|
||||
|
||||
public static function assertSelectorTextSame(string $selector, string $text, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints(
|
||||
new DomCrawlerConstraint\CrawlerSelectorExists($selector),
|
||||
new DomCrawlerConstraint\CrawlerSelectorTextSame($selector, $text)
|
||||
), $message);
|
||||
}
|
||||
|
||||
public static function assertSelectorTextNotContains(string $selector, string $text, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints(
|
||||
new DomCrawlerConstraint\CrawlerSelectorExists($selector),
|
||||
new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text))
|
||||
), $message);
|
||||
}
|
||||
|
||||
public static function assertPageTitleSame(string $expectedTitle, string $message = ''): void
|
||||
{
|
||||
self::assertSelectorTextSame('title', $expectedTitle, $message);
|
||||
}
|
||||
|
||||
public static function assertPageTitleContains(string $expectedTitle, string $message = ''): void
|
||||
{
|
||||
self::assertSelectorTextContains('title', $expectedTitle, $message);
|
||||
}
|
||||
|
||||
public static function assertInputValueSame(string $fieldName, string $expectedValue, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints(
|
||||
new DomCrawlerConstraint\CrawlerSelectorExists("input[name=\"$fieldName\"]"),
|
||||
new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue)
|
||||
), $message);
|
||||
}
|
||||
|
||||
public static function assertInputValueNotSame(string $fieldName, string $expectedValue, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints(
|
||||
new DomCrawlerConstraint\CrawlerSelectorExists("input[name=\"$fieldName\"]"),
|
||||
new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue))
|
||||
), $message);
|
||||
}
|
||||
|
||||
private static function getCrawler(): Crawler
|
||||
{
|
||||
if (!$crawler = self::getClient()->getCrawler()) {
|
||||
static::fail('A client must have a crawler to make assertions. Did you forget to make an HTTP request?');
|
||||
}
|
||||
|
||||
return $crawler;
|
||||
}
|
||||
}
|
|
@ -11,16 +11,6 @@
|
|||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Test;
|
||||
|
||||
use PHPUnit\Framework\Constraint\LogicalAnd;
|
||||
use PHPUnit\Framework\Constraint\LogicalNot;
|
||||
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
|
||||
use Symfony\Component\BrowserKit\Test\Constraint as BrowserKitConstraint;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use Symfony\Component\DomCrawler\Test\Constraint as DomCrawlerConstraint;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Test\Constraint as ResponseConstraint;
|
||||
|
||||
/**
|
||||
* Ideas borrowed from Laravel Dusk's assertions.
|
||||
*
|
||||
|
@ -28,203 +18,6 @@ use Symfony\Component\HttpFoundation\Test\Constraint as ResponseConstraint;
|
|||
*/
|
||||
trait WebTestAssertionsTrait
|
||||
{
|
||||
public static function assertResponseIsSuccessful(string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), new ResponseConstraint\ResponseIsSuccessful(), $message);
|
||||
}
|
||||
|
||||
public static function assertResponseStatusCodeSame(int $expectedCode, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), new ResponseConstraint\ResponseStatusCodeSame($expectedCode), $message);
|
||||
}
|
||||
|
||||
public static function assertResponseRedirects(string $expectedLocation = null, int $expectedCode = null, string $message = ''): void
|
||||
{
|
||||
$constraint = new ResponseConstraint\ResponseIsRedirected();
|
||||
if ($expectedLocation) {
|
||||
$constraint = LogicalAnd::fromConstraints($constraint, new ResponseConstraint\ResponseHeaderSame('Location', $expectedLocation));
|
||||
}
|
||||
if ($expectedCode) {
|
||||
$constraint = LogicalAnd::fromConstraints($constraint, new ResponseConstraint\ResponseStatusCodeSame($expectedCode));
|
||||
}
|
||||
|
||||
self::assertThat(self::getResponse(), $constraint, $message);
|
||||
}
|
||||
|
||||
public static function assertResponseHasHeader(string $headerName, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), new ResponseConstraint\ResponseHasHeader($headerName), $message);
|
||||
}
|
||||
|
||||
public static function assertResponseNotHasHeader(string $headerName, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHasHeader($headerName)), $message);
|
||||
}
|
||||
|
||||
public static function assertResponseHeaderSame(string $headerName, string $expectedValue, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue), $message);
|
||||
}
|
||||
|
||||
public static function assertResponseHeaderNotSame(string $headerName, string $expectedValue, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue)), $message);
|
||||
}
|
||||
|
||||
public static function assertResponseHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), new ResponseConstraint\ResponseHasCookie($name, $path, $domain), $message);
|
||||
}
|
||||
|
||||
public static function assertResponseNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHasCookie($name, $path, $domain)), $message);
|
||||
}
|
||||
|
||||
public static function assertResponseCookieValueSame(string $name, string $expectedValue, string $path = '/', string $domain = null, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getResponse(), LogicalAnd::fromConstraints(
|
||||
new ResponseConstraint\ResponseHasCookie($name, $path, $domain),
|
||||
new ResponseConstraint\ResponseCookieValueSame($name, $expectedValue, $path, $domain)
|
||||
), $message);
|
||||
}
|
||||
|
||||
public static function assertBrowserHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getClient(), new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain), $message);
|
||||
}
|
||||
|
||||
public static function assertBrowserNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getClient(), new LogicalNot(new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain)), $message);
|
||||
}
|
||||
|
||||
public static function assertBrowserCookieValueSame(string $name, string $expectedValue, bool $raw = false, string $path = '/', string $domain = null, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getClient(), LogicalAnd::fromConstraints(
|
||||
new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain),
|
||||
new BrowserKitConstraint\BrowserCookieValueSame($name, $expectedValue, $raw, $path, $domain)
|
||||
), $message);
|
||||
}
|
||||
|
||||
public static function assertSelectorExists(string $selector, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getCrawler(), new DomCrawlerConstraint\CrawlerSelectorExists($selector), $message);
|
||||
}
|
||||
|
||||
public static function assertSelectorNotExists(string $selector, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getCrawler(), new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorExists($selector)), $message);
|
||||
}
|
||||
|
||||
public static function assertSelectorTextContains(string $selector, string $text, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints(
|
||||
new DomCrawlerConstraint\CrawlerSelectorExists($selector),
|
||||
new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text)
|
||||
), $message);
|
||||
}
|
||||
|
||||
public static function assertSelectorTextSame(string $selector, string $text, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints(
|
||||
new DomCrawlerConstraint\CrawlerSelectorExists($selector),
|
||||
new DomCrawlerConstraint\CrawlerSelectorTextSame($selector, $text)
|
||||
), $message);
|
||||
}
|
||||
|
||||
public static function assertSelectorTextNotContains(string $selector, string $text, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints(
|
||||
new DomCrawlerConstraint\CrawlerSelectorExists($selector),
|
||||
new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text))
|
||||
), $message);
|
||||
}
|
||||
|
||||
public static function assertPageTitleSame(string $expectedTitle, string $message = ''): void
|
||||
{
|
||||
self::assertSelectorTextSame('title', $expectedTitle, $message);
|
||||
}
|
||||
|
||||
public static function assertPageTitleContains(string $expectedTitle, string $message = ''): void
|
||||
{
|
||||
self::assertSelectorTextContains('title', $expectedTitle, $message);
|
||||
}
|
||||
|
||||
public static function assertInputValueSame(string $fieldName, string $expectedValue, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints(
|
||||
new DomCrawlerConstraint\CrawlerSelectorExists("input[name=\"$fieldName\"]"),
|
||||
new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue)
|
||||
), $message);
|
||||
}
|
||||
|
||||
public static function assertInputValueNotSame(string $fieldName, string $expectedValue, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints(
|
||||
new DomCrawlerConstraint\CrawlerSelectorExists("input[name=\"$fieldName\"]"),
|
||||
new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue))
|
||||
), $message);
|
||||
}
|
||||
|
||||
public static function assertRequestAttributeValueSame(string $name, string $expectedValue, string $message = ''): void
|
||||
{
|
||||
self::assertThat(self::getRequest(), new ResponseConstraint\RequestAttributeValueSame($name, $expectedValue), $message);
|
||||
}
|
||||
|
||||
public static function assertRouteSame($expectedRoute, array $parameters = [], string $message = ''): void
|
||||
{
|
||||
$constraint = new ResponseConstraint\RequestAttributeValueSame('_route', $expectedRoute);
|
||||
$constraints = [];
|
||||
foreach ($parameters as $key => $value) {
|
||||
$constraints[] = new ResponseConstraint\RequestAttributeValueSame($key, $value);
|
||||
}
|
||||
if ($constraints) {
|
||||
$constraint = LogicalAnd::fromConstraints($constraint, ...$constraints);
|
||||
}
|
||||
|
||||
self::assertThat(self::getRequest(), $constraint, $message);
|
||||
}
|
||||
|
||||
private static function getClient(KernelBrowser $newClient = null): ?KernelBrowser
|
||||
{
|
||||
static $client;
|
||||
|
||||
if (0 < \func_num_args()) {
|
||||
return $client = $newClient;
|
||||
}
|
||||
|
||||
if (!$client instanceof KernelBrowser) {
|
||||
static::fail(sprintf('A client must be set to make assertions on it. Did you forget to call "%s::createClient()"?', __CLASS__));
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
private static function getCrawler(): Crawler
|
||||
{
|
||||
if (!$crawler = self::getClient()->getCrawler()) {
|
||||
static::fail('A client must have a crawler to make assertions. Did you forget to make an HTTP request?');
|
||||
}
|
||||
|
||||
return $crawler;
|
||||
}
|
||||
|
||||
private static function getResponse(): Response
|
||||
{
|
||||
if (!$response = self::getClient()->getResponse()) {
|
||||
static::fail('A client must have an HTTP Response to make assertions. Did you forget to make an HTTP request?');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private static function getRequest(): Request
|
||||
{
|
||||
if (!$request = self::getClient()->getRequest()) {
|
||||
static::fail('A client must have an HTTP Request to make assertions. Did you forget to make an HTTP request?');
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
use BrowserKitAssertionsTrait;
|
||||
use DomCrawlerAssertionsTrait;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ use Symfony\Bundle\FullStack;
|
|||
use Symfony\Component\Cache\Adapter\AdapterInterface;
|
||||
use Symfony\Component\Cache\Adapter\ApcuAdapter;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
use Symfony\Component\Cache\Adapter\ChainAdapter;
|
||||
use Symfony\Component\Cache\Adapter\DoctrineAdapter;
|
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||
use Symfony\Component\Cache\Adapter\ProxyAdapter;
|
||||
|
@ -1459,10 +1458,6 @@ abstract class FrameworkExtensionTest extends TestCase
|
|||
$this->assertSame(DoctrineAdapter::class, $parentDefinition->getClass());
|
||||
break;
|
||||
case 'cache.app':
|
||||
if (ChainAdapter::class === $parentDefinition->getClass()) {
|
||||
break;
|
||||
}
|
||||
// no break
|
||||
case 'cache.adapter.filesystem':
|
||||
$this->assertSame(FilesystemAdapter::class, $parentDefinition->getClass());
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Mailer\Mailer;
|
||||
use Symfony\Component\Mailer\SentMessage;
|
||||
use Symfony\Component\Mailer\Transport\AbstractTransport;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
class MailerTest extends WebTestCase
|
||||
{
|
||||
public function testEnvelopeListener()
|
||||
{
|
||||
self::bootKernel(['test_case' => 'Mailer']);
|
||||
|
||||
$onDoSend = function (SentMessage $message) {
|
||||
$envelope = $message->getEnvelope();
|
||||
|
||||
$this->assertEquals(
|
||||
[new Address('redirected@example.org')],
|
||||
$envelope->getRecipients()
|
||||
);
|
||||
|
||||
$this->assertEquals('sender@example.org', $envelope->getSender()->getAddress());
|
||||
};
|
||||
|
||||
$eventDispatcher = self::$container->get(EventDispatcherInterface::class);
|
||||
$logger = self::$container->get('logger');
|
||||
|
||||
$testTransport = new class($eventDispatcher, $logger, $onDoSend) extends AbstractTransport {
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
private $onDoSend;
|
||||
|
||||
public function __construct(EventDispatcherInterface $eventDispatcher, LoggerInterface $logger, callable $onDoSend)
|
||||
{
|
||||
parent::__construct($eventDispatcher, $logger);
|
||||
$this->onDoSend = $onDoSend;
|
||||
}
|
||||
|
||||
protected function doSend(SentMessage $message): void
|
||||
{
|
||||
$onDoSend = $this->onDoSend;
|
||||
$onDoSend($message);
|
||||
}
|
||||
};
|
||||
|
||||
$mailer = new Mailer($testTransport, null);
|
||||
|
||||
$message = (new Email())
|
||||
->subject('Test subject')
|
||||
->text('Hello world')
|
||||
->from('from@example.org')
|
||||
->to('to@example.org');
|
||||
|
||||
$mailer->send($message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?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.
|
||||
*/
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
||||
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle;
|
||||
|
||||
return [
|
||||
new FrameworkBundle(),
|
||||
new TestBundle(),
|
||||
];
|
|
@ -0,0 +1,9 @@
|
|||
imports:
|
||||
- { resource: ../config/default.yml }
|
||||
|
||||
framework:
|
||||
mailer:
|
||||
envelope:
|
||||
sender: sender@example.org
|
||||
recipients:
|
||||
- redirected@example.org
|
|
@ -34,7 +34,6 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass
|
|||
private $onlyConstructorArguments;
|
||||
private $hasProxyDumper;
|
||||
private $lazy;
|
||||
private $expressionLanguage;
|
||||
private $byConstructor;
|
||||
private $definitions;
|
||||
private $aliases;
|
||||
|
|
|
@ -200,6 +200,10 @@ class MergeExtensionConfigurationContainerBuilder extends ContainerBuilder
|
|||
$bag = $this->getParameterBag();
|
||||
$value = $bag->resolveValue($value);
|
||||
|
||||
if (!$bag instanceof EnvPlaceholderParameterBag) {
|
||||
return parent::resolveEnvPlaceholders($value, $format, $usedEnvs);
|
||||
}
|
||||
|
||||
foreach ($bag->getEnvPlaceholders() as $env => $placeholders) {
|
||||
if (false === strpos($env, ':')) {
|
||||
continue;
|
||||
|
|
|
@ -81,7 +81,7 @@ class ServiceReferenceGraphNode
|
|||
/**
|
||||
* Returns the in edges.
|
||||
*
|
||||
* @return array The in ServiceReferenceGraphEdge array
|
||||
* @return ServiceReferenceGraphEdge[]
|
||||
*/
|
||||
public function getInEdges()
|
||||
{
|
||||
|
@ -91,7 +91,7 @@ class ServiceReferenceGraphNode
|
|||
/**
|
||||
* Returns the out edges.
|
||||
*
|
||||
* @return array The out ServiceReferenceGraphEdge array
|
||||
* @return ServiceReferenceGraphEdge[]
|
||||
*/
|
||||
public function getOutEdges()
|
||||
{
|
||||
|
|
|
@ -47,7 +47,7 @@ final class CrawlerSelectorAttributeValueSame extends Constraint
|
|||
return false;
|
||||
}
|
||||
|
||||
return $this->expectedText === trim($crawler->getNode(0)->getAttribute($this->attribute));
|
||||
return $this->expectedText === trim($crawler->attr($this->attribute));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,6 +6,11 @@ CHANGELOG
|
|||
|
||||
* `Filesystem::dumpFile()` and `appendToFile()` don't accept arrays anymore
|
||||
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
* support for passing a `null` value to `Filesystem::isAbsolutePath()` is deprecated and will be removed in 5.0
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
|
|
|
@ -600,6 +600,10 @@ class Filesystem
|
|||
*/
|
||||
public function isAbsolutePath($file)
|
||||
{
|
||||
if (null === $file) {
|
||||
@trigger_error(sprintf('Calling "%s()" with a null in the $file argument is deprecated since Symfony 4.4.', __METHOD__), E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
return strspn($file, '/\\', 0, 1)
|
||||
|| (\strlen($file) > 3 && ctype_alpha($file[0])
|
||||
&& ':' === $file[1]
|
||||
|
|
|
@ -1397,10 +1397,18 @@ class FilesystemTest extends FilesystemTestCase
|
|||
['var/lib', false],
|
||||
['../var/lib', false],
|
||||
['', false],
|
||||
[null, false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
* @expectedDeprecation Calling "Symfony\Component\Filesystem\Filesystem::isAbsolutePath()" with a null in the $file argument is deprecated since Symfony 4.4.
|
||||
*/
|
||||
public function testIsAbsolutePathWithNull()
|
||||
{
|
||||
$this->assertFalse($this->filesystem->isAbsolutePath(null));
|
||||
}
|
||||
|
||||
public function testTempnam()
|
||||
{
|
||||
$dirname = $this->workspace;
|
||||
|
|
|
@ -57,13 +57,15 @@ class FormTypeValidatorExtensionTest extends BaseValidatorExtensionTest
|
|||
|
||||
public function testGroupSequenceWithConstraintsOption()
|
||||
{
|
||||
$allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
|
||||
|
||||
$form = Forms::createFormFactoryBuilder()
|
||||
->addExtension(new ValidatorExtension(Validation::createValidator()))
|
||||
->getFormFactory()
|
||||
->create(FormTypeTest::TESTED_TYPE, null, (['validation_groups' => new GroupSequence(['First', 'Second'])]))
|
||||
->add('field', TextTypeTest::TESTED_TYPE, [
|
||||
'constraints' => [
|
||||
new Length(['min' => 10, 'groups' => ['First']]),
|
||||
new Length(['min' => 10, 'groups' => ['First']] + $allowEmptyString),
|
||||
new Email(['groups' => ['Second']]),
|
||||
],
|
||||
])
|
||||
|
|
|
@ -61,11 +61,13 @@ class ValidatorTypeGuesserTest extends TestCase
|
|||
|
||||
public function guessRequiredProvider()
|
||||
{
|
||||
$allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
|
||||
|
||||
return [
|
||||
[new NotNull(), new ValueGuess(true, Guess::HIGH_CONFIDENCE)],
|
||||
[new NotBlank(), new ValueGuess(true, Guess::HIGH_CONFIDENCE)],
|
||||
[new IsTrue(), new ValueGuess(true, Guess::HIGH_CONFIDENCE)],
|
||||
[new Length(10), new ValueGuess(false, Guess::LOW_CONFIDENCE)],
|
||||
[new Length(['min' => 10, 'max' => 10] + $allowEmptyString), new ValueGuess(false, Guess::LOW_CONFIDENCE)],
|
||||
[new Range(['min' => 1, 'max' => 20]), new ValueGuess(false, Guess::LOW_CONFIDENCE)],
|
||||
];
|
||||
}
|
||||
|
@ -101,7 +103,9 @@ class ValidatorTypeGuesserTest extends TestCase
|
|||
|
||||
public function testGuessMaxLengthForConstraintWithMinValue()
|
||||
{
|
||||
$constraint = new Length(['min' => '2']);
|
||||
$allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
|
||||
|
||||
$constraint = new Length(['min' => '2'] + $allowEmptyString);
|
||||
|
||||
$result = $this->guesser->guessMaxLengthForConstraint($constraint);
|
||||
$this->assertNull($result);
|
||||
|
|
|
@ -6,6 +6,7 @@ CHANGELOG
|
|||
|
||||
* made `Psr18Client` implement relevant PSR-17 factories
|
||||
* added `HttplugClient`
|
||||
* added support for NTLM authentication
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
|
|
@ -37,7 +37,10 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
|||
use HttpClientTrait;
|
||||
use LoggerAwareTrait;
|
||||
|
||||
private $defaultOptions = self::OPTIONS_DEFAULTS;
|
||||
private $defaultOptions = self::OPTIONS_DEFAULTS + [
|
||||
'auth_ntlm' => null, // array|string - an array containing the username as first value, and optionally the
|
||||
// password as the second one; or string like username:password - enabling NTLM auth
|
||||
];
|
||||
|
||||
/**
|
||||
* An internal object to share state between the client and its responses.
|
||||
|
@ -150,6 +153,25 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface
|
|||
CURLOPT_CERTINFO => $options['capture_peer_cert_chain'],
|
||||
];
|
||||
|
||||
if (isset($options['auth_ntlm'])) {
|
||||
$curlopts[CURLOPT_HTTPAUTH] = CURLAUTH_NTLM;
|
||||
|
||||
if (\is_array($options['auth_ntlm'])) {
|
||||
$count = \count($options['auth_ntlm']);
|
||||
if ($count <= 0 || $count > 2) {
|
||||
throw new InvalidArgumentException(sprintf('Option "auth_ntlm" must contain 1 or 2 elements, %s given.', $count));
|
||||
}
|
||||
|
||||
$options['auth_ntlm'] = implode(':', $options['auth_ntlm']);
|
||||
}
|
||||
|
||||
if (!\is_string($options['auth_ntlm'])) {
|
||||
throw new InvalidArgumentException(sprintf('Option "auth_ntlm" must be string or an array, %s given.', \gettype($options['auth_ntlm'])));
|
||||
}
|
||||
|
||||
$curlopts[CURLOPT_USERPWD] = $options['auth_ntlm'];
|
||||
}
|
||||
|
||||
if (!ZEND_THREAD_SAFE) {
|
||||
$curlopts[CURLOPT_DNS_USE_GLOBAL_CACHE] = false;
|
||||
}
|
||||
|
|
|
@ -179,6 +179,10 @@ trait HttpClientTrait
|
|||
}
|
||||
}
|
||||
|
||||
if ('auth_ntlm' === $name) {
|
||||
throw new InvalidArgumentException(sprintf('Option "%s" is not supported by %s, try using CurlHttpClient instead.', __CLASS__));
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(sprintf('Unsupported option "%s" passed to %s, did you mean "%s"?', $name, __CLASS__, implode('", "', $alternatives ?: array_keys($defaultOptions))));
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ CHANGELOG
|
|||
-----
|
||||
|
||||
* passing arguments to `Request::isMethodSafe()` is deprecated.
|
||||
* `ApacheRequest` is deprecated, use the `Request` class instead.
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
|
|
@ -192,6 +192,10 @@ class Request
|
|||
|
||||
protected static $requestFactory;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $preferredFormat;
|
||||
private $isHostValid = true;
|
||||
private $isForwardedValid = true;
|
||||
|
||||
|
@ -1342,6 +1346,8 @@ class Request
|
|||
* * _format request attribute
|
||||
* * $default
|
||||
*
|
||||
* @see getPreferredFormat
|
||||
*
|
||||
* @param string|null $default The default format
|
||||
*
|
||||
* @return string|null The request format
|
||||
|
@ -1554,6 +1560,30 @@ class Request
|
|||
return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the preferred format for the response by inspecting, in the following order:
|
||||
* * the request format set using setRequestFormat
|
||||
* * the values of the Accept HTTP header
|
||||
* * the content type of the body of the request.
|
||||
*/
|
||||
public function getPreferredFormat(?string $default = 'html'): ?string
|
||||
{
|
||||
if (null !== $this->preferredFormat) {
|
||||
return $this->preferredFormat;
|
||||
}
|
||||
|
||||
$preferredFormat = null;
|
||||
foreach ($this->getAcceptableContentTypes() as $contentType) {
|
||||
if ($preferredFormat = $this->getFormat($contentType)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->preferredFormat = $this->getRequestFormat($preferredFormat ?: $this->getContentType());
|
||||
|
||||
return $this->preferredFormat ?: $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the preferred language.
|
||||
*
|
||||
|
|
|
@ -265,7 +265,7 @@ class Response
|
|||
} else {
|
||||
// Content-type based on the Request
|
||||
if (!$headers->has('Content-Type')) {
|
||||
$format = $request->getRequestFormat();
|
||||
$format = $request->getPreferredFormat();
|
||||
if (null !== $format && $mimeType = $request->getMimeType($format)) {
|
||||
$headers->set('Content-Type', $mimeType);
|
||||
}
|
||||
|
|
|
@ -399,6 +399,32 @@ class RequestTest extends TestCase
|
|||
$this->assertEquals('xml', $dup->getRequestFormat());
|
||||
}
|
||||
|
||||
public function testGetPreferredFormat()
|
||||
{
|
||||
$request = new Request();
|
||||
$this->assertNull($request->getPreferredFormat(null));
|
||||
$this->assertSame('html', $request->getPreferredFormat());
|
||||
$this->assertSame('json', $request->getPreferredFormat('json'));
|
||||
|
||||
$request->setRequestFormat('atom');
|
||||
$request->headers->set('Accept', 'application/ld+json');
|
||||
$request->headers->set('Content-Type', 'application/merge-patch+json');
|
||||
$this->assertSame('atom', $request->getPreferredFormat());
|
||||
|
||||
$request = new Request();
|
||||
$request->headers->set('Accept', 'application/xml');
|
||||
$request->headers->set('Content-Type', 'application/json');
|
||||
$this->assertSame('xml', $request->getPreferredFormat());
|
||||
|
||||
$request = new Request();
|
||||
$request->headers->set('Accept', 'application/xml');
|
||||
$this->assertSame('xml', $request->getPreferredFormat());
|
||||
|
||||
$request = new Request();
|
||||
$request->headers->set('Accept', 'application/json;q=0.8,application/xml;q=0.9');
|
||||
$this->assertSame('xml', $request->getPreferredFormat());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getFormatToMimeTypeMapProviderWithAdditionalNullFormat
|
||||
*/
|
||||
|
|
|
@ -504,6 +504,7 @@ class ResponseTest extends ResponseTestCase
|
|||
$response = new Response('foo');
|
||||
$request = Request::create('/');
|
||||
$request->setRequestFormat('json');
|
||||
$request->headers->remove('accept');
|
||||
|
||||
$response->prepare($request);
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ class ContainerControllerResolver extends ControllerResolver
|
|||
$this->throwExceptionIfControllerWasRemoved($class, $e);
|
||||
|
||||
if ($e instanceof \ArgumentCountError) {
|
||||
throw new \InvalidArgumentException(sprintf('Controller "%s" has required constructor arguments and does not exist in the container. Did you forget to define such a service?', $class), 0, $e);
|
||||
throw new \InvalidArgumentException(sprintf('Controller "%s" has required constructor arguments and does not exist in the container. Did you forget to define the controller as a service?', $class), 0, $e);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('Controller "%s" does neither exist as service nor as class', $class), 0, $e);
|
||||
|
|
|
@ -170,7 +170,7 @@ class DebugHandlersListener implements EventSubscriberInterface
|
|||
$e = $request->attributes->get('exception');
|
||||
|
||||
try {
|
||||
return new Response($this->errorFormatter->render($e, $request->getRequestFormat()), $e->getStatusCode(), $e->getHeaders());
|
||||
return new Response($this->errorFormatter->render($e, $request->getPreferredFormat()), $e->getStatusCode(), $e->getHeaders());
|
||||
} catch (ErrorRendererNotFoundException $_) {
|
||||
return new Response($this->errorFormatter->render($e), $e->getStatusCode(), $e->getHeaders());
|
||||
}
|
||||
|
|
|
@ -184,16 +184,16 @@ class ContainerControllerResolverTest extends ControllerResolverTest
|
|||
$tests[] = [
|
||||
[ControllerTestService::class, 'action'],
|
||||
\InvalidArgumentException::class,
|
||||
'Controller "Symfony\Component\HttpKernel\Tests\Controller\ControllerTestService" has required constructor arguments and does not exist in the container. Did you forget to define such a service?',
|
||||
'Controller "Symfony\Component\HttpKernel\Tests\Controller\ControllerTestService" has required constructor arguments and does not exist in the container. Did you forget to define the controller as a service?',
|
||||
];
|
||||
$tests[] = [
|
||||
ControllerTestService::class.'::action',
|
||||
\InvalidArgumentException::class, 'Controller "Symfony\Component\HttpKernel\Tests\Controller\ControllerTestService" has required constructor arguments and does not exist in the container. Did you forget to define such a service?',
|
||||
\InvalidArgumentException::class, 'Controller "Symfony\Component\HttpKernel\Tests\Controller\ControllerTestService" has required constructor arguments and does not exist in the container. Did you forget to define the controller as a service?',
|
||||
];
|
||||
$tests[] = [
|
||||
InvokableControllerService::class,
|
||||
\InvalidArgumentException::class,
|
||||
'Controller "Symfony\Component\HttpKernel\Tests\Controller\InvokableControllerService" has required constructor arguments and does not exist in the container. Did you forget to define such a service?',
|
||||
'Controller "Symfony\Component\HttpKernel\Tests\Controller\InvokableControllerService" has required constructor arguments and does not exist in the container. Did you forget to define the controller as a service?',
|
||||
];
|
||||
|
||||
return $tests;
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
|
||||
namespace Symfony\Component\Ldap\Adapter;
|
||||
|
||||
use Symfony\Component\Ldap\Exception\AlreadyExistsException;
|
||||
use Symfony\Component\Ldap\Exception\ConnectionTimeoutException;
|
||||
use Symfony\Component\Ldap\Exception\InvalidCredentialsException;
|
||||
|
||||
/**
|
||||
* @author Charles Sarrazin <charles@sarraz.in>
|
||||
*/
|
||||
|
@ -25,6 +29,10 @@ interface ConnectionInterface
|
|||
|
||||
/**
|
||||
* Binds the connection against a user's DN and password.
|
||||
*
|
||||
* @throws AlreadyExistsException When the connection can't be created because of an LDAP_ALREADY_EXISTS error
|
||||
* @throws ConnectionTimeoutException When the connection can't be created because of an LDAP_TIMEOUT error
|
||||
* @throws InvalidCredentialsException When the connection can't be created because of an LDAP_INVALID_CREDENTIALS error
|
||||
*/
|
||||
public function bind(string $dn = null, string $password = null);
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ class ZookeeperStore implements StoreInterface
|
|||
*/
|
||||
public function putOffExpiration(Key $key, $ttl)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
// do nothing, zookeeper locks forever.
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -49,7 +49,6 @@ interface StoreInterface
|
|||
* @param float $ttl amount of seconds to keep the lock in the store
|
||||
*
|
||||
* @throws LockConflictedException
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
public function putOffExpiration(Key $key, $ttl);
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ CHANGELOG
|
|||
|
||||
* Deprecated passing a `ContainerInterface` instance as first argument of the `ConsumeMessagesCommand` constructor,
|
||||
pass a `RoutableMessageBus` instance instead.
|
||||
* Added support for auto trimming of Redis streams.
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
|
|
@ -33,17 +33,26 @@ class MessageBus implements MessageBusInterface
|
|||
} elseif (\is_array($middlewareHandlers)) {
|
||||
$this->middlewareAggregate = new \ArrayObject($middlewareHandlers);
|
||||
} else {
|
||||
$this->middlewareAggregate = new class() {
|
||||
public $aggregate;
|
||||
public $iterator;
|
||||
// $this->middlewareAggregate should be an instance of IteratorAggregate.
|
||||
// When $middlewareHandlers is an Iterator, we wrap it to ensure it is lazy-loaded and can be rewound.
|
||||
$this->middlewareAggregate = new class($middlewareHandlers) implements \IteratorAggregate {
|
||||
private $middlewareHandlers;
|
||||
private $cachedIterator;
|
||||
|
||||
public function __construct($middlewareHandlers)
|
||||
{
|
||||
$this->middlewareHandlers = $middlewareHandlers;
|
||||
}
|
||||
|
||||
public function getIterator()
|
||||
{
|
||||
return $this->aggregate = new \ArrayObject(iterator_to_array($this->iterator, false));
|
||||
if (null === $this->cachedIterator) {
|
||||
$this->cachedIterator = new \ArrayObject(iterator_to_array($this->middlewareHandlers, false));
|
||||
}
|
||||
|
||||
return $this->cachedIterator;
|
||||
}
|
||||
};
|
||||
$this->middlewareAggregate->aggregate = &$this->middlewareAggregate;
|
||||
$this->middlewareAggregate->iterator = $middlewareHandlers;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ use Symfony\Component\Messenger\Envelope;
|
|||
use Symfony\Component\Messenger\MessageBus;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
|
||||
use Symfony\Component\Messenger\Middleware\StackInterface;
|
||||
use Symfony\Component\Messenger\Stamp\BusNameStamp;
|
||||
use Symfony\Component\Messenger\Stamp\DelayStamp;
|
||||
use Symfony\Component\Messenger\Stamp\ReceivedStamp;
|
||||
|
@ -148,4 +149,44 @@ class MessageBusTest extends TestCase
|
|||
$finalEnvelope = (new MessageBus())->dispatch(new Envelope(new \stdClass()), [new DelayStamp(5), new BusNameStamp('bar')]);
|
||||
$this->assertCount(2, $finalEnvelope->all());
|
||||
}
|
||||
|
||||
public function provideConstructorDataStucture()
|
||||
{
|
||||
yield 'iterator' => [new \ArrayObject([
|
||||
new SimpleMiddleware(),
|
||||
new SimpleMiddleware(),
|
||||
])];
|
||||
|
||||
yield 'array' => [[
|
||||
new SimpleMiddleware(),
|
||||
new SimpleMiddleware(),
|
||||
]];
|
||||
|
||||
yield 'generator' => [(function (): \Generator {
|
||||
yield new SimpleMiddleware();
|
||||
yield new SimpleMiddleware();
|
||||
})()];
|
||||
}
|
||||
|
||||
/** @dataProvider provideConstructorDataStucture */
|
||||
public function testConstructDataStructure($dataStructure)
|
||||
{
|
||||
$bus = new MessageBus($dataStructure);
|
||||
$envelope = new Envelope(new DummyMessage('Hello'));
|
||||
$newEnvelope = $bus->dispatch($envelope);
|
||||
$this->assertSame($envelope->getMessage(), $newEnvelope->getMessage());
|
||||
|
||||
// Test rewindable capacity
|
||||
$envelope = new Envelope(new DummyMessage('Hello'));
|
||||
$newEnvelope = $bus->dispatch($envelope);
|
||||
$this->assertSame($envelope->getMessage(), $newEnvelope->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function handle(Envelope $envelope, StackInterface $stack): Envelope
|
||||
{
|
||||
return $envelope;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
|
||||
namespace Symfony\Component\Messenger\Tests\Transport\Doctrine;
|
||||
|
||||
use Doctrine\Common\Persistence\ConnectionRegistry;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bridge\Doctrine\RegistryInterface;
|
||||
use Symfony\Component\Messenger\Transport\Doctrine\Connection;
|
||||
use Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransport;
|
||||
use Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransportFactory;
|
||||
|
@ -23,7 +23,7 @@ class DoctrineTransportFactoryTest extends TestCase
|
|||
public function testSupports()
|
||||
{
|
||||
$factory = new DoctrineTransportFactory(
|
||||
$this->getMockBuilder(RegistryInterface::class)->getMock()
|
||||
$this->getMockBuilder(ConnectionRegistry::class)->getMock()
|
||||
);
|
||||
|
||||
$this->assertTrue($factory->supports('doctrine://default', []));
|
||||
|
@ -35,7 +35,7 @@ class DoctrineTransportFactoryTest extends TestCase
|
|||
$connection = $this->getMockBuilder(\Doctrine\DBAL\Connection::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$registry = $this->getMockBuilder(RegistryInterface::class)->getMock();
|
||||
$registry = $this->getMockBuilder(ConnectionRegistry::class)->getMock();
|
||||
$registry->expects($this->once())
|
||||
->method('getConnection')
|
||||
->willReturn($connection);
|
||||
|
@ -55,7 +55,7 @@ class DoctrineTransportFactoryTest extends TestCase
|
|||
*/
|
||||
public function testCreateTransportMustThrowAnExceptionIfManagerIsNotFound()
|
||||
{
|
||||
$registry = $this->getMockBuilder(RegistryInterface::class)->getMock();
|
||||
$registry = $this->getMockBuilder(ConnectionRegistry::class)->getMock();
|
||||
$registry->expects($this->once())
|
||||
->method('getConnection')
|
||||
->willReturnCallback(function () {
|
||||
|
|
|
@ -42,13 +42,13 @@ class ConnectionTest extends TestCase
|
|||
public function testFromDsnWithOptions()
|
||||
{
|
||||
$this->assertEquals(
|
||||
new Connection(['stream' => 'queue', 'group' => 'group1', 'consumer' => 'consumer1', 'auto_setup' => false], [
|
||||
new Connection(['stream' => 'queue', 'group' => 'group1', 'consumer' => 'consumer1', 'auto_setup' => false, 'stream_max_entries' => 20000], [
|
||||
'host' => 'localhost',
|
||||
'port' => 6379,
|
||||
], [
|
||||
'serializer' => 2,
|
||||
]),
|
||||
Connection::fromDsn('redis://localhost/queue/group1/consumer1', ['serializer' => 2, 'auto_setup' => false])
|
||||
Connection::fromDsn('redis://localhost/queue/group1/consumer1', ['serializer' => 2, 'auto_setup' => false, 'stream_max_entries' => 20000])
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -79,6 +79,16 @@ class ConnectionTest extends TestCase
|
|||
$this->assertNotNull($connection->get());
|
||||
}
|
||||
|
||||
public function testAuth()
|
||||
{
|
||||
$redis = $this->getMockBuilder(\Redis::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$redis->expects($this->exactly(1))->method('auth')
|
||||
->with('password');
|
||||
|
||||
Connection::fromDsn('redis://password@localhost/queue', [], $redis);
|
||||
}
|
||||
|
||||
public function testFirstGetPendingMessagesThenNewMessages()
|
||||
{
|
||||
$redis = $this->getMockBuilder(\Redis::class)->disableOriginalConstructor()->getMock();
|
||||
|
@ -142,4 +152,16 @@ class ConnectionTest extends TestCase
|
|||
$connection->reject($message['id']);
|
||||
$redis->del('messenger-getnonblocking');
|
||||
}
|
||||
|
||||
public function testMaxEntries()
|
||||
{
|
||||
$redis = $this->getMockBuilder(\Redis::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$redis->expects($this->exactly(1))->method('xadd')
|
||||
->with('queue', '*', ['message' => '{"body":"1","headers":[]}'], 20000, true)
|
||||
->willReturn(1);
|
||||
|
||||
$connection = Connection::fromDsn('redis://localhost/queue?stream_max_entries=20000', [], $redis); // 1 = always
|
||||
$connection->add('1', []);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
namespace Symfony\Component\Messenger\Transport\Doctrine;
|
||||
|
||||
use Symfony\Bridge\Doctrine\RegistryInterface;
|
||||
use Doctrine\Common\Persistence\ConnectionRegistry;
|
||||
use Symfony\Component\Messenger\Exception\TransportException;
|
||||
use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
|
||||
use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
|
||||
|
@ -24,7 +24,7 @@ class DoctrineTransportFactory implements TransportFactoryInterface
|
|||
{
|
||||
private $registry;
|
||||
|
||||
public function __construct(RegistryInterface $registry)
|
||||
public function __construct(ConnectionRegistry $registry)
|
||||
{
|
||||
$this->registry = $registry;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ class Connection
|
|||
'group' => 'symfony',
|
||||
'consumer' => 'consumer',
|
||||
'auto_setup' => true,
|
||||
'stream_max_entries' => 0, // any value higher than 0 defines an approximate maximum number of stream entries
|
||||
];
|
||||
|
||||
private $connection;
|
||||
|
@ -38,6 +39,7 @@ class Connection
|
|||
private $group;
|
||||
private $consumer;
|
||||
private $autoSetup;
|
||||
private $maxEntries;
|
||||
private $couldHavePendingMessages = true;
|
||||
|
||||
public function __construct(array $configuration, array $connectionCredentials = [], array $redisOptions = [], \Redis $redis = null)
|
||||
|
@ -49,10 +51,16 @@ class Connection
|
|||
$this->connection = $redis ?: new \Redis();
|
||||
$this->connection->connect($connectionCredentials['host'] ?? '127.0.0.1', $connectionCredentials['port'] ?? 6379);
|
||||
$this->connection->setOption(\Redis::OPT_SERIALIZER, $redisOptions['serializer'] ?? \Redis::SERIALIZER_PHP);
|
||||
|
||||
if (isset($connectionCredentials['auth'])) {
|
||||
$this->connection->auth($connectionCredentials['auth']);
|
||||
}
|
||||
|
||||
$this->stream = $configuration['stream'] ?? self::DEFAULT_OPTIONS['stream'];
|
||||
$this->group = $configuration['group'] ?? self::DEFAULT_OPTIONS['group'];
|
||||
$this->consumer = $configuration['consumer'] ?? self::DEFAULT_OPTIONS['consumer'];
|
||||
$this->autoSetup = $configuration['auto_setup'] ?? self::DEFAULT_OPTIONS['auto_setup'];
|
||||
$this->maxEntries = $configuration['stream_max_entries'] ?? self::DEFAULT_OPTIONS['stream_max_entries'];
|
||||
}
|
||||
|
||||
public static function fromDsn(string $dsn, array $redisOptions = [], \Redis $redis = null): self
|
||||
|
@ -70,6 +78,7 @@ class Connection
|
|||
$connectionCredentials = [
|
||||
'host' => $parsedUrl['host'] ?? '127.0.0.1',
|
||||
'port' => $parsedUrl['port'] ?? 6379,
|
||||
'auth' => $parsedUrl['pass'] ?? $parsedUrl['user'] ?? null,
|
||||
];
|
||||
|
||||
if (isset($parsedUrl['query'])) {
|
||||
|
@ -82,7 +91,19 @@ class Connection
|
|||
unset($redisOptions['auto_setup']);
|
||||
}
|
||||
|
||||
return new self(['stream' => $stream, 'group' => $group, 'consumer' => $consumer, 'auto_setup' => $autoSetup], $connectionCredentials, $redisOptions, $redis);
|
||||
$maxEntries = null;
|
||||
if (\array_key_exists('stream_max_entries', $redisOptions)) {
|
||||
$maxEntries = filter_var($redisOptions['stream_max_entries'], FILTER_VALIDATE_INT);
|
||||
unset($redisOptions['stream_max_entries']);
|
||||
}
|
||||
|
||||
return new self([
|
||||
'stream' => $stream,
|
||||
'group' => $group,
|
||||
'consumer' => $consumer,
|
||||
'auto_setup' => $autoSetup,
|
||||
'stream_max_entries' => $maxEntries,
|
||||
], $connectionCredentials, $redisOptions, $redis);
|
||||
}
|
||||
|
||||
public function get(): ?array
|
||||
|
@ -169,9 +190,15 @@ class Connection
|
|||
|
||||
$e = null;
|
||||
try {
|
||||
$added = $this->connection->xadd($this->stream, '*', ['message' => json_encode(
|
||||
['body' => $body, 'headers' => $headers]
|
||||
)]);
|
||||
if ($this->maxEntries) {
|
||||
$added = $this->connection->xadd($this->stream, '*', ['message' => json_encode(
|
||||
['body' => $body, 'headers' => $headers]
|
||||
)], $this->maxEntries, true);
|
||||
} else {
|
||||
$added = $this->connection->xadd($this->stream, '*', ['message' => json_encode(
|
||||
['body' => $body, 'headers' => $headers]
|
||||
)]);
|
||||
}
|
||||
} catch (\RedisException $e) {
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,8 @@ final class SMimeEncrypter extends SMime
|
|||
private $cipher;
|
||||
|
||||
/**
|
||||
* @param string|string[] $certificate Either a lone X.509 certificate, or an array of X.509 certificates
|
||||
* @param string|string[] $certificate The path (or array of paths) of the file(s) containing the X.509 certificate(s)
|
||||
* @param int $cipher A set of algorithms used to encrypt the message. Must be one of these PHP constants: https://www.php.net/manual/en/openssl.ciphers.php
|
||||
*/
|
||||
public function __construct($certificate, int $cipher = OPENSSL_CIPHER_AES_256_CBC)
|
||||
{
|
||||
|
|
|
@ -30,13 +30,11 @@ final class SMimeSigner extends SMime
|
|||
private $privateKeyPassphrase;
|
||||
|
||||
/**
|
||||
* @see https://secure.php.net/manual/en/openssl.pkcs7.flags.php
|
||||
*
|
||||
* @param string $certificate
|
||||
* @param string $privateKey A file containing the private key (in PEM format)
|
||||
* @param string $certificate The path of the file containing the signing certificate (in PEM format)
|
||||
* @param string $privateKey The path of the file containing the private key (in PEM format)
|
||||
* @param string|null $privateKeyPassphrase A passphrase of the private key (if any)
|
||||
* @param string $extraCerts A file containing intermediate certificates (in PEM format) needed by the signing certificate
|
||||
* @param int $signOptions Bitwise operator options for openssl_pkcs7_sign()
|
||||
* @param string|null $extraCerts The path of the file containing intermediate certificates (in PEM format) needed by the signing certificate
|
||||
* @param int $signOptions Bitwise operator options for openssl_pkcs7_sign() (@see https://secure.php.net/manual/en/openssl.pkcs7.flags.php)
|
||||
*/
|
||||
public function __construct(string $certificate, string $privateKey, ?string $privateKeyPassphrase = null, ?string $extraCerts = null, int $signOptions = PKCS7_DETACHED)
|
||||
{
|
||||
|
|
|
@ -128,17 +128,11 @@ class Message extends RawMessage
|
|||
return bin2hex(random_bytes(16)).strstr($email, '@');
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
return [$this->headers, $this->body];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
[$this->headers, $this->body] = $data;
|
||||
|
|
|
@ -67,17 +67,11 @@ class RawMessage implements \Serializable
|
|||
$this->__unserialize(unserialize($serialized));
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
return [$this->message];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
[$this->message] = $data;
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
CHANGELOG
|
||||
=========
|
||||
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
* deprecated passing `null` as `$defaultLifetime` 2nd argument of `PropertyAccessor::createCache()` method,
|
||||
pass `0` instead
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Symfony\Component\PropertyInfo\Extractor;
|
|||
use phpDocumentor\Reflection\DocBlock;
|
||||
use phpDocumentor\Reflection\DocBlockFactory;
|
||||
use phpDocumentor\Reflection\DocBlockFactoryInterface;
|
||||
use phpDocumentor\Reflection\Types\Context;
|
||||
use phpDocumentor\Reflection\Types\ContextFactory;
|
||||
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
|
||||
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
||||
|
@ -38,6 +39,11 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property
|
|||
*/
|
||||
private $docBlocks = [];
|
||||
|
||||
/**
|
||||
* @var Context[]
|
||||
*/
|
||||
private $contexts = [];
|
||||
|
||||
private $docBlockFactory;
|
||||
private $contextFactory;
|
||||
private $phpDocTypeHelper;
|
||||
|
@ -191,7 +197,7 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property
|
|||
}
|
||||
|
||||
try {
|
||||
return $this->docBlockFactory->create($reflectionProperty, $this->contextFactory->createFromReflector($reflectionProperty->getDeclaringClass()));
|
||||
return $this->docBlockFactory->create($reflectionProperty, $this->createFromReflector($reflectionProperty->getDeclaringClass()));
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
|
@ -227,9 +233,25 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property
|
|||
}
|
||||
|
||||
try {
|
||||
return [$this->docBlockFactory->create($reflectionMethod, $this->contextFactory->createFromReflector($reflectionMethod)), $prefix];
|
||||
return [$this->docBlockFactory->create($reflectionMethod, $this->createFromReflector($reflectionMethod->getDeclaringClass())), $prefix];
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents a lot of redundant calls to ContextFactory::createForNamespace().
|
||||
*/
|
||||
private function createFromReflector(\ReflectionClass $reflector): Context
|
||||
{
|
||||
$cacheKey = $reflector->getNamespaceName().':'.$reflector->getFileName();
|
||||
|
||||
if (isset($this->contexts[$cacheKey])) {
|
||||
return $this->contexts[$cacheKey];
|
||||
}
|
||||
|
||||
$this->contexts[$cacheKey] = $this->contextFactory->createFromReflector($reflector);
|
||||
|
||||
return $this->contexts[$cacheKey];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,14 @@ CHANGELOG
|
|||
4.4.0
|
||||
-----
|
||||
|
||||
* using anything else than a `string` as the code of a `ConstraintViolation` is deprecated, a `string` type-hint will
|
||||
be added to the constructor of the `ConstraintViolation` class and to the `ConstraintViolationBuilder::setCode()`
|
||||
method in 5.0
|
||||
* deprecated passing an `ExpressionLanguage` instance as the second argument of `ExpressionValidator::__construct()`. Pass it as the first argument instead.
|
||||
* added the `compared_value_path` parameter in violations when using any
|
||||
comparison constraint with the `propertyPath` option.
|
||||
* added support for checking an array of types in `TypeValidator`
|
||||
* added a new `allowEmptyString` option to the `Length` constraint to allow rejecting empty strings when `min` is set, by setting it to `false`.
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
|
|
@ -49,7 +49,7 @@ class ConstraintViolation implements ConstraintViolationInterface
|
|||
* caused the violation
|
||||
* @param mixed $cause The cause of the violation
|
||||
*/
|
||||
public function __construct(string $message, ?string $messageTemplate, array $parameters, $root, ?string $propertyPath, $invalidValue, int $plural = null, $code = null, Constraint $constraint = null, $cause = null)
|
||||
public function __construct(string $message, ?string $messageTemplate, array $parameters, $root, ?string $propertyPath, $invalidValue, int $plural = null, string $code = null, Constraint $constraint = null, $cause = null)
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->messageTemplate = $messageTemplate;
|
||||
|
@ -79,13 +79,12 @@ class ConstraintViolation implements ConstraintViolationInterface
|
|||
}
|
||||
|
||||
$propertyPath = (string) $this->propertyPath;
|
||||
$code = (string) $this->code;
|
||||
|
||||
if ('' !== $propertyPath && '[' !== $propertyPath[0] && '' !== $class) {
|
||||
$class .= '.';
|
||||
}
|
||||
|
||||
if ('' !== $code) {
|
||||
if ('' !== $code = $this->code) {
|
||||
$code = ' (code '.$code.')';
|
||||
}
|
||||
|
||||
|
|
|
@ -65,49 +65,49 @@ class FileValidator extends ConstraintValidator
|
|||
$this->context->buildViolation($constraint->uploadIniSizeErrorMessage)
|
||||
->setParameter('{{ limit }}', $limitAsString)
|
||||
->setParameter('{{ suffix }}', $suffix)
|
||||
->setCode(UPLOAD_ERR_INI_SIZE)
|
||||
->setCode((string) UPLOAD_ERR_INI_SIZE)
|
||||
->addViolation();
|
||||
|
||||
return;
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
$this->context->buildViolation($constraint->uploadFormSizeErrorMessage)
|
||||
->setCode(UPLOAD_ERR_FORM_SIZE)
|
||||
->setCode((string) UPLOAD_ERR_FORM_SIZE)
|
||||
->addViolation();
|
||||
|
||||
return;
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
$this->context->buildViolation($constraint->uploadPartialErrorMessage)
|
||||
->setCode(UPLOAD_ERR_PARTIAL)
|
||||
->setCode((string) UPLOAD_ERR_PARTIAL)
|
||||
->addViolation();
|
||||
|
||||
return;
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
$this->context->buildViolation($constraint->uploadNoFileErrorMessage)
|
||||
->setCode(UPLOAD_ERR_NO_FILE)
|
||||
->setCode((string) UPLOAD_ERR_NO_FILE)
|
||||
->addViolation();
|
||||
|
||||
return;
|
||||
case UPLOAD_ERR_NO_TMP_DIR:
|
||||
$this->context->buildViolation($constraint->uploadNoTmpDirErrorMessage)
|
||||
->setCode(UPLOAD_ERR_NO_TMP_DIR)
|
||||
->setCode((string) UPLOAD_ERR_NO_TMP_DIR)
|
||||
->addViolation();
|
||||
|
||||
return;
|
||||
case UPLOAD_ERR_CANT_WRITE:
|
||||
$this->context->buildViolation($constraint->uploadCantWriteErrorMessage)
|
||||
->setCode(UPLOAD_ERR_CANT_WRITE)
|
||||
->setCode((string) UPLOAD_ERR_CANT_WRITE)
|
||||
->addViolation();
|
||||
|
||||
return;
|
||||
case UPLOAD_ERR_EXTENSION:
|
||||
$this->context->buildViolation($constraint->uploadExtensionErrorMessage)
|
||||
->setCode(UPLOAD_ERR_EXTENSION)
|
||||
->setCode((string) UPLOAD_ERR_EXTENSION)
|
||||
->addViolation();
|
||||
|
||||
return;
|
||||
default:
|
||||
$this->context->buildViolation($constraint->uploadErrorMessage)
|
||||
->setCode($value->getError())
|
||||
->setCode((string) $value->getError())
|
||||
->addViolation();
|
||||
|
||||
return;
|
||||
|
|
|
@ -41,6 +41,7 @@ class Length extends Constraint
|
|||
public $min;
|
||||
public $charset = 'UTF-8';
|
||||
public $normalizer;
|
||||
public $allowEmptyString;
|
||||
|
||||
public function __construct($options = null)
|
||||
{
|
||||
|
@ -56,6 +57,13 @@ class Length extends Constraint
|
|||
|
||||
parent::__construct($options);
|
||||
|
||||
if (null === $this->allowEmptyString) {
|
||||
$this->allowEmptyString = true;
|
||||
if (null !== $this->min) {
|
||||
@trigger_error(sprintf('Using the "%s" constraint with the "min" option without setting the "allowEmptyString" one is deprecated and defaults to true. In 5.0, it will become optional and default to false.', self::class), E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $this->min && null === $this->max) {
|
||||
throw new MissingOptionsException(sprintf('Either option "min" or "max" must be given for constraint %s', __CLASS__), ['min', 'max']);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ class LengthValidator extends ConstraintValidator
|
|||
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Length');
|
||||
}
|
||||
|
||||
if (null === $value || '' === $value) {
|
||||
if (null === $value || ('' === $value && $constraint->allowEmptyString)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ EOF;
|
|||
'some_value',
|
||||
null,
|
||||
null,
|
||||
0
|
||||
'0'
|
||||
);
|
||||
|
||||
$expected = <<<'EOF'
|
||||
|
@ -108,4 +108,24 @@ EOF;
|
|||
|
||||
$this->assertSame($expected, (string) $violation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
* @expectedDeprecation Not using a string as the error code in Symfony\Component\Validator\ConstraintViolation::__construct() is deprecated since Symfony 4.4. A type-hint will be added in 5.0.
|
||||
*/
|
||||
public function testNonStringCode()
|
||||
{
|
||||
$violation = new ConstraintViolation(
|
||||
'42 cannot be used here',
|
||||
'this is the message template',
|
||||
[],
|
||||
['some_value' => 42],
|
||||
'some_value',
|
||||
null,
|
||||
null,
|
||||
42
|
||||
);
|
||||
|
||||
self::assertSame(42, $violation->getCode());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -435,23 +435,23 @@ abstract class FileValidatorTest extends ConstraintValidatorTestCase
|
|||
public function uploadedFileErrorProvider()
|
||||
{
|
||||
$tests = [
|
||||
[UPLOAD_ERR_FORM_SIZE, 'uploadFormSizeErrorMessage'],
|
||||
[UPLOAD_ERR_PARTIAL, 'uploadPartialErrorMessage'],
|
||||
[UPLOAD_ERR_NO_FILE, 'uploadNoFileErrorMessage'],
|
||||
[UPLOAD_ERR_NO_TMP_DIR, 'uploadNoTmpDirErrorMessage'],
|
||||
[UPLOAD_ERR_CANT_WRITE, 'uploadCantWriteErrorMessage'],
|
||||
[UPLOAD_ERR_EXTENSION, 'uploadExtensionErrorMessage'],
|
||||
[(string) UPLOAD_ERR_FORM_SIZE, 'uploadFormSizeErrorMessage'],
|
||||
[(string) UPLOAD_ERR_PARTIAL, 'uploadPartialErrorMessage'],
|
||||
[(string) UPLOAD_ERR_NO_FILE, 'uploadNoFileErrorMessage'],
|
||||
[(string) UPLOAD_ERR_NO_TMP_DIR, 'uploadNoTmpDirErrorMessage'],
|
||||
[(string) UPLOAD_ERR_CANT_WRITE, 'uploadCantWriteErrorMessage'],
|
||||
[(string) UPLOAD_ERR_EXTENSION, 'uploadExtensionErrorMessage'],
|
||||
];
|
||||
|
||||
if (class_exists('Symfony\Component\HttpFoundation\File\UploadedFile')) {
|
||||
// when no maxSize is specified on constraint, it should use the ini value
|
||||
$tests[] = [UPLOAD_ERR_INI_SIZE, 'uploadIniSizeErrorMessage', [
|
||||
$tests[] = [(string) UPLOAD_ERR_INI_SIZE, 'uploadIniSizeErrorMessage', [
|
||||
'{{ limit }}' => UploadedFile::getMaxFilesize() / 1048576,
|
||||
'{{ suffix }}' => 'MiB',
|
||||
]];
|
||||
|
||||
// it should use the smaller limitation (maxSize option in this case)
|
||||
$tests[] = [UPLOAD_ERR_INI_SIZE, 'uploadIniSizeErrorMessage', [
|
||||
$tests[] = [(string) UPLOAD_ERR_INI_SIZE, 'uploadIniSizeErrorMessage', [
|
||||
'{{ limit }}' => 1,
|
||||
'{{ suffix }}' => 'bytes',
|
||||
], '1'];
|
||||
|
@ -464,14 +464,14 @@ abstract class FileValidatorTest extends ConstraintValidatorTestCase
|
|||
|
||||
// it correctly parses the maxSize option and not only uses simple string comparison
|
||||
// 1000M should be bigger than the ini value
|
||||
$tests[] = [UPLOAD_ERR_INI_SIZE, 'uploadIniSizeErrorMessage', [
|
||||
$tests[] = [(string) UPLOAD_ERR_INI_SIZE, 'uploadIniSizeErrorMessage', [
|
||||
'{{ limit }}' => $limit,
|
||||
'{{ suffix }}' => $suffix,
|
||||
], '1000M'];
|
||||
|
||||
// it correctly parses the maxSize option and not only uses simple string comparison
|
||||
// 1000M should be bigger than the ini value
|
||||
$tests[] = [UPLOAD_ERR_INI_SIZE, 'uploadIniSizeErrorMessage', [
|
||||
$tests[] = [(string) UPLOAD_ERR_INI_SIZE, 'uploadIniSizeErrorMessage', [
|
||||
'{{ limit }}' => '0.1',
|
||||
'{{ suffix }}' => 'MB',
|
||||
], '100K'];
|
||||
|
|
|
@ -21,7 +21,7 @@ class LengthTest extends TestCase
|
|||
{
|
||||
public function testNormalizerCanBeSet()
|
||||
{
|
||||
$length = new Length(['min' => 0, 'max' => 10, 'normalizer' => 'trim']);
|
||||
$length = new Length(['min' => 0, 'max' => 10, 'normalizer' => 'trim', 'allowEmptyString' => false]);
|
||||
|
||||
$this->assertEquals('trim', $length->normalizer);
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ class LengthTest extends TestCase
|
|||
*/
|
||||
public function testInvalidNormalizerThrowsException()
|
||||
{
|
||||
new Length(['min' => 0, 'max' => 10, 'normalizer' => 'Unknown Callable']);
|
||||
new Length(['min' => 0, 'max' => 10, 'normalizer' => 'Unknown Callable', 'allowEmptyString' => false]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,6 +41,6 @@ class LengthTest extends TestCase
|
|||
*/
|
||||
public function testInvalidNormalizerObjectThrowsException()
|
||||
{
|
||||
new Length(['min' => 0, 'max' => 10, 'normalizer' => new \stdClass()]);
|
||||
new Length(['min' => 0, 'max' => 10, 'normalizer' => new \stdClass(), 'allowEmptyString' => false]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,26 +22,47 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
|
|||
return new LengthValidator();
|
||||
}
|
||||
|
||||
public function testNullIsValid()
|
||||
public function testLegacyNullIsValid()
|
||||
{
|
||||
$this->validator->validate(null, new Length(6));
|
||||
$this->validator->validate(null, new Length(['value' => 6, 'allowEmptyString' => false]));
|
||||
|
||||
$this->assertNoViolation();
|
||||
}
|
||||
|
||||
public function testEmptyStringIsValid()
|
||||
/**
|
||||
* @group legacy
|
||||
* @expectedDeprecation Using the "Symfony\Component\Validator\Constraints\Length" constraint with the "min" option without setting the "allowEmptyString" one is deprecated and defaults to true. In 5.0, it will become optional and default to false.
|
||||
*/
|
||||
public function testLegacyEmptyStringIsValid()
|
||||
{
|
||||
$this->validator->validate('', new Length(6));
|
||||
|
||||
$this->assertNoViolation();
|
||||
}
|
||||
|
||||
public function testEmptyStringIsInvalid()
|
||||
{
|
||||
$this->validator->validate('', new Length([
|
||||
'value' => $limit = 6,
|
||||
'allowEmptyString' => false,
|
||||
'exactMessage' => 'myMessage',
|
||||
]));
|
||||
|
||||
$this->buildViolation('myMessage')
|
||||
->setParameter('{{ value }}', '""')
|
||||
->setParameter('{{ limit }}', $limit)
|
||||
->setInvalidValue('')
|
||||
->setPlural($limit)
|
||||
->setCode(Length::TOO_SHORT_ERROR)
|
||||
->assertRaised();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Validator\Exception\UnexpectedValueException
|
||||
*/
|
||||
public function testExpectsStringCompatibleType()
|
||||
{
|
||||
$this->validator->validate(new \stdClass(), new Length(5));
|
||||
$this->validator->validate(new \stdClass(), new Length(['value' => 5, 'allowEmptyString' => false]));
|
||||
}
|
||||
|
||||
public function getThreeOrLessCharacters()
|
||||
|
@ -109,7 +130,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
|
|||
*/
|
||||
public function testValidValuesMin($value)
|
||||
{
|
||||
$constraint = new Length(['min' => 5]);
|
||||
$constraint = new Length(['min' => 5, 'allowEmptyString' => false]);
|
||||
$this->validator->validate($value, $constraint);
|
||||
|
||||
$this->assertNoViolation();
|
||||
|
@ -131,7 +152,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
|
|||
*/
|
||||
public function testValidValuesExact($value)
|
||||
{
|
||||
$constraint = new Length(4);
|
||||
$constraint = new Length(['value' => 4, 'allowEmptyString' => false]);
|
||||
$this->validator->validate($value, $constraint);
|
||||
|
||||
$this->assertNoViolation();
|
||||
|
@ -142,7 +163,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
|
|||
*/
|
||||
public function testValidNormalizedValues($value)
|
||||
{
|
||||
$constraint = new Length(['min' => 3, 'max' => 3, 'normalizer' => 'trim']);
|
||||
$constraint = new Length(['min' => 3, 'max' => 3, 'normalizer' => 'trim', 'allowEmptyString' => false]);
|
||||
$this->validator->validate($value, $constraint);
|
||||
|
||||
$this->assertNoViolation();
|
||||
|
@ -156,6 +177,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
|
|||
$constraint = new Length([
|
||||
'min' => 4,
|
||||
'minMessage' => 'myMessage',
|
||||
'allowEmptyString' => false,
|
||||
]);
|
||||
|
||||
$this->validator->validate($value, $constraint);
|
||||
|
@ -199,6 +221,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
|
|||
'min' => 4,
|
||||
'max' => 4,
|
||||
'exactMessage' => 'myMessage',
|
||||
'allowEmptyString' => false,
|
||||
]);
|
||||
|
||||
$this->validator->validate($value, $constraint);
|
||||
|
@ -221,6 +244,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
|
|||
'min' => 4,
|
||||
'max' => 4,
|
||||
'exactMessage' => 'myMessage',
|
||||
'allowEmptyString' => false,
|
||||
]);
|
||||
|
||||
$this->validator->validate($value, $constraint);
|
||||
|
@ -244,6 +268,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
|
|||
'max' => 1,
|
||||
'charset' => $charset,
|
||||
'charsetMessage' => 'myMessage',
|
||||
'allowEmptyString' => false,
|
||||
]);
|
||||
|
||||
$this->validator->validate($value, $constraint);
|
||||
|
@ -262,7 +287,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
|
|||
|
||||
public function testConstraintDefaultOption()
|
||||
{
|
||||
$constraint = new Length(5);
|
||||
$constraint = new Length(['value' => 5, 'allowEmptyString' => false]);
|
||||
|
||||
$this->assertEquals(5, $constraint->min);
|
||||
$this->assertEquals(5, $constraint->max);
|
||||
|
@ -270,7 +295,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase
|
|||
|
||||
public function testConstraintAnnotationDefaultOption()
|
||||
{
|
||||
$constraint = new Length(['value' => 5, 'exactMessage' => 'message']);
|
||||
$constraint = new Length(['value' => 5, 'exactMessage' => 'message', 'allowEmptyString' => false]);
|
||||
|
||||
$this->assertEquals(5, $constraint->min);
|
||||
$this->assertEquals(5, $constraint->max);
|
||||
|
|
|
@ -103,7 +103,7 @@ class RecursiveValidatorTest extends AbstractTest
|
|||
public function testCollectionConstraintValidateAllGroupsForNestedConstraints()
|
||||
{
|
||||
$this->metadata->addPropertyConstraint('data', new Collection(['fields' => [
|
||||
'one' => [new NotBlank(['groups' => 'one']), new Length(['min' => 2, 'groups' => 'two'])],
|
||||
'one' => [new NotBlank(['groups' => 'one']), new Length(['min' => 2, 'groups' => 'two', 'allowEmptyString' => false])],
|
||||
'two' => [new NotBlank(['groups' => 'two'])],
|
||||
]]));
|
||||
|
||||
|
@ -121,7 +121,7 @@ class RecursiveValidatorTest extends AbstractTest
|
|||
{
|
||||
$this->metadata->addPropertyConstraint('data', new All(['constraints' => [
|
||||
new NotBlank(['groups' => 'one']),
|
||||
new Length(['min' => 2, 'groups' => 'two']),
|
||||
new Length(['min' => 2, 'groups' => 'two', 'allowEmptyString' => false]),
|
||||
]]));
|
||||
|
||||
$entity = new Entity();
|
||||
|
@ -129,8 +129,9 @@ class RecursiveValidatorTest extends AbstractTest
|
|||
|
||||
$violations = $this->validator->validate($entity, null, ['one', 'two']);
|
||||
|
||||
$this->assertCount(2, $violations);
|
||||
$this->assertCount(3, $violations);
|
||||
$this->assertInstanceOf(NotBlank::class, $violations->get(0)->getConstraint());
|
||||
$this->assertInstanceOf(Length::class, $violations->get(1)->getConstraint());
|
||||
$this->assertInstanceOf(Length::class, $violations->get(2)->getConstraint());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<?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\Validator\Tests\Violation;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Translation\IdentityTranslator;
|
||||
use Symfony\Component\Validator\ConstraintViolationList;
|
||||
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
|
||||
use Symfony\Component\Validator\Violation\ConstraintViolationBuilder;
|
||||
|
||||
class ConstraintViolationBuilderTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @group legacy
|
||||
* @expectedDeprecation Not using a string as the error code in Symfony\Component\Validator\Violation\ConstraintViolationBuilder::setCode() is deprecated since Symfony 4.4. A type-hint will be added in 5.0.
|
||||
* @expectedDeprecation Not using a string as the error code in Symfony\Component\Validator\ConstraintViolation::__construct() is deprecated since Symfony 4.4. A type-hint will be added in 5.0.
|
||||
*/
|
||||
public function testNonStringCode()
|
||||
{
|
||||
$constraintViolationList = new ConstraintViolationList();
|
||||
(new ConstraintViolationBuilder($constraintViolationList, new ConstraintA(), 'invalid message', [], null, 'foo', 'baz', new IdentityTranslator()))
|
||||
->setCode(42)
|
||||
->addViolation();
|
||||
|
||||
self::assertSame(42, $constraintViolationList->get(0)->getCode());
|
||||
}
|
||||
}
|
|
@ -121,6 +121,10 @@ class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface
|
|||
*/
|
||||
public function setCode(?string $code)
|
||||
{
|
||||
if (null !== $code && !\is_string($code)) {
|
||||
@trigger_error(sprintf('Not using a string as the error code in %s() is deprecated since Symfony 4.4. A type-hint will be added in 5.0.', __METHOD__), E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$this->code = $code;
|
||||
|
||||
return $this;
|
||||
|
|
Reference in New Issue