Merge branch '3.4' into 4.1
* 3.4: [DoctrineBridge] support __toString as documented for UniqueEntityValidator [travis] enable Redis cluster [Cache] enable Memcached::OPT_TCP_NODELAY to fix perf of misses fix data mapper return type in docblock fix type error handling when writing values
This commit is contained in:
commit
51525e63f8
|
@ -41,8 +41,15 @@ services:
|
||||||
- mongodb
|
- mongodb
|
||||||
- redis-server
|
- redis-server
|
||||||
- rabbitmq
|
- rabbitmq
|
||||||
|
- docker
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
|
- |
|
||||||
|
# Start Redis cluster
|
||||||
|
docker pull grokzen/redis-cluster:4.0.8
|
||||||
|
docker run -d -p 7000:7000 -p 7001:7001 -p 7002:7002 -p 7003:7003 -p 7004:7004 -p 7005:7005 --name redis-cluster grokzen/redis-cluster:4.0.8
|
||||||
|
export REDIS_CLUSTER_HOSTS='localhost:7000 localhost:7001 localhost:7002 localhost:7003 localhost:7004 localhost:7005'
|
||||||
|
|
||||||
- |
|
- |
|
||||||
# General configuration
|
# General configuration
|
||||||
set -e
|
set -e
|
||||||
|
|
|
@ -482,7 +482,7 @@ class UniqueEntityValidatorTest extends ConstraintValidatorTestCase
|
||||||
|
|
||||||
$this->buildViolation('myMessage')
|
$this->buildViolation('myMessage')
|
||||||
->atPath('property.path.single')
|
->atPath('property.path.single')
|
||||||
->setParameter('{{ value }}', 'object("Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity") identified by (id => 1)')
|
->setParameter('{{ value }}', 'foo')
|
||||||
->setInvalidValue($entity1)
|
->setInvalidValue($entity1)
|
||||||
->setCode(UniqueEntity::NOT_UNIQUE_ERROR)
|
->setCode(UniqueEntity::NOT_UNIQUE_ERROR)
|
||||||
->setCause(array($associated, $associated2))
|
->setCause(array($associated, $associated2))
|
||||||
|
|
|
@ -186,6 +186,10 @@ class UniqueEntityValidator extends ConstraintValidator
|
||||||
return $this->formatValue($value, self::PRETTY_DATE);
|
return $this->formatValue($value, self::PRETTY_DATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (\method_exists($value, '__toString')) {
|
||||||
|
return (string) $value;
|
||||||
|
}
|
||||||
|
|
||||||
if ($class->getName() !== $idClass = \get_class($value)) {
|
if ($class->getName() !== $idClass = \get_class($value)) {
|
||||||
// non unique value might be a composite PK that consists of other entity objects
|
// non unique value might be a composite PK that consists of other entity objects
|
||||||
if ($em->getMetadataFactory()->hasMetadataFor($idClass)) {
|
if ($em->getMetadataFactory()->hasMetadataFor($idClass)) {
|
||||||
|
|
|
@ -89,6 +89,7 @@ class MemcachedAdapterTest extends AdapterTestCase
|
||||||
|
|
||||||
$this->assertTrue($client->getOption(\Memcached::OPT_COMPRESSION));
|
$this->assertTrue($client->getOption(\Memcached::OPT_COMPRESSION));
|
||||||
$this->assertSame(1, $client->getOption(\Memcached::OPT_BINARY_PROTOCOL));
|
$this->assertSame(1, $client->getOption(\Memcached::OPT_BINARY_PROTOCOL));
|
||||||
|
$this->assertSame(1, $client->getOption(\Memcached::OPT_TCP_NODELAY));
|
||||||
$this->assertSame(1, $client->getOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE));
|
$this->assertSame(1, $client->getOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?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\Cache\Tests\Adapter;
|
||||||
|
|
||||||
|
class RedisClusterAdapterTest extends AbstractRedisAdapterTest
|
||||||
|
{
|
||||||
|
public static function setupBeforeClass()
|
||||||
|
{
|
||||||
|
if (!class_exists('RedisCluster')) {
|
||||||
|
self::markTestSkipped('The RedisCluster class is required.');
|
||||||
|
}
|
||||||
|
if (!$hosts = getenv('REDIS_CLUSTER_HOSTS')) {
|
||||||
|
self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.');
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$redis = new \RedisCluster(null, explode(' ', $hosts));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?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\Cache\Tests\Simple;
|
||||||
|
|
||||||
|
class RedisClusterCacheTest extends AbstractRedisCacheTest
|
||||||
|
{
|
||||||
|
public static function setupBeforeClass()
|
||||||
|
{
|
||||||
|
if (!class_exists('RedisCluster')) {
|
||||||
|
self::markTestSkipped('The RedisCluster class is required.');
|
||||||
|
}
|
||||||
|
if (!$hosts = getenv('REDIS_CLUSTER_HOSTS')) {
|
||||||
|
self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.');
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$redis = new \RedisCluster(null, explode(' ', $hosts));
|
||||||
|
}
|
||||||
|
}
|
|
@ -134,6 +134,7 @@ trait MemcachedTrait
|
||||||
$options = array_change_key_case($options, CASE_UPPER);
|
$options = array_change_key_case($options, CASE_UPPER);
|
||||||
$client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
|
$client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
|
||||||
$client->setOption(\Memcached::OPT_NO_BLOCK, true);
|
$client->setOption(\Memcached::OPT_NO_BLOCK, true);
|
||||||
|
$client->setOption(\Memcached::OPT_TCP_NODELAY, true);
|
||||||
if (!array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) {
|
if (!array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) {
|
||||||
$client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
|
$client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,7 @@ interface FormConfigInterface
|
||||||
/**
|
/**
|
||||||
* Returns the data mapper of the form.
|
* Returns the data mapper of the form.
|
||||||
*
|
*
|
||||||
* @return DataMapperInterface The data mapper
|
* @return DataMapperInterface|null The data mapper
|
||||||
*/
|
*/
|
||||||
public function getDataMapper();
|
public function getDataMapper();
|
||||||
|
|
||||||
|
|
|
@ -54,8 +54,10 @@ class RedisStore implements StoreInterface
|
||||||
$script = '
|
$script = '
|
||||||
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
||||||
return redis.call("PEXPIRE", KEYS[1], ARGV[2])
|
return redis.call("PEXPIRE", KEYS[1], ARGV[2])
|
||||||
|
elseif redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2]) then
|
||||||
|
return 1
|
||||||
else
|
else
|
||||||
return redis.call("set", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
|
return 0
|
||||||
end
|
end
|
||||||
';
|
';
|
||||||
|
|
||||||
|
@ -140,7 +142,7 @@ class RedisStore implements StoreInterface
|
||||||
return \call_user_func_array(array($this->redis, 'eval'), array_merge(array($script, 1, $resource), $args));
|
return \call_user_func_array(array($this->redis, 'eval'), array_merge(array($script, 1, $resource), $args));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidArgumentException(sprintf('%s() expects been initialized with a Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, \is_object($this->redis) ? \get_class($this->redis) : \gettype($this->redis)));
|
throw new InvalidArgumentException(sprintf('%s() expects being initialized with a Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, \is_object($this->redis) ? \get_class($this->redis) : \gettype($this->redis)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?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\Lock\Tests\Store;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||||
|
*
|
||||||
|
* @requires extension redis
|
||||||
|
*/
|
||||||
|
class RedisClusterStoreTest extends AbstractRedisStoreTest
|
||||||
|
{
|
||||||
|
public static function setupBeforeClass()
|
||||||
|
{
|
||||||
|
if (!class_exists('RedisCluster')) {
|
||||||
|
self::markTestSkipped('The RedisCluster class is required.');
|
||||||
|
}
|
||||||
|
if (!getenv('REDIS_CLUSTER_HOSTS')) {
|
||||||
|
self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getRedisConnection()
|
||||||
|
{
|
||||||
|
return new \RedisCluster(null, explode(' ', getenv('REDIS_CLUSTER_HOSTS')));
|
||||||
|
}
|
||||||
|
}
|
|
@ -160,7 +160,12 @@ class PropertyAccessor implements PropertyAccessorInterface
|
||||||
|
|
||||||
private static function throwInvalidArgumentException($message, $trace, $i)
|
private static function throwInvalidArgumentException($message, $trace, $i)
|
||||||
{
|
{
|
||||||
if (isset($trace[$i]['file']) && __FILE__ === $trace[$i]['file'] && isset($trace[$i]['args'][0])) {
|
// the type mismatch is not caused by invalid arguments (but e.g. by an incompatible return type hint of the writer method)
|
||||||
|
if (0 !== strpos($message, 'Argument ')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($trace[$i]['file']) && __FILE__ === $trace[$i]['file'] && array_key_exists(0, $trace[$i]['args'])) {
|
||||||
$pos = strpos($message, $delim = 'must be of the type ') ?: (strpos($message, $delim = 'must be an instance of ') ?: strpos($message, $delim = 'must implement interface '));
|
$pos = strpos($message, $delim = 'must be of the type ') ?: (strpos($message, $delim = 'must be an instance of ') ?: strpos($message, $delim = 'must implement interface '));
|
||||||
$pos += \strlen($delim);
|
$pos += \strlen($delim);
|
||||||
$type = $trace[$i]['args'][0];
|
$type = $trace[$i]['args'][0];
|
||||||
|
|
|
@ -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\PropertyAccess\Tests\Fixtures;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
*/
|
||||||
|
class ReturnTyped
|
||||||
|
{
|
||||||
|
public function getFoos(): array
|
||||||
|
{
|
||||||
|
return 'It doesn\'t respect the return type on purpose';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFoo(\DateTime $dateTime)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeFoo(\DateTime $dateTime)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setName($name): self
|
||||||
|
{
|
||||||
|
return 'This does not respect the return type on purpose.';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?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\PropertyAccess\Tests\Fixtures;
|
||||||
|
|
||||||
|
class TestClassTypeErrorInsideCall
|
||||||
|
{
|
||||||
|
public function expectsDateTime(\DateTime $date)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProperty()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setProperty($property)
|
||||||
|
{
|
||||||
|
$this->expectsDateTime(null); // throws TypeError
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,11 +15,13 @@ use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||||
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
|
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
|
||||||
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||||
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\ReturnTyped;
|
||||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClass;
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClass;
|
||||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassIsWritable;
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassIsWritable;
|
||||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicCall;
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicCall;
|
||||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicGet;
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicGet;
|
||||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassSetValue;
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassSetValue;
|
||||||
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassTypeErrorInsideCall;
|
||||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\Ticket5775Object;
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\Ticket5775Object;
|
||||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\TypeHinted;
|
use Symfony\Component\PropertyAccess\Tests\Fixtures\TypeHinted;
|
||||||
|
|
||||||
|
@ -538,6 +540,17 @@ class PropertyAccessorTest extends TestCase
|
||||||
$this->propertyAccessor->setValue($object, 'date', 'This is a string, \DateTime expected.');
|
$this->propertyAccessor->setValue($object, 'date', 'This is a string, \DateTime expected.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidArgumentException
|
||||||
|
* @expectedExceptionMessage Expected argument of type "DateTime", "NULL" given
|
||||||
|
*/
|
||||||
|
public function testThrowTypeErrorWithNullArgument()
|
||||||
|
{
|
||||||
|
$object = new TypeHinted();
|
||||||
|
|
||||||
|
$this->propertyAccessor->setValue($object, 'date', null);
|
||||||
|
}
|
||||||
|
|
||||||
public function testSetTypeHint()
|
public function testSetTypeHint()
|
||||||
{
|
{
|
||||||
$date = new \DateTime();
|
$date = new \DateTime();
|
||||||
|
@ -632,4 +645,34 @@ class PropertyAccessorTest extends TestCase
|
||||||
|
|
||||||
return $obj;
|
return $obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \TypeError
|
||||||
|
*/
|
||||||
|
public function testThrowTypeErrorInsideSetterCall()
|
||||||
|
{
|
||||||
|
$object = new TestClassTypeErrorInsideCall();
|
||||||
|
|
||||||
|
$this->propertyAccessor->setValue($object, 'property', 'foo');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \TypeError
|
||||||
|
*/
|
||||||
|
public function testDoNotDiscardReturnTypeError()
|
||||||
|
{
|
||||||
|
$object = new ReturnTyped();
|
||||||
|
|
||||||
|
$this->propertyAccessor->setValue($object, 'foos', array(new \DateTime()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \TypeError
|
||||||
|
*/
|
||||||
|
public function testDoNotDiscardReturnTypeErrorWhenWriterMethodIsMisconfigured()
|
||||||
|
{
|
||||||
|
$object = new ReturnTyped();
|
||||||
|
|
||||||
|
$this->propertyAccessor->setValue($object, 'name', 'foo');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue