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:
Nicolas Grekas 2018-08-24 12:22:26 +02:00
commit 51525e63f8
14 changed files with 221 additions and 5 deletions

View File

@ -41,8 +41,15 @@ services:
- mongodb
- redis-server
- rabbitmq
- docker
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
set -e

View File

@ -482,7 +482,7 @@ class UniqueEntityValidatorTest extends ConstraintValidatorTestCase
$this->buildViolation('myMessage')
->atPath('property.path.single')
->setParameter('{{ value }}', 'object("Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity") identified by (id => 1)')
->setParameter('{{ value }}', 'foo')
->setInvalidValue($entity1)
->setCode(UniqueEntity::NOT_UNIQUE_ERROR)
->setCause(array($associated, $associated2))

View File

@ -186,6 +186,10 @@ class UniqueEntityValidator extends ConstraintValidator
return $this->formatValue($value, self::PRETTY_DATE);
}
if (\method_exists($value, '__toString')) {
return (string) $value;
}
if ($class->getName() !== $idClass = \get_class($value)) {
// non unique value might be a composite PK that consists of other entity objects
if ($em->getMetadataFactory()->hasMetadataFor($idClass)) {

View File

@ -89,6 +89,7 @@ class MemcachedAdapterTest extends AdapterTestCase
$this->assertTrue($client->getOption(\Memcached::OPT_COMPRESSION));
$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));
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -134,6 +134,7 @@ trait MemcachedTrait
$options = array_change_key_case($options, CASE_UPPER);
$client->setOption(\Memcached::OPT_BINARY_PROTOCOL, 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)) {
$client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
}

View File

@ -99,7 +99,7 @@ interface FormConfigInterface
/**
* Returns the data mapper of the form.
*
* @return DataMapperInterface The data mapper
* @return DataMapperInterface|null The data mapper
*/
public function getDataMapper();

View File

@ -54,8 +54,10 @@ class RedisStore implements StoreInterface
$script = '
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("PEXPIRE", KEYS[1], ARGV[2])
elseif redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2]) then
return 1
else
return redis.call("set", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
return 0
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));
}
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)));
}
/**

View File

@ -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')));
}
}

View File

@ -160,7 +160,12 @@ class PropertyAccessor implements PropertyAccessorInterface
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 += \strlen($delim);
$type = $trace[$i]['args'][0];

View File

@ -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.';
}
}

View File

@ -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
}
}

View File

@ -15,11 +15,13 @@ use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
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\TestClassIsWritable;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicCall;
use Symfony\Component\PropertyAccess\Tests\Fixtures\TestClassMagicGet;
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\TypeHinted;
@ -538,6 +540,17 @@ class PropertyAccessorTest extends TestCase
$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()
{
$date = new \DateTime();
@ -632,4 +645,34 @@ class PropertyAccessorTest extends TestCase
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');
}
}