Merge branch '2.7' into 2.8
* 2.7: [ci] Phpunit tests wont run if composer is installed in a wrapper [ci] Add version tag in phpunit wrapper to trigger cache-reset on demand fix race condition at mkdir (#16258) [VarDumper] Fix PHP7 type-hints compat [Bridge] [PhpUnit] fixes documentation markup. [PropertyAccess] Port of the performance optimization from 2.3 trigger deprecation warning when using empty_value [PropertyAccess] Test access to dynamic properties [PropertyAccess] Fix dynamic property accessing. [Serializer] GetSetNormalizer shouldn't set/get static methods [Serializer] PropertyNormalizer shouldn't set static properties JsonDescriptor - encode container params only once Conflicts: src/Symfony/Component/VarDumper/Tests/Caster/ReflectionCasterTest.php
This commit is contained in:
commit
d86a3f3172
28
phpunit
28
phpunit
@ -1,6 +1,18 @@
|
|||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
<?php
|
<?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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Please update when phpunit needs to be reinstalled with fresh deps:
|
||||||
|
// Cache-Id-Version: 2015-11-09 12:13 UTC
|
||||||
|
|
||||||
use Symfony\Component\Process\ProcessUtils;
|
use Symfony\Component\Process\ProcessUtils;
|
||||||
|
|
||||||
error_reporting(-1);
|
error_reporting(-1);
|
||||||
@ -10,19 +22,11 @@ require __DIR__.'/src/Symfony/Component/Process/ProcessUtils.php';
|
|||||||
$PHPUNIT_VERSION = PHP_VERSION_ID >= 70000 ? '5.0' : '4.8';
|
$PHPUNIT_VERSION = PHP_VERSION_ID >= 70000 ? '5.0' : '4.8';
|
||||||
$PHPUNIT_DIR = __DIR__.'/.phpunit';
|
$PHPUNIT_DIR = __DIR__.'/.phpunit';
|
||||||
$PHP = defined('PHP_BINARY') ? PHP_BINARY : 'php';
|
$PHP = defined('PHP_BINARY') ? PHP_BINARY : 'php';
|
||||||
|
|
||||||
if (!file_exists($COMPOSER = __DIR__.'/composer.phar')) {
|
|
||||||
$COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? `where.exe composer.phar` : (`which composer.phar` ?: `which composer`));
|
|
||||||
if (!file_exists($COMPOSER)) {
|
|
||||||
stream_copy_to_stream(
|
|
||||||
fopen('https://getcomposer.org/composer.phar', 'rb'),
|
|
||||||
fopen($COMPOSER = __DIR__.'/composer.phar', 'wb')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$PHP = ProcessUtils::escapeArgument($PHP);
|
$PHP = ProcessUtils::escapeArgument($PHP);
|
||||||
$COMPOSER = $PHP.' '.ProcessUtils::escapeArgument($COMPOSER);
|
|
||||||
|
$COMPOSER = file_exists($COMPOSER = __DIR__.'/composer.phar') || ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? `where.exe composer.phar` : `which composer.phar`))
|
||||||
|
? $PHP.' '.ProcessUtils::escapeArgument($COMPOSER)
|
||||||
|
: 'composer';
|
||||||
|
|
||||||
if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__FILE__) !== @file_get_contents("$PHPUNIT_DIR/.md5")) {
|
if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__FILE__) !== @file_get_contents("$PHPUNIT_DIR/.md5")) {
|
||||||
// Build a standalone phpunit without symfony/yaml
|
// Build a standalone phpunit without symfony/yaml
|
||||||
|
@ -12,7 +12,7 @@ It comes with the following features:
|
|||||||
|
|
||||||
By default any non-legacy-tagged or any non-@-silenced deprecation notices will
|
By default any non-legacy-tagged or any non-@-silenced deprecation notices will
|
||||||
make tests fail.
|
make tests fail.
|
||||||
This can be changed by setting the SYMFONY_DEPRECATIONS_HELPER environment
|
This can be changed by setting the `SYMFONY_DEPRECATIONS_HELPER` environment
|
||||||
variable to `weak`. This will make the bridge ignore deprecation notices and
|
variable to `weak`. This will make the bridge ignore deprecation notices and
|
||||||
is useful to projects that must use deprecated interfaces for backward
|
is useful to projects that must use deprecated interfaces for backward
|
||||||
compatibility reasons.
|
compatibility reasons.
|
||||||
@ -33,7 +33,7 @@ A summary of deprecation notices is displayed at the end of the test suite:
|
|||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
Add this bridge to the `require-dev` section of your composer.json file
|
Add this bridge to the `require-dev` section of your `composer.json` file
|
||||||
(not in `require`) with e.g. `composer require --dev "symfony/phpunit-bridge"`.
|
(not in `require`) with e.g. `composer require --dev "symfony/phpunit-bridge"`.
|
||||||
|
|
||||||
When running `phpunit`, you will see a summary of deprecation notices at the end
|
When running `phpunit`, you will see a summary of deprecation notices at the end
|
||||||
|
@ -157,7 +157,7 @@ class JsonDescriptor extends Descriptor
|
|||||||
{
|
{
|
||||||
$key = isset($options['parameter']) ? $options['parameter'] : '';
|
$key = isset($options['parameter']) ? $options['parameter'] : '';
|
||||||
|
|
||||||
$this->writeData(array($key => $this->formatParameter($parameter)), $options);
|
$this->writeData(array($key => $parameter), $options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,6 +118,7 @@ abstract class AbstractDescriptorTest extends \PHPUnit_Framework_TestCase
|
|||||||
$data = $this->getDescriptionTestData(ObjectsProvider::getContainerParameter());
|
$data = $this->getDescriptionTestData(ObjectsProvider::getContainerParameter());
|
||||||
|
|
||||||
$data[0][] = array('parameter' => 'database_name');
|
$data[0][] = array('parameter' => 'database_name');
|
||||||
|
$data[1][] = array('parameter' => 'twig.form.resources');
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
@ -72,9 +72,16 @@ class ObjectsProvider
|
|||||||
{
|
{
|
||||||
$builder = new ContainerBuilder();
|
$builder = new ContainerBuilder();
|
||||||
$builder->setParameter('database_name', 'symfony');
|
$builder->setParameter('database_name', 'symfony');
|
||||||
|
$builder->setParameter('twig.form.resources', array(
|
||||||
|
'bootstrap_3_horizontal_layout.html.twig',
|
||||||
|
'bootstrap_3_layout.html.twig',
|
||||||
|
'form_div_layout.html.twig',
|
||||||
|
'form_table_layout.html.twig',
|
||||||
|
));
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'parameter' => $builder,
|
'parameter' => $builder,
|
||||||
|
'array_parameter' => $builder,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"twig.form.resources": ["bootstrap_3_horizontal_layout.html.twig", "bootstrap_3_layout.html.twig", "form_div_layout.html.twig", "form_table_layout.html.twig"]
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
twig.form.resources
|
||||||
|
===================
|
||||||
|
|
||||||
|
["bootstrap_3_horizontal_layout.html.twig","bootstrap_3_layo...
|
@ -0,0 +1 @@
|
|||||||
|
["bootstrap_3_horizontal_layout.html.twig","bootstrap_3_layo...
|
@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<parameter key="twig.form.resources">["bootstrap_3_horizontal_layout.html.twig","bootstrap_3_layo...</parameter>
|
@ -248,13 +248,8 @@ class ChoiceType extends AbstractType
|
|||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
$emptyValue = function (Options $options) {
|
|
||||||
return $options['required'] ? null : '';
|
|
||||||
};
|
|
||||||
|
|
||||||
// for BC with the "empty_value" option
|
|
||||||
$placeholder = function (Options $options) {
|
$placeholder = function (Options $options) {
|
||||||
return $options['empty_value'];
|
return $options['required'] ? null : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
$choiceListNormalizer = function (Options $options, $choiceList) use ($choiceListFactory) {
|
$choiceListNormalizer = function (Options $options, $choiceList) use ($choiceListFactory) {
|
||||||
@ -287,6 +282,12 @@ class ChoiceType extends AbstractType
|
|||||||
};
|
};
|
||||||
|
|
||||||
$placeholderNormalizer = function (Options $options, $placeholder) {
|
$placeholderNormalizer = function (Options $options, $placeholder) {
|
||||||
|
if (!is_object($options['empty_value']) || !$options['empty_value'] instanceof \Exception) {
|
||||||
|
@trigger_error('The form option "empty_value" is deprecated since version 2.6 and will be removed in 3.0. Use "placeholder" instead.', E_USER_DEPRECATED);
|
||||||
|
|
||||||
|
$placeholder = $options['empty_value'];
|
||||||
|
}
|
||||||
|
|
||||||
if ($options['multiple']) {
|
if ($options['multiple']) {
|
||||||
// never use an empty value for this case
|
// never use an empty value for this case
|
||||||
return;
|
return;
|
||||||
@ -328,7 +329,7 @@ class ChoiceType extends AbstractType
|
|||||||
'preferred_choices' => array(),
|
'preferred_choices' => array(),
|
||||||
'group_by' => null,
|
'group_by' => null,
|
||||||
'empty_data' => $emptyData,
|
'empty_data' => $emptyData,
|
||||||
'empty_value' => $emptyValue, // deprecated
|
'empty_value' => new \Exception(), // deprecated
|
||||||
'placeholder' => $placeholder,
|
'placeholder' => $placeholder,
|
||||||
'error_bubbling' => false,
|
'error_bubbling' => false,
|
||||||
'compound' => $compound,
|
'compound' => $compound,
|
||||||
@ -340,7 +341,6 @@ class ChoiceType extends AbstractType
|
|||||||
));
|
));
|
||||||
|
|
||||||
$resolver->setNormalizer('choice_list', $choiceListNormalizer);
|
$resolver->setNormalizer('choice_list', $choiceListNormalizer);
|
||||||
$resolver->setNormalizer('empty_value', $placeholderNormalizer);
|
|
||||||
$resolver->setNormalizer('placeholder', $placeholderNormalizer);
|
$resolver->setNormalizer('placeholder', $placeholderNormalizer);
|
||||||
$resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer);
|
$resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer);
|
||||||
|
|
||||||
|
@ -183,15 +183,17 @@ class DateType extends AbstractType
|
|||||||
return $options['widget'] !== 'single_text';
|
return $options['widget'] !== 'single_text';
|
||||||
};
|
};
|
||||||
|
|
||||||
$emptyValue = $placeholderDefault = function (Options $options) {
|
$placeholder = $placeholderDefault = function (Options $options) {
|
||||||
return $options['required'] ? null : '';
|
return $options['required'] ? null : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
$placeholder = function (Options $options) {
|
|
||||||
return $options['empty_value'];
|
|
||||||
};
|
|
||||||
|
|
||||||
$placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) {
|
$placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) {
|
||||||
|
if (!is_object($options['empty_value']) || !$options['empty_value'] instanceof \Exception) {
|
||||||
|
@trigger_error('The form option "empty_value" is deprecated since version 2.6 and will be removed in 3.0. Use "placeholder" instead.', E_USER_DEPRECATED);
|
||||||
|
|
||||||
|
$placeholder = $options['empty_value'];
|
||||||
|
}
|
||||||
|
|
||||||
if (is_array($placeholder)) {
|
if (is_array($placeholder)) {
|
||||||
$default = $placeholderDefault($options);
|
$default = $placeholderDefault($options);
|
||||||
|
|
||||||
@ -238,7 +240,7 @@ class DateType extends AbstractType
|
|||||||
'format' => $format,
|
'format' => $format,
|
||||||
'model_timezone' => null,
|
'model_timezone' => null,
|
||||||
'view_timezone' => null,
|
'view_timezone' => null,
|
||||||
'empty_value' => $emptyValue, // deprecated
|
'empty_value' => new \Exception(), // deprecated
|
||||||
'placeholder' => $placeholder,
|
'placeholder' => $placeholder,
|
||||||
'html5' => true,
|
'html5' => true,
|
||||||
// Don't modify \DateTime classes by reference, we treat
|
// Don't modify \DateTime classes by reference, we treat
|
||||||
@ -254,7 +256,6 @@ class DateType extends AbstractType
|
|||||||
'choice_translation_domain' => false,
|
'choice_translation_domain' => false,
|
||||||
));
|
));
|
||||||
|
|
||||||
$resolver->setNormalizer('empty_value', $placeholderNormalizer);
|
|
||||||
$resolver->setNormalizer('placeholder', $placeholderNormalizer);
|
$resolver->setNormalizer('placeholder', $placeholderNormalizer);
|
||||||
$resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer);
|
$resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer);
|
||||||
|
|
||||||
|
@ -171,16 +171,17 @@ class TimeType extends AbstractType
|
|||||||
return $options['widget'] !== 'single_text';
|
return $options['widget'] !== 'single_text';
|
||||||
};
|
};
|
||||||
|
|
||||||
$emptyValue = $placeholderDefault = function (Options $options) {
|
$placeholder = $placeholderDefault = function (Options $options) {
|
||||||
return $options['required'] ? null : '';
|
return $options['required'] ? null : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
// for BC with the "empty_value" option
|
|
||||||
$placeholder = function (Options $options) {
|
|
||||||
return $options['empty_value'];
|
|
||||||
};
|
|
||||||
|
|
||||||
$placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) {
|
$placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) {
|
||||||
|
if (!is_object($options['empty_value']) || !$options['empty_value'] instanceof \Exception) {
|
||||||
|
@trigger_error('The form option "empty_value" is deprecated since version 2.6 and will be removed in 3.0. Use "placeholder" instead.', E_USER_DEPRECATED);
|
||||||
|
|
||||||
|
$placeholder = $options['empty_value'];
|
||||||
|
}
|
||||||
|
|
||||||
if (is_array($placeholder)) {
|
if (is_array($placeholder)) {
|
||||||
$default = $placeholderDefault($options);
|
$default = $placeholderDefault($options);
|
||||||
|
|
||||||
@ -224,7 +225,7 @@ class TimeType extends AbstractType
|
|||||||
'with_seconds' => false,
|
'with_seconds' => false,
|
||||||
'model_timezone' => null,
|
'model_timezone' => null,
|
||||||
'view_timezone' => null,
|
'view_timezone' => null,
|
||||||
'empty_value' => $emptyValue, // deprecated
|
'empty_value' => new \Exception(), // deprecated
|
||||||
'placeholder' => $placeholder,
|
'placeholder' => $placeholder,
|
||||||
'html5' => true,
|
'html5' => true,
|
||||||
// Don't modify \DateTime classes by reference, we treat
|
// Don't modify \DateTime classes by reference, we treat
|
||||||
@ -240,7 +241,6 @@ class TimeType extends AbstractType
|
|||||||
'choice_translation_domain' => false,
|
'choice_translation_domain' => false,
|
||||||
));
|
));
|
||||||
|
|
||||||
$resolver->setNormalizer('empty_value', $placeholderNormalizer);
|
|
||||||
$resolver->setNormalizer('placeholder', $placeholderNormalizer);
|
$resolver->setNormalizer('placeholder', $placeholderNormalizer);
|
||||||
$resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer);
|
$resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer);
|
||||||
|
|
||||||
|
@ -32,12 +32,16 @@ class Store implements StoreInterface
|
|||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param string $root The path to the cache directory
|
* @param string $root The path to the cache directory
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException
|
||||||
*/
|
*/
|
||||||
public function __construct($root)
|
public function __construct($root)
|
||||||
{
|
{
|
||||||
$this->root = $root;
|
$this->root = $root;
|
||||||
if (!is_dir($this->root)) {
|
if (!is_dir($this->root)) {
|
||||||
mkdir($this->root, 0777, true);
|
if (false === @mkdir($this->root, 0777, true) && !is_dir($this->root)) {
|
||||||
|
throw new \RuntimeException(sprintf('Unable to create the store directory (%s).', $this->root));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->keyCache = new \SplObjectStorage();
|
$this->keyCache = new \SplObjectStorage();
|
||||||
$this->locks = array();
|
$this->locks = array();
|
||||||
@ -74,7 +78,7 @@ class Store implements StoreInterface
|
|||||||
public function lock(Request $request)
|
public function lock(Request $request)
|
||||||
{
|
{
|
||||||
$path = $this->getPath($this->getCacheKey($request).'.lck');
|
$path = $this->getPath($this->getCacheKey($request).'.lck');
|
||||||
if (!is_dir(dirname($path)) && false === @mkdir(dirname($path), 0777, true)) {
|
if (!is_dir(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,7 +342,7 @@ class Store implements StoreInterface
|
|||||||
private function save($key, $data)
|
private function save($key, $data)
|
||||||
{
|
{
|
||||||
$path = $this->getPath($key);
|
$path = $this->getPath($key);
|
||||||
if (!is_dir(dirname($path)) && false === @mkdir(dirname($path), 0777, true)) {
|
if (!is_dir(dirname($path)) && false === @mkdir(dirname($path), 0777, true) && !is_dir(dirname($path))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,8 +41,8 @@ class FileProfilerStorage implements ProfilerStorageInterface
|
|||||||
}
|
}
|
||||||
$this->folder = substr($dsn, 5);
|
$this->folder = substr($dsn, 5);
|
||||||
|
|
||||||
if (!is_dir($this->folder)) {
|
if (!is_dir($this->folder) && false === @mkdir($this->folder, 0777, true) && !is_dir($this->folder)) {
|
||||||
mkdir($this->folder, 0777, true);
|
throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +128,8 @@ class FileProfilerStorage implements ProfilerStorageInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException
|
||||||
*/
|
*/
|
||||||
public function write(Profile $profile)
|
public function write(Profile $profile)
|
||||||
{
|
{
|
||||||
@ -137,8 +139,8 @@ class FileProfilerStorage implements ProfilerStorageInterface
|
|||||||
if (!$profileIndexed) {
|
if (!$profileIndexed) {
|
||||||
// Create directory
|
// Create directory
|
||||||
$dir = dirname($file);
|
$dir = dirname($file);
|
||||||
if (!is_dir($dir)) {
|
if (!is_dir($dir) && false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
|
||||||
mkdir($dir, 0777, true);
|
throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,12 +20,24 @@ use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
|
|||||||
* Default implementation of {@link PropertyAccessorInterface}.
|
* Default implementation of {@link PropertyAccessorInterface}.
|
||||||
*
|
*
|
||||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
*/
|
*/
|
||||||
class PropertyAccessor implements PropertyAccessorInterface
|
class PropertyAccessor implements PropertyAccessorInterface
|
||||||
{
|
{
|
||||||
const VALUE = 0;
|
const VALUE = 0;
|
||||||
const IS_REF = 1;
|
const IS_REF = 1;
|
||||||
const IS_REF_CHAINED = 2;
|
const IS_REF_CHAINED = 2;
|
||||||
|
const ACCESS_HAS_PROPERTY = 0;
|
||||||
|
const ACCESS_TYPE = 1;
|
||||||
|
const ACCESS_NAME = 2;
|
||||||
|
const ACCESS_REF = 3;
|
||||||
|
const ACCESS_ADDER = 4;
|
||||||
|
const ACCESS_REMOVER = 5;
|
||||||
|
const ACCESS_TYPE_METHOD = 0;
|
||||||
|
const ACCESS_TYPE_PROPERTY = 1;
|
||||||
|
const ACCESS_TYPE_MAGIC = 2;
|
||||||
|
const ACCESS_TYPE_ADDER_AND_REMOVER = 3;
|
||||||
|
const ACCESS_TYPE_NOT_FOUND = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var bool
|
* @var bool
|
||||||
@ -37,6 +49,16 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
*/
|
*/
|
||||||
private $ignoreInvalidIndices;
|
private $ignoreInvalidIndices;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $readPropertyCache = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $writePropertyCache = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should not be used by application code. Use
|
* Should not be used by application code. Use
|
||||||
* {@link PropertyAccess::createPropertyAccessor()} instead.
|
* {@link PropertyAccess::createPropertyAccessor()} instead.
|
||||||
@ -78,7 +100,7 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
self::IS_REF => true,
|
self::IS_REF => true,
|
||||||
self::IS_REF_CHAINED => true,
|
self::IS_REF_CHAINED => true,
|
||||||
));
|
));
|
||||||
|
|
||||||
$propertyMaxIndex = count($propertyValues) - 1;
|
$propertyMaxIndex = count($propertyValues) - 1;
|
||||||
|
|
||||||
for ($i = $propertyMaxIndex; $i >= 0; --$i) {
|
for ($i = $propertyMaxIndex; $i >= 0; --$i) {
|
||||||
@ -330,51 +352,31 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
throw new NoSuchPropertyException(sprintf('Cannot read property "%s" from an array. Maybe you intended to write the property path as "[%s]" instead.', $property, $property));
|
throw new NoSuchPropertyException(sprintf('Cannot read property "%s" from an array. Maybe you intended to write the property path as "[%s]" instead.', $property, $property));
|
||||||
}
|
}
|
||||||
|
|
||||||
$camelized = $this->camelize($property);
|
$access = $this->getReadAccessInfo($object, $property);
|
||||||
$reflClass = new \ReflectionClass($object);
|
|
||||||
$getter = 'get'.$camelized;
|
|
||||||
$getsetter = lcfirst($camelized); // jQuery style, e.g. read: last(), write: last($item)
|
|
||||||
$isser = 'is'.$camelized;
|
|
||||||
$hasser = 'has'.$camelized;
|
|
||||||
$classHasProperty = $reflClass->hasProperty($property);
|
|
||||||
|
|
||||||
if ($reflClass->hasMethod($getter) && $reflClass->getMethod($getter)->isPublic()) {
|
if (self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]) {
|
||||||
$result[self::VALUE] = $object->$getter();
|
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
|
||||||
} elseif ($this->isMethodAccessible($reflClass, $getsetter, 0)) {
|
} elseif (self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]) {
|
||||||
$result[self::VALUE] = $object->$getsetter();
|
if ($access[self::ACCESS_REF]) {
|
||||||
} elseif ($reflClass->hasMethod($isser) && $reflClass->getMethod($isser)->isPublic()) {
|
$result[self::VALUE] = &$object->{$access[self::ACCESS_NAME]};
|
||||||
$result[self::VALUE] = $object->$isser();
|
$result[self::IS_REF] = true;
|
||||||
} elseif ($reflClass->hasMethod($hasser) && $reflClass->getMethod($hasser)->isPublic()) {
|
} else {
|
||||||
$result[self::VALUE] = $object->$hasser();
|
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]};
|
||||||
} elseif ($classHasProperty && $reflClass->getProperty($property)->isPublic()) {
|
}
|
||||||
$result[self::VALUE] = &$object->$property;
|
} elseif (!$access[self::ACCESS_HAS_PROPERTY] && property_exists($object, $property)) {
|
||||||
$result[self::IS_REF] = true;
|
|
||||||
} elseif ($reflClass->hasMethod('__get') && $reflClass->getMethod('__get')->isPublic()) {
|
|
||||||
$result[self::VALUE] = $object->$property;
|
|
||||||
} elseif (!$classHasProperty && property_exists($object, $property)) {
|
|
||||||
// Needed to support \stdClass instances. We need to explicitly
|
// Needed to support \stdClass instances. We need to explicitly
|
||||||
// exclude $classHasProperty, otherwise if in the previous clause
|
// exclude $classHasProperty, otherwise if in the previous clause
|
||||||
// a *protected* property was found on the class, property_exists()
|
// a *protected* property was found on the class, property_exists()
|
||||||
// returns true, consequently the following line will result in a
|
// returns true, consequently the following line will result in a
|
||||||
// fatal error.
|
// fatal error.
|
||||||
|
|
||||||
$result[self::VALUE] = &$object->$property;
|
$result[self::VALUE] = &$object->$property;
|
||||||
$result[self::IS_REF] = true;
|
$result[self::IS_REF] = true;
|
||||||
} elseif ($this->magicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->isPublic()) {
|
} elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) {
|
||||||
// we call the getter and hope the __call do the job
|
// we call the getter and hope the __call do the job
|
||||||
$result[self::VALUE] = $object->$getter();
|
$result[self::VALUE] = $object->{$access[self::ACCESS_NAME]}();
|
||||||
} else {
|
} else {
|
||||||
$methods = array($getter, $getsetter, $isser, $hasser, '__get');
|
throw new NoSuchPropertyException($access[self::ACCESS_NAME]);
|
||||||
if ($this->magicCall) {
|
|
||||||
$methods[] = '__call';
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new NoSuchPropertyException(sprintf(
|
|
||||||
'Neither the property "%s" nor one of the methods "%s()" '.
|
|
||||||
'exist and have public access in class "%s".',
|
|
||||||
$property,
|
|
||||||
implode('()", "', $methods),
|
|
||||||
$reflClass->name
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Objects are always passed around by reference
|
// Objects are always passed around by reference
|
||||||
@ -385,6 +387,81 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guesses how to read the property value.
|
||||||
|
*
|
||||||
|
* @param string $object
|
||||||
|
* @param string $property
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getReadAccessInfo($object, $property)
|
||||||
|
{
|
||||||
|
$key = get_class($object).'::'.$property;
|
||||||
|
|
||||||
|
if (isset($this->readPropertyCache[$key])) {
|
||||||
|
$access = $this->readPropertyCache[$key];
|
||||||
|
} else {
|
||||||
|
$access = array();
|
||||||
|
|
||||||
|
$reflClass = new \ReflectionClass($object);
|
||||||
|
$access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
|
||||||
|
$camelProp = $this->camelize($property);
|
||||||
|
$getter = 'get'.$camelProp;
|
||||||
|
$getsetter = lcfirst($camelProp); // jQuery style, e.g. read: last(), write: last($item)
|
||||||
|
$isser = 'is'.$camelProp;
|
||||||
|
$hasser = 'has'.$camelProp;
|
||||||
|
$classHasProperty = $reflClass->hasProperty($property);
|
||||||
|
|
||||||
|
if ($reflClass->hasMethod($getter) && $reflClass->getMethod($getter)->isPublic()) {
|
||||||
|
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
|
||||||
|
$access[self::ACCESS_NAME] = $getter;
|
||||||
|
} elseif ($reflClass->hasMethod($getsetter) && $reflClass->getMethod($getsetter)->isPublic()) {
|
||||||
|
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
|
||||||
|
$access[self::ACCESS_NAME] = $getsetter;
|
||||||
|
} elseif ($reflClass->hasMethod($isser) && $reflClass->getMethod($isser)->isPublic()) {
|
||||||
|
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
|
||||||
|
$access[self::ACCESS_NAME] = $isser;
|
||||||
|
} elseif ($reflClass->hasMethod($hasser) && $reflClass->getMethod($hasser)->isPublic()) {
|
||||||
|
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
|
||||||
|
$access[self::ACCESS_NAME] = $hasser;
|
||||||
|
} elseif ($reflClass->hasMethod('__get') && $reflClass->getMethod('__get')->isPublic()) {
|
||||||
|
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
|
||||||
|
$access[self::ACCESS_NAME] = $property;
|
||||||
|
$access[self::ACCESS_REF] = false;
|
||||||
|
} elseif ($classHasProperty && $reflClass->getProperty($property)->isPublic()) {
|
||||||
|
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
|
||||||
|
$access[self::ACCESS_NAME] = $property;
|
||||||
|
$access[self::ACCESS_REF] = true;
|
||||||
|
|
||||||
|
$result[self::VALUE] = &$object->$property;
|
||||||
|
$result[self::IS_REF] = true;
|
||||||
|
} elseif ($this->magicCall && $reflClass->hasMethod('__call') && $reflClass->getMethod('__call')->isPublic()) {
|
||||||
|
// we call the getter and hope the __call do the job
|
||||||
|
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_MAGIC;
|
||||||
|
$access[self::ACCESS_NAME] = $getter;
|
||||||
|
} else {
|
||||||
|
$methods = array($getter, $getsetter, $isser, $hasser, '__get');
|
||||||
|
if ($this->magicCall) {
|
||||||
|
$methods[] = '__call';
|
||||||
|
}
|
||||||
|
|
||||||
|
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
|
||||||
|
$access[self::ACCESS_NAME] = sprintf(
|
||||||
|
'Neither the property "%s" nor one of the methods "%s()" '.
|
||||||
|
'exist and have public access in class "%s".',
|
||||||
|
$property,
|
||||||
|
implode('()", "', $methods),
|
||||||
|
$reflClass->name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->readPropertyCache[$key] = $access;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $access;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the value of an index in a given array-accessible value.
|
* Sets the value of an index in a given array-accessible value.
|
||||||
*
|
*
|
||||||
@ -419,55 +496,26 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
throw new NoSuchPropertyException(sprintf('Cannot write property "%s" to an array. Maybe you should write the property path as "[%s]" instead?', $property, $property));
|
throw new NoSuchPropertyException(sprintf('Cannot write property "%s" to an array. Maybe you should write the property path as "[%s]" instead?', $property, $property));
|
||||||
}
|
}
|
||||||
|
|
||||||
$reflClass = new \ReflectionClass($object);
|
$access = $this->getWriteAccessInfo($object, $property, $value);
|
||||||
$camelized = $this->camelize($property);
|
|
||||||
$singulars = (array) StringUtil::singularify($camelized);
|
|
||||||
|
|
||||||
if (is_array($value) || $value instanceof \Traversable) {
|
if (self::ACCESS_TYPE_METHOD === $access[self::ACCESS_TYPE]) {
|
||||||
$methods = $this->findAdderAndRemover($reflClass, $singulars);
|
$object->{$access[self::ACCESS_NAME]}($value);
|
||||||
|
} elseif (self::ACCESS_TYPE_PROPERTY === $access[self::ACCESS_TYPE]) {
|
||||||
// Use addXxx() and removeXxx() to write the collection
|
$object->{$access[self::ACCESS_NAME]} = $value;
|
||||||
if (null !== $methods) {
|
} elseif (self::ACCESS_TYPE_ADDER_AND_REMOVER === $access[self::ACCESS_TYPE]) {
|
||||||
$this->writeCollection($object, $property, $value, $methods[0], $methods[1]);
|
$this->writeCollection($object, $property, $value, $access[self::ACCESS_ADDER], $access[self::ACCESS_REMOVER]);
|
||||||
|
} elseif (!$access[self::ACCESS_HAS_PROPERTY] && property_exists($object, $property)) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$setter = 'set'.$camelized;
|
|
||||||
$getsetter = lcfirst($camelized); // jQuery style, e.g. read: last(), write: last($item)
|
|
||||||
$classHasProperty = $reflClass->hasProperty($property);
|
|
||||||
|
|
||||||
if ($this->isMethodAccessible($reflClass, $setter, 1)) {
|
|
||||||
$object->$setter($value);
|
|
||||||
} elseif ($this->isMethodAccessible($reflClass, $getsetter, 1)) {
|
|
||||||
$object->$getsetter($value);
|
|
||||||
} elseif ($classHasProperty && $reflClass->getProperty($property)->isPublic()) {
|
|
||||||
$object->$property = $value;
|
|
||||||
} elseif ($this->isMethodAccessible($reflClass, '__set', 2)) {
|
|
||||||
$object->$property = $value;
|
|
||||||
} elseif (!$classHasProperty && property_exists($object, $property)) {
|
|
||||||
// Needed to support \stdClass instances. We need to explicitly
|
// Needed to support \stdClass instances. We need to explicitly
|
||||||
// exclude $classHasProperty, otherwise if in the previous clause
|
// exclude $classHasProperty, otherwise if in the previous clause
|
||||||
// a *protected* property was found on the class, property_exists()
|
// a *protected* property was found on the class, property_exists()
|
||||||
// returns true, consequently the following line will result in a
|
// returns true, consequently the following line will result in a
|
||||||
// fatal error.
|
// fatal error.
|
||||||
|
|
||||||
$object->$property = $value;
|
$object->$property = $value;
|
||||||
} elseif ($this->magicCall && $this->isMethodAccessible($reflClass, '__call', 2)) {
|
} elseif (self::ACCESS_TYPE_MAGIC === $access[self::ACCESS_TYPE]) {
|
||||||
// we call the getter and hope the __call do the job
|
$object->{$access[self::ACCESS_NAME]}($value);
|
||||||
$object->$setter($value);
|
|
||||||
} else {
|
} else {
|
||||||
throw new NoSuchPropertyException(sprintf(
|
throw new NoSuchPropertyException($access[self::ACCESS_NAME]);
|
||||||
'Neither the property "%s" nor one of the methods %s"%s()", "%s()", '.
|
|
||||||
'"__set()" or "__call()" exist and have public access in class "%s".',
|
|
||||||
$property,
|
|
||||||
implode('', array_map(function ($singular) {
|
|
||||||
return '"add'.$singular.'()"/"remove'.$singular.'()", ';
|
|
||||||
}, $singulars)),
|
|
||||||
$setter,
|
|
||||||
$getsetter,
|
|
||||||
$reflClass->name
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,6 +567,90 @@ class PropertyAccessor implements PropertyAccessorInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guesses how to write the property value.
|
||||||
|
*
|
||||||
|
* @param string $object
|
||||||
|
* @param string $property
|
||||||
|
* @param mixed $value
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getWriteAccessInfo($object, $property, $value)
|
||||||
|
{
|
||||||
|
$key = get_class($object).'::'.$property;
|
||||||
|
$guessedAdders = '';
|
||||||
|
|
||||||
|
if (isset($this->writePropertyCache[$key])) {
|
||||||
|
$access = $this->writePropertyCache[$key];
|
||||||
|
} else {
|
||||||
|
$access = array();
|
||||||
|
|
||||||
|
$reflClass = new \ReflectionClass($object);
|
||||||
|
$access[self::ACCESS_HAS_PROPERTY] = $reflClass->hasProperty($property);
|
||||||
|
$camelized = $this->camelize($property);
|
||||||
|
$singulars = (array) StringUtil::singularify($camelized);
|
||||||
|
|
||||||
|
if (is_array($value) || $value instanceof \Traversable) {
|
||||||
|
$methods = $this->findAdderAndRemover($reflClass, $singulars);
|
||||||
|
|
||||||
|
if (null === $methods) {
|
||||||
|
// It is sufficient to include only the adders in the error
|
||||||
|
// message. If the user implements the adder but not the remover,
|
||||||
|
// an exception will be thrown in findAdderAndRemover() that
|
||||||
|
// the remover has to be implemented as well.
|
||||||
|
$guessedAdders = '"add'.implode('()", "add', $singulars).'()", ';
|
||||||
|
} else {
|
||||||
|
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_ADDER_AND_REMOVER;
|
||||||
|
$access[self::ACCESS_ADDER] = $methods[0];
|
||||||
|
$access[self::ACCESS_REMOVER] = $methods[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($access[self::ACCESS_TYPE])) {
|
||||||
|
$setter = 'set'.$this->camelize($property);
|
||||||
|
$getsetter = lcfirst($camelized); // jQuery style, e.g. read: last(), write: last($item)
|
||||||
|
|
||||||
|
$classHasProperty = $reflClass->hasProperty($property);
|
||||||
|
|
||||||
|
if ($this->isMethodAccessible($reflClass, $setter, 1)) {
|
||||||
|
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
|
||||||
|
$access[self::ACCESS_NAME] = $setter;
|
||||||
|
} elseif ($this->isMethodAccessible($reflClass, $getsetter, 1)) {
|
||||||
|
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_METHOD;
|
||||||
|
$access[self::ACCESS_NAME] = $getsetter;
|
||||||
|
} elseif ($this->isMethodAccessible($reflClass, '__set', 2)) {
|
||||||
|
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
|
||||||
|
$access[self::ACCESS_NAME] = $property;
|
||||||
|
} elseif ($classHasProperty && $reflClass->getProperty($property)->isPublic()) {
|
||||||
|
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_PROPERTY;
|
||||||
|
$access[self::ACCESS_NAME] = $property;
|
||||||
|
} elseif ($this->magicCall && $this->isMethodAccessible($reflClass, '__call', 2)) {
|
||||||
|
// we call the getter and hope the __call do the job
|
||||||
|
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_MAGIC;
|
||||||
|
$access[self::ACCESS_NAME] = $setter;
|
||||||
|
} else {
|
||||||
|
$access[self::ACCESS_TYPE] = self::ACCESS_TYPE_NOT_FOUND;
|
||||||
|
$access[self::ACCESS_NAME] = sprintf(
|
||||||
|
'Neither the property "%s" nor one of the methods %s"%s()", "%s()", '.
|
||||||
|
'"__set()" or "__call()" exist and have public access in class "%s".',
|
||||||
|
$property,
|
||||||
|
implode('', array_map(function ($singular) {
|
||||||
|
return '"add'.$singular.'()"/"remove'.$singular.'()", ';
|
||||||
|
}, $singulars)),
|
||||||
|
$setter,
|
||||||
|
$getsetter,
|
||||||
|
$reflClass->name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->writePropertyCache[$key] = $access;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $access;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether a property is writable in the given object.
|
* Returns whether a property is writable in the given object.
|
||||||
*
|
*
|
||||||
|
@ -114,7 +114,7 @@ class GetSetMethodNormalizer extends AbstractNormalizer
|
|||||||
if ($allowed && !$ignored) {
|
if ($allowed && !$ignored) {
|
||||||
$setter = 'set'.ucfirst($attribute);
|
$setter = 'set'.ucfirst($attribute);
|
||||||
|
|
||||||
if (in_array($setter, $classMethods)) {
|
if (in_array($setter, $classMethods) && !$reflectionClass->getMethod($setter)->isStatic()) {
|
||||||
$object->$setter($value);
|
$object->$setter($value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,10 +170,13 @@ class GetSetMethodNormalizer extends AbstractNormalizer
|
|||||||
{
|
{
|
||||||
$methodLength = strlen($method->name);
|
$methodLength = strlen($method->name);
|
||||||
|
|
||||||
return (
|
return
|
||||||
((0 === strpos($method->name, 'get') && 3 < $methodLength) ||
|
!$method->isStatic() &&
|
||||||
(0 === strpos($method->name, 'is') && 2 < $methodLength)) &&
|
(
|
||||||
0 === $method->getNumberOfRequiredParameters()
|
((0 === strpos($method->name, 'get') && 3 < $methodLength) ||
|
||||||
);
|
(0 === strpos($method->name, 'is') && 2 < $methodLength)) &&
|
||||||
|
0 === $method->getNumberOfRequiredParameters()
|
||||||
|
)
|
||||||
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ class PropertyNormalizer extends AbstractNormalizer
|
|||||||
$allowedAttributes = $this->getAllowedAttributes($object, $context, true);
|
$allowedAttributes = $this->getAllowedAttributes($object, $context, true);
|
||||||
|
|
||||||
foreach ($reflectionObject->getProperties() as $property) {
|
foreach ($reflectionObject->getProperties() as $property) {
|
||||||
if (in_array($property->name, $this->ignoredAttributes)) {
|
if (in_array($property->name, $this->ignoredAttributes) || $property->isStatic()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,6 +110,10 @@ class PropertyNormalizer extends AbstractNormalizer
|
|||||||
if ($allowed && !$ignored && $reflectionClass->hasProperty($propertyName)) {
|
if ($allowed && !$ignored && $reflectionClass->hasProperty($propertyName)) {
|
||||||
$property = $reflectionClass->getProperty($propertyName);
|
$property = $reflectionClass->getProperty($propertyName);
|
||||||
|
|
||||||
|
if ($property->isStatic()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Override visibility
|
// Override visibility
|
||||||
if (!$property->isPublic()) {
|
if (!$property->isPublic()) {
|
||||||
$property->setAccessible(true);
|
$property->setAccessible(true);
|
||||||
|
@ -549,11 +549,24 @@ class GetSetMethodNormalizerTest extends \PHPUnit_Framework_TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDenormalizeShouldNotSetStaticAttribute()
|
||||||
|
{
|
||||||
|
$obj = $this->normalizer->denormalize(array('staticObject' => true), __NAMESPACE__.'\GetSetDummy');
|
||||||
|
|
||||||
|
$this->assertEquals(new GetSetDummy(), $obj);
|
||||||
|
$this->assertNull(GetSetDummy::getStaticObject());
|
||||||
|
}
|
||||||
|
|
||||||
public function testNoTraversableSupport()
|
public function testNoTraversableSupport()
|
||||||
{
|
{
|
||||||
$this->assertFalse($this->normalizer->supportsNormalization(new \ArrayObject()));
|
$this->assertFalse($this->normalizer->supportsNormalization(new \ArrayObject()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testNoStaticGetSetSupport()
|
||||||
|
{
|
||||||
|
$this->assertFalse($this->normalizer->supportsNormalization(new ObjectWithJustStaticSetterDummy()));
|
||||||
|
}
|
||||||
|
|
||||||
public function testPrivateSetter()
|
public function testPrivateSetter()
|
||||||
{
|
{
|
||||||
$obj = $this->normalizer->denormalize(array('foo' => 'foobar'), __NAMESPACE__.'\ObjectWithPrivateSetterDummy');
|
$obj = $this->normalizer->denormalize(array('foo' => 'foobar'), __NAMESPACE__.'\ObjectWithPrivateSetterDummy');
|
||||||
@ -568,6 +581,7 @@ class GetSetDummy
|
|||||||
private $baz;
|
private $baz;
|
||||||
protected $camelCase;
|
protected $camelCase;
|
||||||
protected $object;
|
protected $object;
|
||||||
|
private static $staticObject;
|
||||||
|
|
||||||
public function getFoo()
|
public function getFoo()
|
||||||
{
|
{
|
||||||
@ -628,6 +642,16 @@ class GetSetDummy
|
|||||||
{
|
{
|
||||||
return $this->object;
|
return $this->object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getStaticObject()
|
||||||
|
{
|
||||||
|
return self::$staticObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function setStaticObject($object)
|
||||||
|
{
|
||||||
|
self::$staticObject = $object;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GetConstructorDummy
|
class GetConstructorDummy
|
||||||
@ -799,3 +823,18 @@ class ObjectWithPrivateSetterDummy
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ObjectWithJustStaticSetterDummy
|
||||||
|
{
|
||||||
|
private static $foo = 'bar';
|
||||||
|
|
||||||
|
public static function getFoo()
|
||||||
|
{
|
||||||
|
return self::$foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function setFoo($foo)
|
||||||
|
{
|
||||||
|
self::$foo = $foo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -409,6 +409,14 @@ class PropertyNormalizerTest extends \PHPUnit_Framework_TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDenormalizeShouldIgnoreStaticProperty()
|
||||||
|
{
|
||||||
|
$obj = $this->normalizer->denormalize(array('outOfScope' => true), __NAMESPACE__.'\PropertyDummy');
|
||||||
|
|
||||||
|
$this->assertEquals(new PropertyDummy(), $obj);
|
||||||
|
$this->assertEquals('out_of_scope', PropertyDummy::$outOfScope);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\Serializer\Exception\LogicException
|
* @expectedException \Symfony\Component\Serializer\Exception\LogicException
|
||||||
* @expectedExceptionMessage Cannot normalize attribute "bar" because injected serializer is not a normalizer
|
* @expectedExceptionMessage Cannot normalize attribute "bar" because injected serializer is not a normalizer
|
||||||
@ -429,10 +437,16 @@ class PropertyNormalizerTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
$this->assertFalse($this->normalizer->supportsNormalization(new \ArrayObject()));
|
$this->assertFalse($this->normalizer->supportsNormalization(new \ArrayObject()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testNoStaticPropertySupport()
|
||||||
|
{
|
||||||
|
$this->assertFalse($this->normalizer->supportsNormalization(new StaticPropertyDummy()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PropertyDummy
|
class PropertyDummy
|
||||||
{
|
{
|
||||||
|
public static $outOfScope = 'out_of_scope';
|
||||||
public $foo;
|
public $foo;
|
||||||
private $bar;
|
private $bar;
|
||||||
protected $camelCase;
|
protected $camelCase;
|
||||||
@ -491,3 +505,9 @@ class PropertyCamelizedDummy
|
|||||||
$this->kevinDunglas = $kevinDunglas;
|
$this->kevinDunglas = $kevinDunglas;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class StaticPropertyDummy
|
||||||
|
{
|
||||||
|
private static $property = 'value';
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -227,7 +227,11 @@ class ReflectionCaster
|
|||||||
));
|
));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($c->isArray()) {
|
if (method_exists($c, 'hasType')) {
|
||||||
|
if ($c->hasType()) {
|
||||||
|
$a[$prefix.'typeHint'] = $c->getType()->__toString();
|
||||||
|
}
|
||||||
|
} elseif ($c->isArray()) {
|
||||||
$a[$prefix.'typeHint'] = 'array';
|
$a[$prefix.'typeHint'] = 'array';
|
||||||
} elseif (method_exists($c, 'isCallable') && $c->isCallable()) {
|
} elseif (method_exists($c, 'isCallable') && $c->isCallable()) {
|
||||||
$a[$prefix.'typeHint'] = 'callable';
|
$a[$prefix.'typeHint'] = 'callable';
|
||||||
|
@ -100,17 +100,38 @@ EOTXT
|
|||||||
/**
|
/**
|
||||||
* @requires PHP 7.0
|
* @requires PHP 7.0
|
||||||
*/
|
*/
|
||||||
public function testReturnType()
|
public function testReflectionParameterScalar()
|
||||||
{
|
{
|
||||||
$f = eval('return function ():int {};');
|
$f = eval('return function (int $a) {};');
|
||||||
|
$var = new \ReflectionParameter($f, 0);
|
||||||
|
|
||||||
$this->assertDumpMatchesFormat(
|
$this->assertDumpMatchesFormat(
|
||||||
<<<'EOTXT'
|
<<<'EOTXT'
|
||||||
|
ReflectionParameter {
|
||||||
|
+name: "a"
|
||||||
|
position: 0
|
||||||
|
typeHint: "int"
|
||||||
|
}
|
||||||
|
EOTXT
|
||||||
|
, $var
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires PHP 7.0
|
||||||
|
*/
|
||||||
|
public function testReturnType()
|
||||||
|
{
|
||||||
|
$f = eval('return function ():int {};');
|
||||||
|
$line = __LINE__ - 1;
|
||||||
|
|
||||||
|
$this->assertDumpMatchesFormat(
|
||||||
|
<<<EOTXT
|
||||||
Closure {
|
Closure {
|
||||||
returnType: "int"
|
returnType: "int"
|
||||||
class: "Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest"
|
class: "Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest"
|
||||||
this: Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest { …}
|
this: Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest { …}
|
||||||
file: "%sReflectionCasterTest.php(105) : eval()'d code"
|
file: "%sReflectionCasterTest.php($line) : eval()'d code"
|
||||||
line: "1 to 1"
|
line: "1 to 1"
|
||||||
}
|
}
|
||||||
EOTXT
|
EOTXT
|
||||||
|
Reference in New Issue
Block a user