[PropertyAccess] Extracted PropertyAccess component out of Form
This commit is contained in:
parent
b981a6fa60
commit
1bae7b242c
128
UPGRADE-2.2.md
128
UPGRADE-2.2.md
@ -84,6 +84,46 @@
|
||||
{{ error.message }}
|
||||
```
|
||||
|
||||
* FormType, ModelType and PropertyPathMapper now have constructors. If you
|
||||
extended these classes, you should call the parent constructor now.
|
||||
Note that you are not recommended to extend FormType nor ModelType. You should
|
||||
extend AbstractType instead and use the Form component's own inheritance
|
||||
mechanism (`AbstractType::getParent()`).
|
||||
|
||||
Before:
|
||||
|
||||
```
|
||||
use Symfony\Component\Form\Extensions\Core\DataMapper\PropertyPathMapper;
|
||||
|
||||
class CustomMapper extends PropertyPathMapper
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```
|
||||
use Symfony\Component\Form\Extensions\Core\DataMapper\PropertyPathMapper;
|
||||
|
||||
class CustomMapper extends PropertyPathMapper
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Deprecations
|
||||
|
||||
* The methods `getParent()`, `setParent()` and `hasParent()` in
|
||||
@ -91,6 +131,94 @@
|
||||
You should not rely on these methods in your form type because the parent
|
||||
of a form can change after building it.
|
||||
|
||||
* The class PropertyPath and related classes were deprecated and moved to a
|
||||
dedicated component PropertyAccess. If you used any of these classes or
|
||||
interfaces, you should adapt the namespaces now. During the move,
|
||||
InvalidPropertyException was renamed to NoSuchPropertyException.
|
||||
|
||||
Before:
|
||||
|
||||
```
|
||||
use Symfony\Component\Form\Util\PropertyPath;
|
||||
use Symfony\Component\Form\Util\PropertyPathBuilder;
|
||||
use Symfony\Component\Form\Util\PropertyPathInterface;
|
||||
use Symfony\Component\Form\Util\PropertyPathIterator;
|
||||
use Symfony\Component\Form\Util\PropertyPathIteratorInterface;
|
||||
use Symfony\Component\Form\Exception\InvalidPropertyException;
|
||||
use Symfony\Component\Form\Exception\InvalidPropertyPathException;
|
||||
use Symfony\Component\Form\Exception\PropertyAccessDeniedException;
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```
|
||||
use Symfony\Component\PropertyAccess\PropertyPath;
|
||||
use Symfony\Component\PropertyAccess\PropertyPathBuilder;
|
||||
use Symfony\Component\PropertyAccess\PropertyPathInterface;
|
||||
use Symfony\Component\PropertyAccess\PropertyPathIterator;
|
||||
use Symfony\Component\PropertyAccess\PropertyPathIteratorInterface;
|
||||
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
|
||||
use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException;
|
||||
use Symfony\Component\PropertyAccess\Exception\PropertyAccessDeniedException;
|
||||
```
|
||||
|
||||
Also, `FormUtil::singularify()` was split away into a class StringUtil
|
||||
in the new component.
|
||||
|
||||
Before:
|
||||
|
||||
```
|
||||
use Symfony\Component\Form\Util\FormUtil;
|
||||
|
||||
$singular = FormUtil::singularify($plural);
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```
|
||||
use Symfony\Component\PropertyAccess\StringUtil;
|
||||
|
||||
$singular = StringUtil::singularify($plural);
|
||||
```
|
||||
|
||||
The methods `getValue()` and `setValue()` were moved to a new class
|
||||
PropertyAccessor.
|
||||
|
||||
Before:
|
||||
|
||||
```
|
||||
use Symfony\Component\Form\Util\PropertyPath;
|
||||
|
||||
$propertyPath = new PropertyPath('some.path');
|
||||
|
||||
$value = $propertyPath->getValue($object);
|
||||
$propertyPath->setValue($object, 'new value');
|
||||
```
|
||||
|
||||
After (alternative 1):
|
||||
|
||||
```
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
|
||||
$accessor = PropertyAccess::getPropertyAccessor();
|
||||
|
||||
$value = $propertyAccessor->getValue($object, 'some.path');
|
||||
$accessor->setValue($object, 'some.path', 'new value');
|
||||
```
|
||||
|
||||
After (alternative 2):
|
||||
|
||||
```
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyPath;
|
||||
|
||||
$accessor = PropertyAccess::getPropertyAccessor();
|
||||
$propertyPath = new PropertyPath('some.path');
|
||||
|
||||
$value = $propertyAccessor->getValue($object, $propertyPath);
|
||||
$accessor->setValue($object, $propertyPath, 'new value');
|
||||
```
|
||||
|
||||
### Routing
|
||||
|
||||
* RouteCollection does not behave like a tree structure anymore but as a flat
|
||||
|
@ -1,6 +1,12 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
2.2.0
|
||||
-----
|
||||
|
||||
* added an optional PropertyAccessorInterface parameter to DoctrineType,
|
||||
EntityType and EntityChoiceList
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
||||
|
@ -15,6 +15,7 @@ use Symfony\Component\Form\Exception\Exception;
|
||||
use Symfony\Component\Form\Exception\StringCastException;
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
|
||||
/**
|
||||
* A choice list presenting a list of Doctrine entities as choices
|
||||
@ -86,17 +87,18 @@ class EntityChoiceList extends ObjectChoiceList
|
||||
/**
|
||||
* Creates a new entity choice list.
|
||||
*
|
||||
* @param ObjectManager $manager An EntityManager instance
|
||||
* @param string $class The class name
|
||||
* @param string $labelPath The property path used for the label
|
||||
* @param EntityLoaderInterface $entityLoader An optional query builder
|
||||
* @param array $entities An array of choices
|
||||
* @param array $preferredEntities An array of preferred choices
|
||||
* @param string $groupPath A property path pointing to the property used
|
||||
* to group the choices. Only allowed if
|
||||
* the choices are given as flat array.
|
||||
* @param ObjectManager $manager An EntityManager instance
|
||||
* @param string $class The class name
|
||||
* @param string $labelPath The property path used for the label
|
||||
* @param EntityLoaderInterface $entityLoader An optional query builder
|
||||
* @param array $entities An array of choices
|
||||
* @param array $preferredEntities An array of preferred choices
|
||||
* @param string $groupPath A property path pointing to the property used
|
||||
* to group the choices. Only allowed if
|
||||
* the choices are given as flat array.
|
||||
* @param PropertyAccessorInterface $propertyAccessor The reflection graph for reading property paths.
|
||||
*/
|
||||
public function __construct(ObjectManager $manager, $class, $labelPath = null, EntityLoaderInterface $entityLoader = null, $entities = null, array $preferredEntities = array(), $groupPath = null)
|
||||
public function __construct(ObjectManager $manager, $class, $labelPath = null, EntityLoaderInterface $entityLoader = null, $entities = null, array $preferredEntities = array(), $groupPath = null, PropertyAccessorInterface $propertyAccessor = null)
|
||||
{
|
||||
$this->em = $manager;
|
||||
$this->entityLoader = $entityLoader;
|
||||
@ -122,7 +124,7 @@ class EntityChoiceList extends ObjectChoiceList
|
||||
$entities = array();
|
||||
}
|
||||
|
||||
parent::__construct($entities, $labelPath, $preferredEntities, $groupPath);
|
||||
parent::__construct($entities, $labelPath, $preferredEntities, $groupPath, null, $propertyAccessor);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -13,6 +13,7 @@ namespace Symfony\Bridge\Doctrine\Form;
|
||||
|
||||
use Doctrine\Common\Persistence\ManagerRegistry;
|
||||
use Symfony\Component\Form\AbstractExtension;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
|
||||
class DoctrineOrmExtension extends AbstractExtension
|
||||
{
|
||||
@ -26,7 +27,7 @@ class DoctrineOrmExtension extends AbstractExtension
|
||||
protected function loadTypes()
|
||||
{
|
||||
return array(
|
||||
new Type\EntityType($this->registry),
|
||||
new Type\EntityType($this->registry, PropertyAccess::getPropertyAccessor()),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,8 @@ use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
|
||||
abstract class DoctrineType extends AbstractType
|
||||
{
|
||||
@ -35,9 +37,15 @@ abstract class DoctrineType extends AbstractType
|
||||
*/
|
||||
private $choiceListCache = array();
|
||||
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
/**
|
||||
* @var PropertyAccessorInterface
|
||||
*/
|
||||
private $propertyAccessor;
|
||||
|
||||
public function __construct(ManagerRegistry $registry, PropertyAccessorInterface $propertyAccessor = null)
|
||||
{
|
||||
$this->registry = $registry;
|
||||
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::getPropertyAccessor();
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
@ -54,6 +62,7 @@ abstract class DoctrineType extends AbstractType
|
||||
{
|
||||
$choiceListCache =& $this->choiceListCache;
|
||||
$registry = $this->registry;
|
||||
$propertyAccessor = $this->propertyAccessor;
|
||||
$type = $this;
|
||||
|
||||
$loader = function (Options $options) use ($type) {
|
||||
@ -64,7 +73,7 @@ abstract class DoctrineType extends AbstractType
|
||||
return null;
|
||||
};
|
||||
|
||||
$choiceList = function (Options $options) use (&$choiceListCache, &$time) {
|
||||
$choiceList = function (Options $options) use (&$choiceListCache, $propertyAccessor) {
|
||||
// Support for closures
|
||||
$propertyHash = is_object($options['property'])
|
||||
? spl_object_hash($options['property'])
|
||||
@ -118,7 +127,8 @@ abstract class DoctrineType extends AbstractType
|
||||
$options['loader'],
|
||||
$options['choices'],
|
||||
$options['preferred_choices'],
|
||||
$options['group_by']
|
||||
$options['group_by'],
|
||||
$propertyAccessor
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -90,6 +90,7 @@ class EntityTypeTest extends TypeTestCase
|
||||
parent::tearDown();
|
||||
|
||||
$this->em = null;
|
||||
$this->emRegistry = null;
|
||||
}
|
||||
|
||||
protected function getExtensions()
|
||||
|
@ -5,6 +5,9 @@ CHANGELOG
|
||||
-----
|
||||
|
||||
* added a collection type for the I18n behavior
|
||||
* added an optional PropertyAccessorInterface parameter to ModelType and
|
||||
ModelChoiceList
|
||||
* [BC BREAK] ModelType now has a constructor
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
@ -18,6 +18,7 @@ use \Persistent;
|
||||
use Symfony\Component\Form\Exception\FormException;
|
||||
use Symfony\Component\Form\Exception\StringCastException;
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
|
||||
/**
|
||||
* Widely inspired by the EntityChoiceList.
|
||||
@ -69,16 +70,17 @@ class ModelChoiceList extends ObjectChoiceList
|
||||
*
|
||||
* @see Symfony\Bridge\Propel1\Form\Type\ModelType How to use the preferred choices.
|
||||
*
|
||||
* @param string $class The FQCN of the model class to be loaded.
|
||||
* @param string $labelPath A property path pointing to the property used for the choice labels.
|
||||
* @param array $choices An optional array to use, rather than fetching the models.
|
||||
* @param ModelCriteria $queryObject The query to use retrieving model data from database.
|
||||
* @param string $groupPath A property path pointing to the property used to group the choices.
|
||||
* @param array|ModelCriteria $preferred The preferred items of this choice.
|
||||
* Either an array if $choices is given,
|
||||
* or a ModelCriteria to be merged with the $queryObject.
|
||||
* @param string $class The FQCN of the model class to be loaded.
|
||||
* @param string $labelPath A property path pointing to the property used for the choice labels.
|
||||
* @param array $choices An optional array to use, rather than fetching the models.
|
||||
* @param ModelCriteria $queryObject The query to use retrieving model data from database.
|
||||
* @param string $groupPath A property path pointing to the property used to group the choices.
|
||||
* @param array|ModelCriteria $preferred The preferred items of this choice.
|
||||
* Either an array if $choices is given,
|
||||
* or a ModelCriteria to be merged with the $queryObject.
|
||||
* @param PropertyAccessorInterface $propertyAccessor The reflection graph for reading property paths.
|
||||
*/
|
||||
public function __construct($class, $labelPath = null, $choices = null, $queryObject = null, $groupPath = null, $preferred = array())
|
||||
public function __construct($class, $labelPath = null, $choices = null, $queryObject = null, $groupPath = null, $preferred = array(), PropertyAccessorInterface $propertyAccessor = null)
|
||||
{
|
||||
$this->class = $class;
|
||||
|
||||
@ -104,7 +106,7 @@ class ModelChoiceList extends ObjectChoiceList
|
||||
$this->identifierAsIndex = true;
|
||||
}
|
||||
|
||||
parent::__construct($choices, $labelPath, $preferred, $groupPath);
|
||||
parent::__construct($choices, $labelPath, $preferred, $groupPath, null, $propertyAccessor);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Bridge\Propel1\Form;
|
||||
|
||||
use Symfony\Component\Form\AbstractExtension;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
|
||||
/**
|
||||
* Represents the Propel form extension, which loads the Propel functionality.
|
||||
@ -23,7 +24,7 @@ class PropelExtension extends AbstractExtension
|
||||
protected function loadTypes()
|
||||
{
|
||||
return array(
|
||||
new Type\ModelType(),
|
||||
new Type\ModelType(PropertyAccess::getPropertyAccessor()),
|
||||
new Type\TranslationCollectionType(),
|
||||
new Type\TranslationType()
|
||||
);
|
||||
|
@ -17,6 +17,8 @@ use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
|
||||
/**
|
||||
* ModelType class.
|
||||
@ -48,6 +50,16 @@ use Symfony\Component\OptionsResolver\OptionsResolverInterface;
|
||||
*/
|
||||
class ModelType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @var PropertyAccessorInterface
|
||||
*/
|
||||
private $propertyAccessor;
|
||||
|
||||
public function __construct(PropertyAccessorInterface $propertyAccessor = null)
|
||||
{
|
||||
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::getPropertyAccessor();
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
if ($options['multiple']) {
|
||||
@ -57,14 +69,17 @@ class ModelType extends AbstractType
|
||||
|
||||
public function setDefaultOptions(OptionsResolverInterface $resolver)
|
||||
{
|
||||
$choiceList = function (Options $options) {
|
||||
$propertyAccessor = $this->propertyAccessor;
|
||||
|
||||
$choiceList = function (Options $options) use ($propertyAccessor) {
|
||||
return new ModelChoiceList(
|
||||
$options['class'],
|
||||
$options['property'],
|
||||
$options['choices'],
|
||||
$options['query'],
|
||||
$options['group_by'],
|
||||
$options['preferred_choices']
|
||||
$options['preferred_choices'],
|
||||
$propertyAccessor
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -26,6 +26,10 @@ class ModelChoiceListTest extends Propel1TestCase
|
||||
if (!class_exists('Symfony\Component\Form\Form')) {
|
||||
$this->markTestSkipped('The "Form" component is not available');
|
||||
}
|
||||
|
||||
if (!class_exists('Symfony\Component\PropertyAccess\PropertyAccessor')) {
|
||||
$this->markTestSkipped('The "PropertyAccessor" component is not available');
|
||||
}
|
||||
}
|
||||
|
||||
public function testEmptyChoicesReturnsEmpty()
|
||||
|
@ -10,6 +10,7 @@
|
||||
<parameter key="form.factory.class">Symfony\Component\Form\FormFactory</parameter>
|
||||
<parameter key="form.extension.class">Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension</parameter>
|
||||
<parameter key="form.type_guesser.validator.class">Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser</parameter>
|
||||
<parameter key="property_accessor.class">Symfony\Component\PropertyAccess\PropertyAccessor</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
@ -53,11 +54,15 @@
|
||||
<argument type="service" id="validator.mapping.class_metadata_factory" />
|
||||
</service>
|
||||
|
||||
<!-- PropertyAccessor -->
|
||||
<service id="property_accessor" class="%property_accessor.class%" />
|
||||
|
||||
<!-- CoreExtension -->
|
||||
<service id="form.type.field" class="Symfony\Component\Form\Extension\Core\Type\FieldType">
|
||||
<tag name="form.type" alias="field" />
|
||||
</service>
|
||||
<service id="form.type.form" class="Symfony\Component\Form\Extension\Core\Type\FormType">
|
||||
<argument type="service" id="property_accessor"/>
|
||||
<tag name="form.type" alias="form" />
|
||||
</service>
|
||||
<service id="form.type.birthday" class="Symfony\Component\Form\Extension\Core\Type\BirthdayType">
|
||||
|
@ -13,6 +13,15 @@ CHANGELOG
|
||||
* [BC BREAK] FormException is now an interface
|
||||
* protected FormBuilder methods from being called when it is turned into a FormConfigInterface with getFormConfig()
|
||||
* [BC BREAK] inserted argument `$message` in the constructor of `FormError`
|
||||
* the PropertyPath class and related classes were moved to a dedicated
|
||||
PropertyAccess component. During the move, InvalidPropertyException was
|
||||
renamed to NoSuchPropertyException. FormUtil was split: FormUtil::singularify()
|
||||
can now be found in Symfony\Component\PropertyAccess\StringUtil. The methods
|
||||
getValue() and setValue() from PropertyPath were extracted into a new class
|
||||
PropertyAccessor.
|
||||
* added an optional PropertyAccessorInterface parameter to FormType,
|
||||
ObjectChoiceList and PropertyPathMapper
|
||||
* [BC BREAK] PropertyPathMapper and FormType now have a constructor
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
@ -11,9 +11,11 @@
|
||||
|
||||
namespace Symfony\Component\Form\Extension\Core\ChoiceList;
|
||||
|
||||
use Symfony\Component\Form\Util\PropertyPath;
|
||||
use Symfony\Component\Form\Exception\StringCastException;
|
||||
use Symfony\Component\Form\Exception\InvalidPropertyException;
|
||||
use Symfony\Component\PropertyAccess\PropertyPath;
|
||||
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
|
||||
/**
|
||||
* A choice list for object choices.
|
||||
@ -32,6 +34,11 @@ use Symfony\Component\Form\Exception\InvalidPropertyException;
|
||||
*/
|
||||
class ObjectChoiceList extends ChoiceList
|
||||
{
|
||||
/**
|
||||
* @var PropertyAccessorInterface
|
||||
*/
|
||||
private $propertyAccessor;
|
||||
|
||||
/**
|
||||
* The property path used to obtain the choice label.
|
||||
*
|
||||
@ -56,28 +63,30 @@ class ObjectChoiceList extends ChoiceList
|
||||
/**
|
||||
* Creates a new object choice list.
|
||||
*
|
||||
* @param array|\Traversable $choices The array of choices. Choices may also be given
|
||||
* as hierarchy of unlimited depth by creating nested
|
||||
* arrays. The title of the sub-hierarchy can be
|
||||
* stored in the array key pointing to the nested
|
||||
* array. The topmost level of the hierarchy may also
|
||||
* be a \Traversable.
|
||||
* @param string $labelPath A property path pointing to the property used
|
||||
* for the choice labels. The value is obtained
|
||||
* by calling the getter on the object. If the
|
||||
* path is NULL, the object's __toString() method
|
||||
* is used instead.
|
||||
* @param array $preferredChoices A flat array of choices that should be
|
||||
* presented to the user with priority.
|
||||
* @param string $groupPath A property path pointing to the property used
|
||||
* to group the choices. Only allowed if
|
||||
* the choices are given as flat array.
|
||||
* @param string $valuePath A property path pointing to the property used
|
||||
* for the choice values. If not given, integers
|
||||
* are generated instead.
|
||||
* @param array|\Traversable $choices The array of choices. Choices may also be given
|
||||
* as hierarchy of unlimited depth by creating nested
|
||||
* arrays. The title of the sub-hierarchy can be
|
||||
* stored in the array key pointing to the nested
|
||||
* array. The topmost level of the hierarchy may also
|
||||
* be a \Traversable.
|
||||
* @param string $labelPath A property path pointing to the property used
|
||||
* for the choice labels. The value is obtained
|
||||
* by calling the getter on the object. If the
|
||||
* path is NULL, the object's __toString() method
|
||||
* is used instead.
|
||||
* @param array $preferredChoices A flat array of choices that should be
|
||||
* presented to the user with priority.
|
||||
* @param string $groupPath A property path pointing to the property used
|
||||
* to group the choices. Only allowed if
|
||||
* the choices are given as flat array.
|
||||
* @param string $valuePath A property path pointing to the property used
|
||||
* for the choice values. If not given, integers
|
||||
* are generated instead.
|
||||
* @param PropertyAccessorInterface $propertyAccessor The reflection graph for reading property paths.
|
||||
*/
|
||||
public function __construct($choices, $labelPath = null, array $preferredChoices = array(), $groupPath = null, $valuePath = null)
|
||||
public function __construct($choices, $labelPath = null, array $preferredChoices = array(), $groupPath = null, $valuePath = null, PropertyAccessorInterface $propertyAccessor = null)
|
||||
{
|
||||
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::getPropertyAccessor();
|
||||
$this->labelPath = null !== $labelPath ? new PropertyPath($labelPath) : null;
|
||||
$this->groupPath = null !== $groupPath ? new PropertyPath($groupPath) : null;
|
||||
$this->valuePath = null !== $valuePath ? new PropertyPath($valuePath) : null;
|
||||
@ -108,8 +117,8 @@ class ObjectChoiceList extends ChoiceList
|
||||
}
|
||||
|
||||
try {
|
||||
$group = $this->groupPath->getValue($choice);
|
||||
} catch (InvalidPropertyException $e) {
|
||||
$group = $this->propertyAccessor->getValue($choice, $this->groupPath);
|
||||
} catch (NoSuchPropertyException $e) {
|
||||
// Don't group items whose group property does not exist
|
||||
// see https://github.com/symfony/symfony/commit/d9b7abb7c7a0f28e0ce970afc5e305dce5dccddf
|
||||
$group = null;
|
||||
@ -150,7 +159,7 @@ class ObjectChoiceList extends ChoiceList
|
||||
protected function createValue($choice)
|
||||
{
|
||||
if ($this->valuePath) {
|
||||
return (string) $this->valuePath->getValue($choice);
|
||||
return (string) $this->propertyAccessor->getValue($choice, $this->valuePath);
|
||||
}
|
||||
|
||||
return parent::createValue($choice);
|
||||
@ -163,7 +172,7 @@ class ObjectChoiceList extends ChoiceList
|
||||
$labels[$i] = array();
|
||||
$this->extractLabels($choice, $labels[$i]);
|
||||
} elseif ($this->labelPath) {
|
||||
$labels[$i] = $this->labelPath->getValue($choice);
|
||||
$labels[$i] = $this->propertyAccessor->getValue($choice, $this->labelPath);
|
||||
} elseif (method_exists($choice, '__toString')) {
|
||||
$labels[$i] = (string) $choice;
|
||||
} else {
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\Form\Extension\Core;
|
||||
|
||||
use Symfony\Component\Form\AbstractExtension;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
|
||||
/**
|
||||
* Represents the main form extension, which loads the core functionality.
|
||||
@ -24,7 +25,7 @@ class CoreExtension extends AbstractExtension
|
||||
{
|
||||
return array(
|
||||
new Type\FieldType(),
|
||||
new Type\FormType(),
|
||||
new Type\FormType(PropertyAccess::getPropertyAccessor()),
|
||||
new Type\BirthdayType(),
|
||||
new Type\CheckboxType(),
|
||||
new Type\ChoiceType(),
|
||||
|
@ -14,9 +14,31 @@ namespace Symfony\Component\Form\Extension\Core\DataMapper;
|
||||
use Symfony\Component\Form\DataMapperInterface;
|
||||
use Symfony\Component\Form\Util\VirtualFormAwareIterator;
|
||||
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
|
||||
/**
|
||||
* A data mapper using property paths to read/write data.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class PropertyPathMapper implements DataMapperInterface
|
||||
{
|
||||
/**
|
||||
* @var PropertyAccessorInterface
|
||||
*/
|
||||
private $propertyAccessor;
|
||||
|
||||
/**
|
||||
* Creates a new property path mapper.
|
||||
*
|
||||
* @param PropertyAccessorInterface $propertyAccessor
|
||||
*/
|
||||
public function __construct(PropertyAccessorInterface $propertyAccessor = null)
|
||||
{
|
||||
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::getPropertyAccessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -39,7 +61,7 @@ class PropertyPathMapper implements DataMapperInterface
|
||||
$config = $form->getConfig();
|
||||
|
||||
if (null !== $propertyPath && $config->getMapped()) {
|
||||
$form->setData($propertyPath->getValue($data));
|
||||
$form->setData($this->propertyAccessor->getValue($data, $propertyPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -70,8 +92,8 @@ class PropertyPathMapper implements DataMapperInterface
|
||||
if (null !== $propertyPath && $config->getMapped() && $form->isSynchronized() && !$form->isDisabled()) {
|
||||
// If the data is identical to the value in $data, we are
|
||||
// dealing with a reference
|
||||
if (!is_object($data) || !$config->getByReference() || $form->getData() !== $propertyPath->getValue($data)) {
|
||||
$propertyPath->setValue($data, $form->getData());
|
||||
if (!is_object($data) || !$config->getByReference() || $form->getData() !== $this->propertyAccessor->getValue($data, $propertyPath)) {
|
||||
$this->propertyAccessor->setValue($data, $propertyPath, $form->getData());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,9 +20,21 @@ use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper;
|
||||
use Symfony\Component\Form\Exception\Exception;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
|
||||
class FormType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @var PropertyAccessorInterface
|
||||
*/
|
||||
private $propertyAccessor;
|
||||
|
||||
public function __construct(PropertyAccessorInterface $propertyAccessor = null)
|
||||
{
|
||||
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::getPropertyAccessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -41,7 +53,7 @@ class FormType extends AbstractType
|
||||
->setCompound($options['compound'])
|
||||
->setData(isset($options['data']) ? $options['data'] : null)
|
||||
->setDataLocked(isset($options['data']))
|
||||
->setDataMapper($options['compound'] ? new PropertyPathMapper() : null)
|
||||
->setDataMapper($options['compound'] ? new PropertyPathMapper($this->propertyAccessor) : null)
|
||||
;
|
||||
|
||||
if ($options['trim']) {
|
||||
|
@ -12,7 +12,7 @@
|
||||
namespace Symfony\Component\Form\Extension\Validator\ViolationMapper;
|
||||
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\Util\PropertyPath;
|
||||
use Symfony\Component\PropertyAccess\PropertyPath;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
|
@ -13,9 +13,9 @@ namespace Symfony\Component\Form\Extension\Validator\ViolationMapper;
|
||||
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\Util\VirtualFormAwareIterator;
|
||||
use Symfony\Component\Form\Util\PropertyPathIterator;
|
||||
use Symfony\Component\Form\Util\PropertyPathBuilder;
|
||||
use Symfony\Component\Form\Util\PropertyPathIteratorInterface;
|
||||
use Symfony\Component\PropertyAccess\PropertyPathIterator;
|
||||
use Symfony\Component\PropertyAccess\PropertyPathBuilder;
|
||||
use Symfony\Component\PropertyAccess\PropertyPathIteratorInterface;
|
||||
use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationPathIterator;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Validator\ConstraintViolation;
|
||||
@ -265,7 +265,7 @@ class ViolationMapper implements ViolationMapperInterface
|
||||
$propertyPathBuilder->remove(0, $i + 1);
|
||||
$i = 0;
|
||||
} else {
|
||||
/* @var \Symfony\Component\Form\Util\PropertyPathInterface $propertyPath */
|
||||
/* @var \Symfony\Component\PropertyAccess\PropertyPathInterface $propertyPath */
|
||||
$propertyPath = $scope->getPropertyPath();
|
||||
|
||||
if (null === $propertyPath) {
|
||||
|
@ -11,8 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\Form\Extension\Validator\ViolationMapper;
|
||||
|
||||
use Symfony\Component\Form\Util\PropertyPath;
|
||||
use Symfony\Component\Form\Util\PropertyPathInterface;
|
||||
use Symfony\Component\PropertyAccess\PropertyPath;
|
||||
use Symfony\Component\PropertyAccess\PropertyPathInterface;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\Form\Extension\Validator\ViolationMapper;
|
||||
|
||||
use Symfony\Component\Form\Util\PropertyPathIterator;
|
||||
use Symfony\Component\PropertyAccess\PropertyPathIterator;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
|
@ -16,8 +16,8 @@ use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Form\Exception\AlreadyBoundException;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
use Symfony\Component\Form\Util\FormUtil;
|
||||
use Symfony\Component\Form\Util\PropertyPath;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\PropertyAccess\PropertyPath;
|
||||
|
||||
/**
|
||||
* Form represents a form.
|
||||
|
@ -14,8 +14,8 @@ namespace Symfony\Component\Form;
|
||||
use Symfony\Component\Form\Exception\BadMethodCallException;
|
||||
use Symfony\Component\Form\Exception\Exception;
|
||||
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Form\Util\PropertyPath;
|
||||
use Symfony\Component\Form\Util\PropertyPathInterface;
|
||||
use Symfony\Component\PropertyAccess\PropertyPath;
|
||||
use Symfony\Component\PropertyAccess\PropertyPathInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
|
||||
|
@ -117,7 +117,7 @@ interface FormConfigBuilderInterface extends FormConfigInterface
|
||||
/**
|
||||
* Sets the data mapper used by the form.
|
||||
*
|
||||
* @param DataMapperInterface $dataMapper
|
||||
* @param DataMapperInterface $dataMapper
|
||||
*
|
||||
* @return self The configuration object.
|
||||
*/
|
||||
@ -126,7 +126,7 @@ interface FormConfigBuilderInterface extends FormConfigInterface
|
||||
/**
|
||||
* Set whether the form is disabled.
|
||||
*
|
||||
* @param Boolean $disabled Whether the form is disabled
|
||||
* @param Boolean $disabled Whether the form is disabled
|
||||
*
|
||||
* @return self The configuration object.
|
||||
*/
|
||||
@ -135,7 +135,7 @@ interface FormConfigBuilderInterface extends FormConfigInterface
|
||||
/**
|
||||
* Sets the data used for the client data when no value is bound.
|
||||
*
|
||||
* @param mixed $emptyData The empty data.
|
||||
* @param mixed $emptyData The empty data.
|
||||
*
|
||||
* @return self The configuration object.
|
||||
*/
|
||||
@ -144,7 +144,7 @@ interface FormConfigBuilderInterface extends FormConfigInterface
|
||||
/**
|
||||
* Sets whether errors bubble up to the parent.
|
||||
*
|
||||
* @param Boolean $errorBubbling
|
||||
* @param Boolean $errorBubbling
|
||||
*
|
||||
* @return self The configuration object.
|
||||
*/
|
||||
@ -162,9 +162,9 @@ interface FormConfigBuilderInterface extends FormConfigInterface
|
||||
/**
|
||||
* Sets the property path that the form should be mapped to.
|
||||
*
|
||||
* @param null|string|PropertyPathInterface $propertyPath The property path or null if the path
|
||||
* should be set automatically based on
|
||||
* the form's name.
|
||||
* @param null|string|\Symfony\Component\PropertyAccess\PropertyPathInterface $propertyPath
|
||||
* The property path or null if the path should be set
|
||||
* automatically based on the form's name.
|
||||
*
|
||||
* @return self The configuration object.
|
||||
*/
|
||||
@ -174,7 +174,7 @@ interface FormConfigBuilderInterface extends FormConfigInterface
|
||||
* Sets whether the form should be mapped to an element of its
|
||||
* parent's data.
|
||||
*
|
||||
* @param Boolean $mapped Whether the form should be mapped.
|
||||
* @param Boolean $mapped Whether the form should be mapped.
|
||||
*
|
||||
* @return self The configuration object.
|
||||
*/
|
||||
@ -183,7 +183,7 @@ interface FormConfigBuilderInterface extends FormConfigInterface
|
||||
/**
|
||||
* Sets whether the form's data should be modified by reference.
|
||||
*
|
||||
* @param Boolean $byReference Whether the data should be
|
||||
* @param Boolean $byReference Whether the data should be
|
||||
* modified by reference.
|
||||
*
|
||||
* @return self The configuration object.
|
||||
@ -193,7 +193,7 @@ interface FormConfigBuilderInterface extends FormConfigInterface
|
||||
/**
|
||||
* Sets whether the form should be virtual.
|
||||
*
|
||||
* @param Boolean $virtual Whether the form should be virtual.
|
||||
* @param Boolean $virtual Whether the form should be virtual.
|
||||
*
|
||||
* @return self The configuration object.
|
||||
*/
|
||||
@ -202,7 +202,7 @@ interface FormConfigBuilderInterface extends FormConfigInterface
|
||||
/**
|
||||
* Sets whether the form should be compound.
|
||||
*
|
||||
* @param Boolean $compound Whether the form should be compound.
|
||||
* @param Boolean $compound Whether the form should be compound.
|
||||
*
|
||||
* @return self The configuration object.
|
||||
*
|
||||
@ -235,7 +235,7 @@ interface FormConfigBuilderInterface extends FormConfigInterface
|
||||
* this configuration. The data can only be modified then by
|
||||
* binding the form.
|
||||
*
|
||||
* @param Boolean $locked Whether to lock the default data.
|
||||
* @param Boolean $locked Whether to lock the default data.
|
||||
*
|
||||
* @return self The configuration object.
|
||||
*/
|
||||
|
@ -35,7 +35,7 @@ interface FormConfigInterface
|
||||
/**
|
||||
* Returns the property path that the form should be mapped to.
|
||||
*
|
||||
* @return null|Util\PropertyPathInterface The property path.
|
||||
* @return null|\Symfony\Component\PropertyAccess\PropertyPathInterface The property path.
|
||||
*/
|
||||
public function getPropertyPath();
|
||||
|
||||
|
@ -166,7 +166,7 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable
|
||||
/**
|
||||
* Returns the property path that the form is mapped to.
|
||||
*
|
||||
* @return Util\PropertyPathInterface The property path.
|
||||
* @return \Symfony\Component\PropertyAccess\PropertyPathInterface The property path.
|
||||
*/
|
||||
public function getPropertyPath();
|
||||
|
||||
|
@ -39,6 +39,9 @@ class ObjectChoiceListTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
private $obj4;
|
||||
|
||||
/**
|
||||
* @var ObjectChoiceList
|
||||
*/
|
||||
private $list;
|
||||
|
||||
protected function setUp()
|
||||
|
@ -11,10 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Extension\Core\DataMapper;
|
||||
|
||||
use Symfony\Component\Form\Form;
|
||||
use Symfony\Component\Form\FormConfigBuilder;
|
||||
use Symfony\Component\Form\FormConfigInterface;
|
||||
use Symfony\Component\Form\Util\PropertyPath;
|
||||
use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper;
|
||||
|
||||
class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
|
||||
@ -29,14 +27,24 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
|
||||
*/
|
||||
private $dispatcher;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
private $propertyAccessor;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
if (!class_exists('Symfony\Component\EventDispatcher\Event')) {
|
||||
$this->markTestSkipped('The "EventDispatcher" component is not available');
|
||||
}
|
||||
|
||||
if (!class_exists('Symfony\Component\PropertyAccess\PropertyAccess')) {
|
||||
$this->markTestSkipped('The "PropertyAccess" component is not available');
|
||||
}
|
||||
|
||||
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
|
||||
$this->mapper = new PropertyPathMapper();
|
||||
$this->propertyAccessor = $this->getMock('Symfony\Component\PropertyAccess\PropertyAccessorInterface');
|
||||
$this->mapper = new PropertyPathMapper($this->propertyAccessor);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -45,7 +53,7 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
|
||||
*/
|
||||
private function getPropertyPath($path)
|
||||
{
|
||||
return $this->getMockBuilder('Symfony\Component\Form\Util\PropertyPath')
|
||||
return $this->getMockBuilder('Symfony\Component\PropertyAccess\PropertyPath')
|
||||
->setConstructorArgs(array($path))
|
||||
->setMethods(array('getValue', 'setValue'))
|
||||
->getMock();
|
||||
@ -84,9 +92,9 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
|
||||
$engine = new \stdClass();
|
||||
$propertyPath = $this->getPropertyPath('engine');
|
||||
|
||||
$propertyPath->expects($this->once())
|
||||
$this->propertyAccessor->expects($this->once())
|
||||
->method('getValue')
|
||||
->with($car)
|
||||
->with($car, $propertyPath)
|
||||
->will($this->returnValue($engine));
|
||||
|
||||
$config = new FormConfigBuilder('name', '\stdClass', $this->dispatcher);
|
||||
@ -107,9 +115,9 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
|
||||
$engine = new \stdClass();
|
||||
$propertyPath = $this->getPropertyPath('engine');
|
||||
|
||||
$propertyPath->expects($this->once())
|
||||
$this->propertyAccessor->expects($this->once())
|
||||
->method('getValue')
|
||||
->with($car)
|
||||
->with($car, $propertyPath)
|
||||
->will($this->returnValue($engine));
|
||||
|
||||
$config = new FormConfigBuilder('name', '\stdClass', $this->dispatcher);
|
||||
@ -143,7 +151,7 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
|
||||
$car = new \stdClass();
|
||||
$propertyPath = $this->getPropertyPath('engine');
|
||||
|
||||
$propertyPath->expects($this->never())
|
||||
$this->propertyAccessor->expects($this->never())
|
||||
->method('getValue');
|
||||
|
||||
$config = new FormConfigBuilder('name', '\stdClass', $this->dispatcher);
|
||||
@ -161,7 +169,7 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
$propertyPath = $this->getPropertyPath('engine');
|
||||
|
||||
$propertyPath->expects($this->never())
|
||||
$this->propertyAccessor->expects($this->never())
|
||||
->method('getValue');
|
||||
|
||||
$config = new FormConfigBuilder('name', '\stdClass', $this->dispatcher);
|
||||
@ -180,9 +188,9 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
|
||||
$engine = new \stdClass();
|
||||
$propertyPath = $this->getPropertyPath('engine');
|
||||
|
||||
$propertyPath->expects($this->once())
|
||||
$this->propertyAccessor->expects($this->once())
|
||||
->method('getValue')
|
||||
->with($car)
|
||||
->with($car, $propertyPath)
|
||||
->will($this->returnValue($engine));
|
||||
|
||||
$config = new FormConfigBuilder('name', '\stdClass', $this->dispatcher);
|
||||
@ -211,9 +219,9 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
|
||||
$engine = new \stdClass();
|
||||
$propertyPath = $this->getPropertyPath('engine');
|
||||
|
||||
$propertyPath->expects($this->once())
|
||||
$this->propertyAccessor->expects($this->once())
|
||||
->method('setValue')
|
||||
->with($car, $engine);
|
||||
->with($car, $propertyPath, $engine);
|
||||
|
||||
$config = new FormConfigBuilder('name', '\stdClass', $this->dispatcher);
|
||||
$config->setByReference(false);
|
||||
@ -230,9 +238,9 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
|
||||
$engine = new \stdClass();
|
||||
$propertyPath = $this->getPropertyPath('engine');
|
||||
|
||||
$propertyPath->expects($this->once())
|
||||
$this->propertyAccessor->expects($this->once())
|
||||
->method('setValue')
|
||||
->with($car, $engine);
|
||||
->with($car, $propertyPath, $engine);
|
||||
|
||||
$config = new FormConfigBuilder('name', '\stdClass', $this->dispatcher);
|
||||
$config->setByReference(true);
|
||||
@ -250,12 +258,12 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
|
||||
$propertyPath = $this->getPropertyPath('engine');
|
||||
|
||||
// $car already contains the reference of $engine
|
||||
$propertyPath->expects($this->once())
|
||||
$this->propertyAccessor->expects($this->once())
|
||||
->method('getValue')
|
||||
->with($car)
|
||||
->with($car, $propertyPath)
|
||||
->will($this->returnValue($engine));
|
||||
|
||||
$propertyPath->expects($this->never())
|
||||
$this->propertyAccessor->expects($this->never())
|
||||
->method('setValue');
|
||||
|
||||
$config = new FormConfigBuilder('name', '\stdClass', $this->dispatcher);
|
||||
@ -273,7 +281,7 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
|
||||
$engine = new \stdClass();
|
||||
$propertyPath = $this->getPropertyPath('engine');
|
||||
|
||||
$propertyPath->expects($this->never())
|
||||
$this->propertyAccessor->expects($this->never())
|
||||
->method('setValue');
|
||||
|
||||
$config = new FormConfigBuilder('name', '\stdClass', $this->dispatcher);
|
||||
@ -291,7 +299,7 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
|
||||
$car = new \stdClass();
|
||||
$propertyPath = $this->getPropertyPath('engine');
|
||||
|
||||
$propertyPath->expects($this->never())
|
||||
$this->propertyAccessor->expects($this->never())
|
||||
->method('setValue');
|
||||
|
||||
$config = new FormConfigBuilder('name', '\stdClass', $this->dispatcher);
|
||||
@ -309,7 +317,7 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
|
||||
$engine = new \stdClass();
|
||||
$propertyPath = $this->getPropertyPath('engine');
|
||||
|
||||
$propertyPath->expects($this->never())
|
||||
$this->propertyAccessor->expects($this->never())
|
||||
->method('setValue');
|
||||
|
||||
$config = new FormConfigBuilder('name', '\stdClass', $this->dispatcher);
|
||||
@ -327,7 +335,7 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
|
||||
$engine = new \stdClass();
|
||||
$propertyPath = $this->getPropertyPath('engine');
|
||||
|
||||
$propertyPath->expects($this->never())
|
||||
$this->propertyAccessor->expects($this->never())
|
||||
->method('setValue');
|
||||
|
||||
$config = new FormConfigBuilder('name', '\stdClass', $this->dispatcher);
|
||||
@ -347,14 +355,14 @@ class PropertyPathMapperTest extends \PHPUnit_Framework_TestCase
|
||||
$parentPath = $this->getPropertyPath('name');
|
||||
$childPath = $this->getPropertyPath('engine');
|
||||
|
||||
$parentPath->expects($this->never())
|
||||
->method('getValue');
|
||||
$parentPath->expects($this->never())
|
||||
->method('setValue');
|
||||
// getValue() and setValue() must never be invoked for $parentPath
|
||||
|
||||
$childPath->expects($this->once())
|
||||
$this->propertyAccessor->expects($this->once())
|
||||
->method('getValue')
|
||||
->with($car, $childPath);
|
||||
$this->propertyAccessor->expects($this->once())
|
||||
->method('setValue')
|
||||
->with($car, $engine);
|
||||
->with($car, $childPath, $engine);
|
||||
|
||||
$config = new FormConfigBuilder('name', '\stdClass', $this->dispatcher);
|
||||
$config->setPropertyPath($parentPath);
|
||||
|
@ -11,8 +11,6 @@
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
|
||||
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
|
||||
|
||||
use Symfony\Component\Form\Util\PropertyPath;
|
||||
use Symfony\Component\PropertyAccess\PropertyPath;
|
||||
use Symfony\Component\Form\Form;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Tests\Fixtures\Author;
|
||||
|
@ -14,7 +14,7 @@ namespace Symfony\Component\Form\Tests\Extension\Validator\EventListener;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormBuilder;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\Util\PropertyPath;
|
||||
use Symfony\Component\PropertyAccess\PropertyPath;
|
||||
use Symfony\Component\Form\Extension\Validator\Constraints\Form;
|
||||
use Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener;
|
||||
use Symfony\Component\Validator\ConstraintViolation;
|
||||
|
@ -17,7 +17,7 @@ use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Form;
|
||||
use Symfony\Component\Form\FormConfigBuilder;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\Util\PropertyPath;
|
||||
use Symfony\Component\PropertyAccess\PropertyPath;
|
||||
use Symfony\Component\Validator\ConstraintViolation;
|
||||
|
||||
/**
|
||||
|
@ -14,7 +14,7 @@ namespace Symfony\Component\Form\Tests;
|
||||
use Symfony\Component\Form\Form;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\Util\PropertyPath;
|
||||
use Symfony\Component\PropertyAccess\PropertyPath;
|
||||
use Symfony\Component\Form\FormConfigBuilder;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
|
@ -1,557 +0,0 @@
|
||||
<?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\Form\Tests\Util;
|
||||
|
||||
use Symfony\Component\Form\Util\PropertyPath;
|
||||
use Symfony\Component\Form\Tests\Fixtures\Author;
|
||||
use Symfony\Component\Form\Tests\Fixtures\Magician;
|
||||
|
||||
class PropertyPathTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testGetValueReadsArray()
|
||||
{
|
||||
$array = array('firstName' => 'Bernhard');
|
||||
|
||||
$path = new PropertyPath('[firstName]');
|
||||
|
||||
$this->assertEquals('Bernhard', $path->getValue($array));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\InvalidPropertyException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfIndexNotationExpected()
|
||||
{
|
||||
$array = array('firstName' => 'Bernhard');
|
||||
|
||||
$path = new PropertyPath('firstName');
|
||||
|
||||
$path->getValue($array);
|
||||
}
|
||||
|
||||
public function testGetValueReadsZeroIndex()
|
||||
{
|
||||
$array = array('Bernhard');
|
||||
|
||||
$path = new PropertyPath('[0]');
|
||||
|
||||
$this->assertEquals('Bernhard', $path->getValue($array));
|
||||
}
|
||||
|
||||
public function testGetValueReadsIndexWithSpecialChars()
|
||||
{
|
||||
$array = array('%!@$§.' => 'Bernhard');
|
||||
|
||||
$path = new PropertyPath('[%!@$§.]');
|
||||
|
||||
$this->assertEquals('Bernhard', $path->getValue($array));
|
||||
}
|
||||
|
||||
public function testGetValueReadsNestedIndexWithSpecialChars()
|
||||
{
|
||||
$array = array('root' => array('%!@$§.' => 'Bernhard'));
|
||||
|
||||
$path = new PropertyPath('[root][%!@$§.]');
|
||||
|
||||
$this->assertEquals('Bernhard', $path->getValue($array));
|
||||
}
|
||||
|
||||
public function testGetValueReadsArrayWithCustomPropertyPath()
|
||||
{
|
||||
$array = array('child' => array('index' => array('firstName' => 'Bernhard')));
|
||||
|
||||
$path = new PropertyPath('[child][index][firstName]');
|
||||
|
||||
$this->assertEquals('Bernhard', $path->getValue($array));
|
||||
}
|
||||
|
||||
public function testGetValueReadsArrayWithMissingIndexForCustomPropertyPath()
|
||||
{
|
||||
$array = array('child' => array('index' => array()));
|
||||
|
||||
$path = new PropertyPath('[child][index][firstName]');
|
||||
|
||||
$this->assertNull($path->getValue($array));
|
||||
}
|
||||
|
||||
public function testGetValueReadsProperty()
|
||||
{
|
||||
$object = new Author();
|
||||
$object->firstName = 'Bernhard';
|
||||
|
||||
$path = new PropertyPath('firstName');
|
||||
|
||||
$this->assertEquals('Bernhard', $path->getValue($object));
|
||||
}
|
||||
|
||||
public function testGetValueIgnoresSingular()
|
||||
{
|
||||
$this->markTestSkipped('This feature is temporarily disabled as of 2.1');
|
||||
|
||||
$object = (object) array('children' => 'Many');
|
||||
|
||||
$path = new PropertyPath('children|child');
|
||||
|
||||
$this->assertEquals('Many', $path->getValue($object));
|
||||
}
|
||||
|
||||
public function testGetValueReadsPropertyWithSpecialCharsExceptDot()
|
||||
{
|
||||
$array = (object) array('%!@$§' => 'Bernhard');
|
||||
|
||||
$path = new PropertyPath('%!@$§');
|
||||
|
||||
$this->assertEquals('Bernhard', $path->getValue($array));
|
||||
}
|
||||
|
||||
public function testGetValueReadsPropertyWithCustomPropertyPath()
|
||||
{
|
||||
$object = new Author();
|
||||
$object->child = array();
|
||||
$object->child['index'] = new Author();
|
||||
$object->child['index']->firstName = 'Bernhard';
|
||||
|
||||
$path = new PropertyPath('child[index].firstName');
|
||||
|
||||
$this->assertEquals('Bernhard', $path->getValue($object));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\PropertyAccessDeniedException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfPropertyIsNotPublic()
|
||||
{
|
||||
$path = new PropertyPath('privateProperty');
|
||||
|
||||
$path->getValue(new Author());
|
||||
}
|
||||
|
||||
public function testGetValueReadsGetters()
|
||||
{
|
||||
$path = new PropertyPath('lastName');
|
||||
|
||||
$object = new Author();
|
||||
$object->setLastName('Schussek');
|
||||
|
||||
$this->assertEquals('Schussek', $path->getValue($object));
|
||||
}
|
||||
|
||||
public function testGetValueCamelizesGetterNames()
|
||||
{
|
||||
$path = new PropertyPath('last_name');
|
||||
|
||||
$object = new Author();
|
||||
$object->setLastName('Schussek');
|
||||
|
||||
$this->assertEquals('Schussek', $path->getValue($object));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\PropertyAccessDeniedException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfGetterIsNotPublic()
|
||||
{
|
||||
$path = new PropertyPath('privateGetter');
|
||||
|
||||
$path->getValue(new Author());
|
||||
}
|
||||
|
||||
public function testGetValueReadsIssers()
|
||||
{
|
||||
$path = new PropertyPath('australian');
|
||||
|
||||
$object = new Author();
|
||||
$object->setAustralian(false);
|
||||
|
||||
$this->assertFalse($path->getValue($object));
|
||||
}
|
||||
|
||||
public function testGetValueReadHassers()
|
||||
{
|
||||
$path = new PropertyPath('read_permissions');
|
||||
|
||||
$object = new Author();
|
||||
$object->setReadPermissions(true);
|
||||
|
||||
$this->assertTrue($path->getValue($object));
|
||||
}
|
||||
|
||||
public function testGetValueReadsMagicGet()
|
||||
{
|
||||
$path = new PropertyPath('magicProperty');
|
||||
|
||||
$object = new Magician();
|
||||
$object->__set('magicProperty', 'foobar');
|
||||
|
||||
$this->assertSame('foobar', $path->getValue($object));
|
||||
}
|
||||
|
||||
/*
|
||||
* https://github.com/symfony/symfony/pull/4450
|
||||
*/
|
||||
public function testGetValueReadsMagicGetThatReturnsConstant()
|
||||
{
|
||||
$path = new PropertyPath('magicProperty');
|
||||
|
||||
$object = new Magician();
|
||||
|
||||
$this->assertNull($path->getValue($object));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\PropertyAccessDeniedException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfIsserIsNotPublic()
|
||||
{
|
||||
$path = new PropertyPath('privateIsser');
|
||||
|
||||
$path->getValue(new Author());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\InvalidPropertyException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfPropertyDoesNotExist()
|
||||
{
|
||||
$path = new PropertyPath('foobar');
|
||||
|
||||
$path->getValue(new Author());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfNotObjectOrArray()
|
||||
{
|
||||
$path = new PropertyPath('foobar');
|
||||
|
||||
$path->getValue('baz');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfNull()
|
||||
{
|
||||
$path = new PropertyPath('foobar');
|
||||
|
||||
$path->getValue(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfEmpty()
|
||||
{
|
||||
$path = new PropertyPath('foobar');
|
||||
|
||||
$path->getValue('');
|
||||
}
|
||||
|
||||
public function testSetValueUpdatesArrays()
|
||||
{
|
||||
$array = array();
|
||||
|
||||
$path = new PropertyPath('[firstName]');
|
||||
$path->setValue($array, 'Bernhard');
|
||||
|
||||
$this->assertEquals(array('firstName' => 'Bernhard'), $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\InvalidPropertyException
|
||||
*/
|
||||
public function testSetValueThrowsExceptionIfIndexNotationExpected()
|
||||
{
|
||||
$array = array();
|
||||
|
||||
$path = new PropertyPath('firstName');
|
||||
$path->setValue($array, 'Bernhard');
|
||||
}
|
||||
|
||||
public function testSetValueUpdatesArraysWithCustomPropertyPath()
|
||||
{
|
||||
$array = array();
|
||||
|
||||
$path = new PropertyPath('[child][index][firstName]');
|
||||
$path->setValue($array, 'Bernhard');
|
||||
|
||||
$this->assertEquals(array('child' => array('index' => array('firstName' => 'Bernhard'))), $array);
|
||||
}
|
||||
|
||||
public function testSetValueUpdatesProperties()
|
||||
{
|
||||
$object = new Author();
|
||||
|
||||
$path = new PropertyPath('firstName');
|
||||
$path->setValue($object, 'Bernhard');
|
||||
|
||||
$this->assertEquals('Bernhard', $object->firstName);
|
||||
}
|
||||
|
||||
public function testSetValueUpdatesPropertiesWithCustomPropertyPath()
|
||||
{
|
||||
$object = new Author();
|
||||
$object->child = array();
|
||||
$object->child['index'] = new Author();
|
||||
|
||||
$path = new PropertyPath('child[index].firstName');
|
||||
$path->setValue($object, 'Bernhard');
|
||||
|
||||
$this->assertEquals('Bernhard', $object->child['index']->firstName);
|
||||
}
|
||||
|
||||
public function testSetValueUpdateMagicSet()
|
||||
{
|
||||
$object = new Magician();
|
||||
|
||||
$path = new PropertyPath('magicProperty');
|
||||
$path->setValue($object, 'foobar');
|
||||
|
||||
$this->assertEquals('foobar', $object->__get('magicProperty'));
|
||||
}
|
||||
|
||||
public function testSetValueUpdatesSetters()
|
||||
{
|
||||
$object = new Author();
|
||||
|
||||
$path = new PropertyPath('lastName');
|
||||
$path->setValue($object, 'Schussek');
|
||||
|
||||
$this->assertEquals('Schussek', $object->getLastName());
|
||||
}
|
||||
|
||||
public function testSetValueCamelizesSetterNames()
|
||||
{
|
||||
$object = new Author();
|
||||
|
||||
$path = new PropertyPath('last_name');
|
||||
$path->setValue($object, 'Schussek');
|
||||
|
||||
$this->assertEquals('Schussek', $object->getLastName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\PropertyAccessDeniedException
|
||||
*/
|
||||
public function testSetValueThrowsExceptionIfGetterIsNotPublic()
|
||||
{
|
||||
$path = new PropertyPath('privateSetter');
|
||||
|
||||
$path->setValue(new Author(), 'foobar');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testSetValueThrowsExceptionIfNotObjectOrArray()
|
||||
{
|
||||
$path = new PropertyPath('foobar');
|
||||
$value = 'baz';
|
||||
|
||||
$path->setValue($value, 'bam');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testSetValueThrowsExceptionIfNull()
|
||||
{
|
||||
$path = new PropertyPath('foobar');
|
||||
$value = null;
|
||||
|
||||
$path->setValue($value, 'bam');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testSetValueThrowsExceptionIfEmpty()
|
||||
{
|
||||
$path = new PropertyPath('foobar');
|
||||
$value = '';
|
||||
|
||||
$path->setValue($value, 'bam');
|
||||
}
|
||||
|
||||
public function testToString()
|
||||
{
|
||||
$path = new PropertyPath('reference.traversable[index].property');
|
||||
|
||||
$this->assertEquals('reference.traversable[index].property', $path->__toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\InvalidPropertyPathException
|
||||
*/
|
||||
public function testInvalidPropertyPath_noDotBeforeProperty()
|
||||
{
|
||||
new PropertyPath('[index]property');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\InvalidPropertyPathException
|
||||
*/
|
||||
public function testInvalidPropertyPath_dotAtTheBeginning()
|
||||
{
|
||||
new PropertyPath('.property');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\InvalidPropertyPathException
|
||||
*/
|
||||
public function testInvalidPropertyPath_unexpectedCharacters()
|
||||
{
|
||||
new PropertyPath('property.$form');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\InvalidPropertyPathException
|
||||
*/
|
||||
public function testInvalidPropertyPath_empty()
|
||||
{
|
||||
new PropertyPath('');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testInvalidPropertyPath_null()
|
||||
{
|
||||
new PropertyPath(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testInvalidPropertyPath_false()
|
||||
{
|
||||
new PropertyPath(false);
|
||||
}
|
||||
|
||||
public function testValidPropertyPath_zero()
|
||||
{
|
||||
new PropertyPath('0');
|
||||
}
|
||||
|
||||
public function testGetParent_dot()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent.child');
|
||||
|
||||
$this->assertEquals(new PropertyPath('grandpa.parent'), $propertyPath->getParent());
|
||||
}
|
||||
|
||||
public function testGetParent_index()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$this->assertEquals(new PropertyPath('grandpa.parent'), $propertyPath->getParent());
|
||||
}
|
||||
|
||||
public function testGetParent_noParent()
|
||||
{
|
||||
$propertyPath = new PropertyPath('path');
|
||||
|
||||
$this->assertNull($propertyPath->getParent());
|
||||
}
|
||||
|
||||
public function testCopyConstructor()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
$copy = new PropertyPath($propertyPath);
|
||||
|
||||
$this->assertEquals($propertyPath, $copy);
|
||||
}
|
||||
|
||||
public function testGetElement()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$this->assertEquals('child', $propertyPath->getElement(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OutOfBoundsException
|
||||
*/
|
||||
public function testGetElementDoesNotAcceptInvalidIndices()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$propertyPath->getElement(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OutOfBoundsException
|
||||
*/
|
||||
public function testGetElementDoesNotAcceptNegativeIndices()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$propertyPath->getElement(-1);
|
||||
}
|
||||
|
||||
public function testIsProperty()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$this->assertTrue($propertyPath->isProperty(1));
|
||||
$this->assertFalse($propertyPath->isProperty(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OutOfBoundsException
|
||||
*/
|
||||
public function testIsPropertyDoesNotAcceptInvalidIndices()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$propertyPath->isProperty(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OutOfBoundsException
|
||||
*/
|
||||
public function testIsPropertyDoesNotAcceptNegativeIndices()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$propertyPath->isProperty(-1);
|
||||
}
|
||||
|
||||
public function testIsIndex()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$this->assertFalse($propertyPath->isIndex(1));
|
||||
$this->assertTrue($propertyPath->isIndex(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OutOfBoundsException
|
||||
*/
|
||||
public function testIsIndexDoesNotAcceptInvalidIndices()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$propertyPath->isIndex(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OutOfBoundsException
|
||||
*/
|
||||
public function testIsIndexDoesNotAcceptNegativeIndices()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$propertyPath->isIndex(-1);
|
||||
}
|
||||
}
|
@ -11,181 +11,29 @@
|
||||
|
||||
namespace Symfony\Component\Form\Util;
|
||||
|
||||
use Symfony\Component\PropertyAccess\StringUtil;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class FormUtil
|
||||
{
|
||||
/**
|
||||
* Map english plural to singular suffixes
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @see http://english-zone.com/spelling/plurals.html
|
||||
* @see http://www.scribd.com/doc/3271143/List-of-100-Irregular-Plural-Nouns-in-English
|
||||
*/
|
||||
private static $pluralMap = array(
|
||||
// First entry: plural suffix, reversed
|
||||
// Second entry: length of plural suffix
|
||||
// Third entry: Whether the suffix may succeed a vocal
|
||||
// Fourth entry: Whether the suffix may succeed a consonant
|
||||
// Fifth entry: singular suffix, normal
|
||||
|
||||
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
|
||||
array('a', 1, true, true, array('on', 'um')),
|
||||
|
||||
// nebulae (nebula)
|
||||
array('ea', 2, true, true, 'a'),
|
||||
|
||||
// mice (mouse), lice (louse)
|
||||
array('eci', 3, false, true, 'ouse'),
|
||||
|
||||
// geese (goose)
|
||||
array('esee', 4, false, true, 'oose'),
|
||||
|
||||
// fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
|
||||
array('i', 1, true, true, 'us'),
|
||||
|
||||
// men (man), women (woman)
|
||||
array('nem', 3, true, true, 'man'),
|
||||
|
||||
// children (child)
|
||||
array('nerdlihc', 8, true, true, 'child'),
|
||||
|
||||
// oxen (ox)
|
||||
array('nexo', 4, false, false, 'ox'),
|
||||
|
||||
// indices (index), appendices (appendix), prices (price)
|
||||
array('seci', 4, false, true, array('ex', 'ix', 'ice')),
|
||||
|
||||
// babies (baby)
|
||||
array('sei', 3, false, true, 'y'),
|
||||
|
||||
// analyses (analysis), ellipses (ellipsis), funguses (fungus),
|
||||
// neuroses (neurosis), theses (thesis), emphases (emphasis),
|
||||
// oases (oasis), crises (crisis), houses (house), bases (base),
|
||||
// atlases (atlas), kisses (kiss)
|
||||
array('ses', 3, true, true, array('s', 'se', 'sis')),
|
||||
|
||||
// lives (life), wives (wife)
|
||||
array('sevi', 4, false, true, 'ife'),
|
||||
|
||||
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
|
||||
array('sev', 3, true, true, 'f'),
|
||||
|
||||
// axes (axis), axes (ax), axes (axe)
|
||||
array('sexa', 4, false, false, array('ax', 'axe', 'axis')),
|
||||
|
||||
// indexes (index), matrixes (matrix)
|
||||
array('sex', 3, true, false, 'x'),
|
||||
|
||||
// quizzes (quiz)
|
||||
array('sezz', 4, true, false, 'z'),
|
||||
|
||||
// bureaus (bureau)
|
||||
array('suae', 4, false, true, 'eau'),
|
||||
|
||||
// roses (rose), garages (garage), cassettes (cassette),
|
||||
// waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
|
||||
// shoes (shoe)
|
||||
array('se', 2, true, true, array('', 'e')),
|
||||
|
||||
// tags (tag)
|
||||
array('s', 1, true, true, ''),
|
||||
|
||||
// chateaux (chateau)
|
||||
array('xuae', 4, false, true, 'eau'),
|
||||
);
|
||||
|
||||
/**
|
||||
* This class should not be instantiated
|
||||
*/
|
||||
private function __construct() {}
|
||||
|
||||
/**
|
||||
* Returns the singular form of a word
|
||||
* Alias for {@link StringUtil::singularify()}
|
||||
*
|
||||
* If the method can't determine the form with certainty, an array of the
|
||||
* possible singulars is returned.
|
||||
*
|
||||
* @param string $plural A word in plural form
|
||||
* @return string|array The singular form or an array of possible singular
|
||||
* forms
|
||||
* @deprecated Deprecated since version 2.2, to be removed in 2.3. Use
|
||||
* {@link StringUtil::singularify()} instead.
|
||||
*/
|
||||
public static function singularify($plural)
|
||||
{
|
||||
$pluralRev = strrev($plural);
|
||||
$lowerPluralRev = strtolower($pluralRev);
|
||||
$pluralLength = strlen($lowerPluralRev);
|
||||
trigger_error('\Symfony\Component\Form\Util\FormUtil::singularify() is deprecated since version 2.2 and will be removed in 2.3. Use \Symfony\Component\PropertyAccess\StringUtil::singularify() in the PropertyAccess component instead.', E_USER_DEPRECATED);
|
||||
|
||||
// The outer loop iterates over the entries of the plural table
|
||||
// The inner loop $j iterates over the characters of the plural suffix
|
||||
// in the plural table to compare them with the characters of the actual
|
||||
// given plural suffix
|
||||
foreach (self::$pluralMap as $map) {
|
||||
$suffix = $map[0];
|
||||
$suffixLength = $map[1];
|
||||
$j = 0;
|
||||
|
||||
// Compare characters in the plural table and of the suffix of the
|
||||
// given plural one by one
|
||||
while ($suffix[$j] === $lowerPluralRev[$j]) {
|
||||
// Let $j point to the next character
|
||||
++$j;
|
||||
|
||||
// Successfully compared the last character
|
||||
// Add an entry with the singular suffix to the singular array
|
||||
if ($j === $suffixLength) {
|
||||
// Is there any character preceding the suffix in the plural string?
|
||||
if ($j < $pluralLength) {
|
||||
$nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]);
|
||||
|
||||
if (!$map[2] && $nextIsVocal) {
|
||||
// suffix may not succeed a vocal but next char is one
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$map[3] && !$nextIsVocal) {
|
||||
// suffix may not succeed a consonant but next char is one
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$newBase = substr($plural, 0, $pluralLength - $suffixLength);
|
||||
$newSuffix = $map[4];
|
||||
|
||||
// Check whether the first character in the plural suffix
|
||||
// is uppercased. If yes, uppercase the first character in
|
||||
// the singular suffix too
|
||||
$firstUpper = ctype_upper($pluralRev[$j - 1]);
|
||||
|
||||
if (is_array($newSuffix)) {
|
||||
$singulars = array();
|
||||
|
||||
foreach ($newSuffix as $newSuffixEntry) {
|
||||
$singulars[] = $newBase . ($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
|
||||
}
|
||||
|
||||
return $singulars;
|
||||
}
|
||||
|
||||
return $newBase . ($firstUpper ? ucFirst($newSuffix) : $newSuffix);
|
||||
}
|
||||
|
||||
// Suffix is longer than word
|
||||
if ($j === $pluralLength) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert teeth to tooth, feet to foot
|
||||
if (false !== ($pos = strpos($plural, 'ee'))) {
|
||||
return substr_replace($plural, 'oo', $pos, 2);
|
||||
}
|
||||
|
||||
// Assume that plural and singular is identical
|
||||
return $plural;
|
||||
return StringUtil::singularify($plural);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,619 +11,47 @@
|
||||
|
||||
namespace Symfony\Component\Form\Util;
|
||||
|
||||
use Traversable;
|
||||
use ReflectionClass;
|
||||
use Symfony\Component\Form\Exception\InvalidPropertyPathException;
|
||||
use Symfony\Component\Form\Exception\InvalidPropertyException;
|
||||
use Symfony\Component\Form\Exception\PropertyAccessDeniedException;
|
||||
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\PropertyAccess\PropertyPath as BasePropertyPath;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
|
||||
/**
|
||||
* Allows easy traversing of a property path
|
||||
* Alias for {@link \Symfony\Component\PropertyAccess\PropertyPath}.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated deprecated since version 2.2, to be removed in 2.3. Use
|
||||
* {@link \Symfony\Component\PropertyAccess\PropertyPath}
|
||||
* instead.
|
||||
*/
|
||||
class PropertyPath implements \IteratorAggregate, PropertyPathInterface
|
||||
class PropertyPath extends BasePropertyPath
|
||||
{
|
||||
/**
|
||||
* Character used for separating between plural and singular of an element.
|
||||
* @var string
|
||||
*/
|
||||
const SINGULAR_SEPARATOR = '|';
|
||||
|
||||
const VALUE = 0;
|
||||
const IS_REF = 1;
|
||||
|
||||
/**
|
||||
* The elements of the property path
|
||||
* @var array
|
||||
*/
|
||||
private $elements = array();
|
||||
|
||||
/**
|
||||
* The singular forms of the elements in the property path.
|
||||
* @var array
|
||||
*/
|
||||
private $singulars = array();
|
||||
|
||||
/**
|
||||
* The number of elements in the property path
|
||||
* @var integer
|
||||
*/
|
||||
private $length;
|
||||
|
||||
/**
|
||||
* Contains a Boolean for each property in $elements denoting whether this
|
||||
* element is an index. It is a property otherwise.
|
||||
* @var array
|
||||
*/
|
||||
private $isIndex = array();
|
||||
|
||||
/**
|
||||
* String representation of the path
|
||||
* @var string
|
||||
*/
|
||||
private $pathAsString;
|
||||
|
||||
/**
|
||||
* Constructs a property path from a string.
|
||||
*
|
||||
* @param PropertyPath|string $propertyPath The property path as string or instance.
|
||||
*
|
||||
* @throws UnexpectedTypeException If the given path is not a string.
|
||||
* @throws InvalidPropertyPathException If the syntax of the property path is not valid.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($propertyPath)
|
||||
{
|
||||
// Can be used as copy constructor
|
||||
if ($propertyPath instanceof PropertyPath) {
|
||||
/* @var PropertyPath $propertyPath */
|
||||
$this->elements = $propertyPath->elements;
|
||||
$this->singulars = $propertyPath->singulars;
|
||||
$this->length = $propertyPath->length;
|
||||
$this->isIndex = $propertyPath->isIndex;
|
||||
$this->pathAsString = $propertyPath->pathAsString;
|
||||
parent::__construct($propertyPath);
|
||||
|
||||
return;
|
||||
}
|
||||
if (!is_string($propertyPath)) {
|
||||
throw new UnexpectedTypeException($propertyPath, 'string or Symfony\Component\Form\Util\PropertyPath');
|
||||
}
|
||||
|
||||
if ('' === $propertyPath) {
|
||||
throw new InvalidPropertyPathException('The property path should not be empty.');
|
||||
}
|
||||
|
||||
$this->pathAsString = $propertyPath;
|
||||
$position = 0;
|
||||
$remaining = $propertyPath;
|
||||
|
||||
// first element is evaluated differently - no leading dot for properties
|
||||
$pattern = '/^(([^\.\[]+)|\[([^\]]+)\])(.*)/';
|
||||
|
||||
while (preg_match($pattern, $remaining, $matches)) {
|
||||
if ('' !== $matches[2]) {
|
||||
$element = $matches[2];
|
||||
$this->isIndex[] = false;
|
||||
} else {
|
||||
$element = $matches[3];
|
||||
$this->isIndex[] = true;
|
||||
}
|
||||
// Disabled this behaviour as the syntax is not yet final
|
||||
//$pos = strpos($element, self::SINGULAR_SEPARATOR);
|
||||
$pos = false;
|
||||
$singular = null;
|
||||
|
||||
if (false !== $pos) {
|
||||
$singular = substr($element, $pos + 1);
|
||||
$element = substr($element, 0, $pos);
|
||||
}
|
||||
|
||||
$this->elements[] = $element;
|
||||
$this->singulars[] = $singular;
|
||||
|
||||
$position += strlen($matches[1]);
|
||||
$remaining = $matches[4];
|
||||
$pattern = '/^(\.(\w+)|\[([^\]]+)\])(.*)/';
|
||||
}
|
||||
|
||||
if ('' !== $remaining) {
|
||||
throw new InvalidPropertyPathException(sprintf(
|
||||
'Could not parse property path "%s". Unexpected token "%s" at position %d',
|
||||
$propertyPath,
|
||||
$remaining{0},
|
||||
$position
|
||||
));
|
||||
}
|
||||
|
||||
$this->length = count($this->elements);
|
||||
trigger_error('\Symfony\Component\Form\Util\PropertyPath is deprecated since version 2.2 and will be removed in 2.3. Use \Symfony\Component\PropertyAccess\PropertyPath instead.', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->pathAsString;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLength()
|
||||
{
|
||||
return $this->length;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
if ($this->length <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parent = clone $this;
|
||||
|
||||
--$parent->length;
|
||||
$parent->pathAsString = substr($parent->pathAsString, 0, max(strrpos($parent->pathAsString, '.'), strrpos($parent->pathAsString, '[')));
|
||||
array_pop($parent->elements);
|
||||
array_pop($parent->singulars);
|
||||
array_pop($parent->isIndex);
|
||||
|
||||
return $parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new iterator for this path
|
||||
*
|
||||
* @return PropertyPathIteratorInterface
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new PropertyPathIterator($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getElements()
|
||||
{
|
||||
return $this->elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getElement($index)
|
||||
{
|
||||
if (!isset($this->elements[$index])) {
|
||||
throw new \OutOfBoundsException('The index ' . $index . ' is not within the property path');
|
||||
}
|
||||
|
||||
return $this->elements[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isProperty($index)
|
||||
{
|
||||
if (!isset($this->isIndex[$index])) {
|
||||
throw new \OutOfBoundsException('The index ' . $index . ' is not within the property path');
|
||||
}
|
||||
|
||||
return !$this->isIndex[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isIndex($index)
|
||||
{
|
||||
if (!isset($this->isIndex[$index])) {
|
||||
throw new \OutOfBoundsException('The index ' . $index . ' is not within the property path');
|
||||
}
|
||||
|
||||
return $this->isIndex[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value at the end of the property path of the object
|
||||
*
|
||||
* Example:
|
||||
* <code>
|
||||
* $path = new PropertyPath('child.name');
|
||||
*
|
||||
* echo $path->getValue($object);
|
||||
* // equals echo $object->getChild()->getName();
|
||||
* </code>
|
||||
*
|
||||
* This method first tries to find a public getter for each property in the
|
||||
* path. The name of the getter must be the camel-cased property name
|
||||
* prefixed with "get", "is", or "has".
|
||||
*
|
||||
* If the getter does not exist, this method tries to find a public
|
||||
* property. The value of the property is then returned.
|
||||
*
|
||||
* If none of them are found, an exception is thrown.
|
||||
*
|
||||
* @param object|array $objectOrArray The object or array to traverse
|
||||
*
|
||||
* @return mixed The value at the end of the property path
|
||||
*
|
||||
* @throws InvalidPropertyException If the property/getter does not exist
|
||||
* @throws PropertyAccessDeniedException If the property/getter exists but is not public
|
||||
* Alias for {@link PropertyAccessor::getValue()}
|
||||
*/
|
||||
public function getValue($objectOrArray)
|
||||
{
|
||||
$propertyValues =& $this->readPropertiesUntil($objectOrArray, $this->length - 1);
|
||||
$propertyAccessor = PropertyAccess::getPropertyAccessor();
|
||||
|
||||
return $propertyValues[count($propertyValues) - 1][self::VALUE];
|
||||
return $propertyAccessor->getValue($objectOrArray, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value at the end of the property path of the object
|
||||
*
|
||||
* Example:
|
||||
* <code>
|
||||
* $path = new PropertyPath('child.name');
|
||||
*
|
||||
* echo $path->setValue($object, 'Fabien');
|
||||
* // equals echo $object->getChild()->setName('Fabien');
|
||||
* </code>
|
||||
*
|
||||
* This method first tries to find a public setter for each property in the
|
||||
* path. The name of the setter must be the camel-cased property name
|
||||
* prefixed with "set".
|
||||
*
|
||||
* If the setter does not exist, this method tries to find a public
|
||||
* property. The value of the property is then changed.
|
||||
*
|
||||
* If neither is found, an exception is thrown.
|
||||
*
|
||||
* @param object|array $objectOrArray The object or array to modify.
|
||||
* @param mixed $value The value to set at the end of the property path.
|
||||
*
|
||||
* @throws InvalidPropertyException If a property does not exist.
|
||||
* @throws PropertyAccessDeniedException If a property cannot be accessed due to
|
||||
* access restrictions (private or protected).
|
||||
* @throws UnexpectedTypeException If a value within the path is neither object
|
||||
* nor array.
|
||||
* Alias for {@link PropertyAccessor::setValue()}
|
||||
*/
|
||||
public function setValue(&$objectOrArray, $value)
|
||||
public function setValue($objectOrArray, $value)
|
||||
{
|
||||
$propertyValues =& $this->readPropertiesUntil($objectOrArray, $this->length - 2);
|
||||
$overwrite = true;
|
||||
$propertyAccessor = PropertyAccess::getPropertyAccessor();
|
||||
|
||||
// Add the root object to the list
|
||||
array_unshift($propertyValues, array(
|
||||
self::VALUE => &$objectOrArray,
|
||||
self::IS_REF => true,
|
||||
));
|
||||
|
||||
for ($i = count($propertyValues) - 1; $i >= 0; --$i) {
|
||||
$objectOrArray =& $propertyValues[$i][self::VALUE];
|
||||
|
||||
if ($overwrite) {
|
||||
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
|
||||
throw new UnexpectedTypeException($objectOrArray, 'object or array');
|
||||
}
|
||||
|
||||
$property = $this->elements[$i];
|
||||
$singular = $this->singulars[$i];
|
||||
$isIndex = $this->isIndex[$i];
|
||||
|
||||
$this->writeProperty($objectOrArray, $property, $singular, $isIndex, $value);
|
||||
}
|
||||
|
||||
$value =& $objectOrArray;
|
||||
$overwrite = !$propertyValues[$i][self::IS_REF];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the path from an object up to a given path index.
|
||||
*
|
||||
* @param object|array $objectOrArray The object or array to read from.
|
||||
* @param integer $lastIndex The integer up to which should be read.
|
||||
*
|
||||
* @return array The values read in the path.
|
||||
*
|
||||
* @throws UnexpectedTypeException If a value within the path is neither object nor array.
|
||||
*/
|
||||
private function &readPropertiesUntil(&$objectOrArray, $lastIndex)
|
||||
{
|
||||
$propertyValues = array();
|
||||
|
||||
for ($i = 0; $i <= $lastIndex; ++$i) {
|
||||
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
|
||||
throw new UnexpectedTypeException($objectOrArray, 'object or array');
|
||||
}
|
||||
|
||||
$property = $this->elements[$i];
|
||||
$isIndex = $this->isIndex[$i];
|
||||
$isArrayAccess = is_array($objectOrArray) || $objectOrArray instanceof \ArrayAccess;
|
||||
|
||||
// Create missing nested arrays on demand
|
||||
if ($isIndex && $isArrayAccess && !isset($objectOrArray[$property])) {
|
||||
$objectOrArray[$property] = $i + 1 < $this->length ? array() : null;
|
||||
}
|
||||
|
||||
$propertyValue =& $this->readProperty($objectOrArray, $property, $isIndex);
|
||||
$objectOrArray =& $propertyValue[self::VALUE];
|
||||
|
||||
$propertyValues[] =& $propertyValue;
|
||||
}
|
||||
|
||||
return $propertyValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the a property from an object or array.
|
||||
*
|
||||
* @param object|array $objectOrArray The object or array to read from.
|
||||
* @param string $property The property to read.
|
||||
* @param Boolean $isIndex Whether to interpret the property as index.
|
||||
*
|
||||
* @return mixed The value of the read property
|
||||
*
|
||||
* @throws InvalidPropertyException If the property does not exist.
|
||||
* @throws PropertyAccessDeniedException If the property cannot be accessed due to
|
||||
* access restrictions (private or protected).
|
||||
*/
|
||||
private function &readProperty(&$objectOrArray, $property, $isIndex)
|
||||
{
|
||||
// Use an array instead of an object since performance is
|
||||
// very crucial here
|
||||
$result = array(
|
||||
self::VALUE => null,
|
||||
self::IS_REF => false
|
||||
);
|
||||
|
||||
if ($isIndex) {
|
||||
if (!$objectOrArray instanceof \ArrayAccess && !is_array($objectOrArray)) {
|
||||
throw new InvalidPropertyException(sprintf('Index "%s" cannot be read from object of type "%s" because it doesn\'t implement \ArrayAccess', $property, get_class($objectOrArray)));
|
||||
}
|
||||
|
||||
if (isset($objectOrArray[$property])) {
|
||||
if (is_array($objectOrArray)) {
|
||||
$result[self::VALUE] =& $objectOrArray[$property];
|
||||
$result[self::IS_REF] = true;
|
||||
} else {
|
||||
$result[self::VALUE] = $objectOrArray[$property];
|
||||
}
|
||||
}
|
||||
} elseif (is_object($objectOrArray)) {
|
||||
$camelProp = $this->camelize($property);
|
||||
$reflClass = new ReflectionClass($objectOrArray);
|
||||
$getter = 'get'.$camelProp;
|
||||
$isser = 'is'.$camelProp;
|
||||
$hasser = 'has'.$camelProp;
|
||||
|
||||
if ($reflClass->hasMethod($getter)) {
|
||||
if (!$reflClass->getMethod($getter)->isPublic()) {
|
||||
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $getter, $reflClass->name));
|
||||
}
|
||||
|
||||
$result[self::VALUE] = $objectOrArray->$getter();
|
||||
} elseif ($reflClass->hasMethod($isser)) {
|
||||
if (!$reflClass->getMethod($isser)->isPublic()) {
|
||||
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $isser, $reflClass->name));
|
||||
}
|
||||
|
||||
$result[self::VALUE] = $objectOrArray->$isser();
|
||||
} elseif ($reflClass->hasMethod($hasser)) {
|
||||
if (!$reflClass->getMethod($hasser)->isPublic()) {
|
||||
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $hasser, $reflClass->name));
|
||||
}
|
||||
|
||||
$result[self::VALUE] = $objectOrArray->$hasser();
|
||||
} elseif ($reflClass->hasMethod('__get')) {
|
||||
// needed to support magic method __get
|
||||
$result[self::VALUE] = $objectOrArray->$property;
|
||||
} elseif ($reflClass->hasProperty($property)) {
|
||||
if (!$reflClass->getProperty($property)->isPublic()) {
|
||||
throw new PropertyAccessDeniedException(sprintf('Property "%s" is not public in class "%s". Maybe you should create the method "%s()" or "%s()" or "%s()"?', $property, $reflClass->name, $getter, $isser, $hasser));
|
||||
}
|
||||
|
||||
$result[self::VALUE] =& $objectOrArray->$property;
|
||||
$result[self::IS_REF] = true;
|
||||
} elseif (property_exists($objectOrArray, $property)) {
|
||||
// needed to support \stdClass instances
|
||||
$result[self::VALUE] =& $objectOrArray->$property;
|
||||
$result[self::IS_REF] = true;
|
||||
} else {
|
||||
throw new InvalidPropertyException(sprintf('Neither property "%s" nor method "%s()" nor method "%s()" exists in class "%s"', $property, $getter, $isser, $reflClass->name));
|
||||
}
|
||||
} else {
|
||||
throw new InvalidPropertyException(sprintf('Cannot read property "%s" from an array. Maybe you should write the property path as "[%s]" instead?', $property, $property));
|
||||
}
|
||||
|
||||
// Objects are always passed around by reference
|
||||
if (is_object($result[self::VALUE])) {
|
||||
$result[self::IS_REF] = true;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the property at the given index in the path
|
||||
*
|
||||
* @param object|array $objectOrArray The object or array to write to.
|
||||
* @param string $property The property to write.
|
||||
* @param string|null $singular The singular form of the property name or null.
|
||||
* @param Boolean $isIndex Whether to interpret the property as index.
|
||||
* @param mixed $value The value to write.
|
||||
*
|
||||
* @throws InvalidPropertyException If the property does not exist.
|
||||
* @throws PropertyAccessDeniedException If the property cannot be accessed due to
|
||||
* access restrictions (private or protected).
|
||||
*/
|
||||
private function writeProperty(&$objectOrArray, $property, $singular, $isIndex, $value)
|
||||
{
|
||||
$adderRemoverError = null;
|
||||
|
||||
if ($isIndex) {
|
||||
if (!$objectOrArray instanceof \ArrayAccess && !is_array($objectOrArray)) {
|
||||
throw new InvalidPropertyException(sprintf('Index "%s" cannot be modified in object of type "%s" because it doesn\'t implement \ArrayAccess', $property, get_class($objectOrArray)));
|
||||
}
|
||||
|
||||
$objectOrArray[$property] = $value;
|
||||
} elseif (is_object($objectOrArray)) {
|
||||
$reflClass = new ReflectionClass($objectOrArray);
|
||||
|
||||
// The plural form is the last element of the property path
|
||||
$plural = $this->camelize($this->elements[$this->length - 1]);
|
||||
|
||||
// Any of the two methods is required, but not yet known
|
||||
$singulars = null !== $singular ? array($singular) : (array) FormUtil::singularify($plural);
|
||||
|
||||
if (is_array($value) || $value instanceof Traversable) {
|
||||
$methods = $this->findAdderAndRemover($reflClass, $singulars);
|
||||
if (null !== $methods) {
|
||||
// At this point the add and remove methods have been found
|
||||
// Use iterator_to_array() instead of clone in order to prevent side effects
|
||||
// see https://github.com/symfony/symfony/issues/4670
|
||||
$itemsToAdd = is_object($value) ? iterator_to_array($value) : $value;
|
||||
$itemToRemove = array();
|
||||
$propertyValue = $this->readProperty($objectOrArray, $property, $isIndex);
|
||||
$previousValue = $propertyValue[self::VALUE];
|
||||
|
||||
if (is_array($previousValue) || $previousValue instanceof Traversable) {
|
||||
foreach ($previousValue as $previousItem) {
|
||||
foreach ($value as $key => $item) {
|
||||
if ($item === $previousItem) {
|
||||
// Item found, don't add
|
||||
unset($itemsToAdd[$key]);
|
||||
|
||||
// Next $previousItem
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Item not found, add to remove list
|
||||
$itemToRemove[] = $previousItem;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($itemToRemove as $item) {
|
||||
call_user_func(array($objectOrArray, $methods[1]), $item);
|
||||
}
|
||||
|
||||
foreach ($itemsToAdd as $item) {
|
||||
call_user_func(array($objectOrArray, $methods[0]), $item);
|
||||
}
|
||||
|
||||
return;
|
||||
} else {
|
||||
$adderRemoverError = ', nor could adders and removers be found based on the ';
|
||||
if (null === $singular) {
|
||||
// $adderRemoverError .= 'guessed singulars: '.implode(', ', $singulars).' (provide a singular by suffixing the property path with "|{singular}" to override the guesser)';
|
||||
$adderRemoverError .= 'guessed singulars: '.implode(', ', $singulars);
|
||||
} else {
|
||||
$adderRemoverError .= 'passed singular: '.$singular;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$setter = 'set'.$this->camelize($property);
|
||||
if ($reflClass->hasMethod($setter)) {
|
||||
if (!$reflClass->getMethod($setter)->isPublic()) {
|
||||
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $setter, $reflClass->name));
|
||||
}
|
||||
|
||||
$objectOrArray->$setter($value);
|
||||
} elseif ($reflClass->hasMethod('__set')) {
|
||||
// needed to support magic method __set
|
||||
$objectOrArray->$property = $value;
|
||||
} elseif ($reflClass->hasProperty($property)) {
|
||||
if (!$reflClass->getProperty($property)->isPublic()) {
|
||||
throw new PropertyAccessDeniedException(sprintf('Property "%s" is not public in class "%s"%s. Maybe you should create the method "%s()"?', $property, $reflClass->name, $adderRemoverError, $setter));
|
||||
}
|
||||
|
||||
$objectOrArray->$property = $value;
|
||||
} elseif (property_exists($objectOrArray, $property)) {
|
||||
// needed to support \stdClass instances
|
||||
$objectOrArray->$property = $value;
|
||||
} else {
|
||||
throw new InvalidPropertyException(sprintf('Neither element "%s" nor method "%s()" exists in class "%s"%s', $property, $setter, $reflClass->name, $adderRemoverError));
|
||||
}
|
||||
} else {
|
||||
throw new InvalidPropertyException(sprintf('Cannot write property "%s" in an array. Maybe you should write the property path as "[%s]" instead?', $property, $property));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Camelizes a given string.
|
||||
*
|
||||
* @param string $string Some string.
|
||||
*
|
||||
* @return string The camelized version of the string.
|
||||
*/
|
||||
private function camelize($string)
|
||||
{
|
||||
return preg_replace_callback('/(^|_|\.)+(.)/', function ($match) { return ('.' === $match[1] ? '_' : '').strtoupper($match[2]); }, $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for add and remove methods.
|
||||
*
|
||||
* @param \ReflectionClass $reflClass The reflection class for the given object
|
||||
* @param array $singulars The singular form of the property name or null.
|
||||
*
|
||||
* @return array|null An array containing the adder and remover when found, null otherwise.
|
||||
*
|
||||
* @throws InvalidPropertyException If the property does not exist.
|
||||
*/
|
||||
private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars)
|
||||
{
|
||||
foreach ($singulars as $singular) {
|
||||
$addMethod = 'add' . $singular;
|
||||
$removeMethod = 'remove' . $singular;
|
||||
|
||||
$addMethodFound = $this->isAccessible($reflClass, $addMethod, 1);
|
||||
$removeMethodFound = $this->isAccessible($reflClass, $removeMethod, 1);
|
||||
|
||||
if ($addMethodFound && $removeMethodFound) {
|
||||
return array($addMethod, $removeMethod);
|
||||
}
|
||||
|
||||
if ($addMethodFound xor $removeMethodFound) {
|
||||
throw new InvalidPropertyException(sprintf(
|
||||
'Found the public method "%s", but did not find a public "%s" on class %s',
|
||||
$addMethodFound ? $addMethod : $removeMethod,
|
||||
$addMethodFound ? $removeMethod : $addMethod,
|
||||
$reflClass->name
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a method is public and has a specific number of required parameters.
|
||||
*
|
||||
* @param \ReflectionClass $class The class of the method.
|
||||
* @param string $methodName The method name.
|
||||
* @param integer $parameters The number of parameters.
|
||||
*
|
||||
* @return Boolean Whether the method is public and has $parameters
|
||||
* required parameters.
|
||||
*/
|
||||
private function isAccessible(ReflectionClass $class, $methodName, $parameters)
|
||||
{
|
||||
if ($class->hasMethod($methodName)) {
|
||||
$method = $class->getMethod($methodName);
|
||||
|
||||
if ($method->isPublic() && $method->getNumberOfRequiredParameters() === $parameters) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return $propertyAccessor->getValue($objectOrArray, $this, $value);
|
||||
}
|
||||
}
|
||||
|
@ -11,284 +11,26 @@
|
||||
|
||||
namespace Symfony\Component\Form\Util;
|
||||
|
||||
use Symfony\Component\PropertyAccess\PropertyPathBuilder as BasePropertyPathBuilder;
|
||||
|
||||
/**
|
||||
* Alias for {@link \Symfony\Component\PropertyAccess\PropertyPathBuilder}.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated deprecated since version 2.2, to be removed in 2.3. Use
|
||||
* {@link \Symfony\Component\PropertyAccess\PropertyPathBuilder}
|
||||
* instead.
|
||||
*/
|
||||
class PropertyPathBuilder
|
||||
class PropertyPathBuilder extends BasePropertyPathBuilder
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
private $elements = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $isIndex = array();
|
||||
|
||||
/**
|
||||
* Creates a new property path builder.
|
||||
*
|
||||
* @param null|PropertyPathInterface $path The path to initially store
|
||||
* in the builder. Optional.
|
||||
*/
|
||||
public function __construct(PropertyPathInterface $path = null)
|
||||
public function __construct($propertyPath)
|
||||
{
|
||||
if (null !== $path) {
|
||||
$this->append($path);
|
||||
}
|
||||
}
|
||||
parent::__construct($propertyPath);
|
||||
|
||||
/**
|
||||
* Appends a (sub-) path to the current path.
|
||||
*
|
||||
* @param PropertyPathInterface $path The path to append.
|
||||
* @param integer $offset The offset where the appended piece
|
||||
* starts in $path.
|
||||
* @param integer $length The length of the appended piece.
|
||||
* If 0, the full path is appended.
|
||||
*/
|
||||
public function append(PropertyPathInterface $path, $offset = 0, $length = 0)
|
||||
{
|
||||
if (0 === $length) {
|
||||
$end = $path->getLength();
|
||||
} else {
|
||||
$end = $offset + $length;
|
||||
}
|
||||
|
||||
for (; $offset < $end; ++$offset) {
|
||||
$this->elements[] = $path->getElement($offset);
|
||||
$this->isIndex[] = $path->isIndex($offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an index element to the current path.
|
||||
*
|
||||
* @param string $name The name of the appended index.
|
||||
*/
|
||||
public function appendIndex($name)
|
||||
{
|
||||
$this->elements[] = $name;
|
||||
$this->isIndex[] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a property element to the current path.
|
||||
*
|
||||
* @param string $name The name of the appended property.
|
||||
*/
|
||||
public function appendProperty($name)
|
||||
{
|
||||
$this->elements[] = $name;
|
||||
$this->isIndex[] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes elements from the current path.
|
||||
*
|
||||
* @param integer $offset The offset at which to remove.
|
||||
* @param integer $length The length of the removed piece.
|
||||
*
|
||||
* @throws \OutOfBoundsException if offset is invalid
|
||||
*/
|
||||
public function remove($offset, $length = 1)
|
||||
{
|
||||
if (!isset($this->elements[$offset])) {
|
||||
throw new \OutOfBoundsException('The offset ' . $offset . ' is not within the property path');
|
||||
}
|
||||
|
||||
$this->resize($offset, $length, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces a sub-path by a different (sub-) path.
|
||||
*
|
||||
* @param integer $offset The offset at which to replace.
|
||||
* @param integer $length The length of the piece to replace.
|
||||
* @param PropertyPathInterface $path The path to insert.
|
||||
* @param integer $pathOffset The offset where the inserted piece
|
||||
* starts in $path.
|
||||
* @param integer $pathLength The length of the inserted piece.
|
||||
* If 0, the full path is inserted.
|
||||
*
|
||||
* @throws \OutOfBoundsException If the offset is invalid.
|
||||
*/
|
||||
public function replace($offset, $length, PropertyPathInterface $path, $pathOffset = 0, $pathLength = 0)
|
||||
{
|
||||
if (!isset($this->elements[$offset])) {
|
||||
throw new \OutOfBoundsException('The offset ' . $offset . ' is not within the property path');
|
||||
}
|
||||
|
||||
if (0 === $pathLength) {
|
||||
$pathLength = $path->getLength() - $pathOffset;
|
||||
}
|
||||
|
||||
$this->resize($offset, $length, $pathLength);
|
||||
|
||||
for ($i = 0; $i < $pathLength; ++$i) {
|
||||
$this->elements[$offset + $i] = $path->getElement($pathOffset + $i);
|
||||
$this->isIndex[$offset + $i] = $path->isIndex($pathOffset + $i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces a property element by an index element.
|
||||
*
|
||||
* @param integer $offset The offset at which to replace.
|
||||
* @param string $name The new name of the element. Optional.
|
||||
*
|
||||
* @throws \OutOfBoundsException If the offset is invalid.
|
||||
*/
|
||||
public function replaceByIndex($offset, $name = null)
|
||||
{
|
||||
if (!isset($this->elements[$offset])) {
|
||||
throw new \OutOfBoundsException('The offset ' . $offset . ' is not within the property path');
|
||||
}
|
||||
|
||||
if (null !== $name) {
|
||||
$this->elements[$offset] = $name;
|
||||
}
|
||||
|
||||
$this->isIndex[$offset] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an index element by a property element.
|
||||
*
|
||||
* @param integer $offset The offset at which to replace.
|
||||
* @param string $name The new name of the element. Optional.
|
||||
*
|
||||
* @throws \OutOfBoundsException If the offset is invalid.
|
||||
*/
|
||||
public function replaceByProperty($offset, $name = null)
|
||||
{
|
||||
if (!isset($this->elements[$offset])) {
|
||||
throw new \OutOfBoundsException('The offset ' . $offset . ' is not within the property path');
|
||||
}
|
||||
|
||||
if (null !== $name) {
|
||||
$this->elements[$offset] = $name;
|
||||
}
|
||||
|
||||
$this->isIndex[$offset] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the current path.
|
||||
*
|
||||
* @return integer The path length.
|
||||
*/
|
||||
public function getLength()
|
||||
{
|
||||
return count($this->elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current property path.
|
||||
*
|
||||
* @return PropertyPathInterface The constructed property path.
|
||||
*/
|
||||
public function getPropertyPath()
|
||||
{
|
||||
$pathAsString = $this->__toString();
|
||||
|
||||
return '' !== $pathAsString ? new PropertyPath($pathAsString) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current property path as string.
|
||||
*
|
||||
* @return string The property path as string.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$string = '';
|
||||
|
||||
foreach ($this->elements as $offset => $element) {
|
||||
if ($this->isIndex[$offset]) {
|
||||
$element = '[' . $element . ']';
|
||||
} elseif ('' !== $string) {
|
||||
$string .= '.';
|
||||
}
|
||||
|
||||
$string .= $element;
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the path so that a chunk of length $cutLength is
|
||||
* removed at $offset and another chunk of length $insertionLength
|
||||
* can be inserted.
|
||||
*
|
||||
* @param integer $offset The offset where the removed chunk starts.
|
||||
* @param integer $cutLength The length of the removed chunk.
|
||||
* @param integer $insertionLength The length of the inserted chunk.
|
||||
*/
|
||||
private function resize($offset, $cutLength, $insertionLength)
|
||||
{
|
||||
// Nothing else to do in this case
|
||||
if ($insertionLength === $cutLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
$length = count($this->elements);
|
||||
|
||||
if ($cutLength > $insertionLength) {
|
||||
// More elements should be removed than inserted
|
||||
$diff = $cutLength - $insertionLength;
|
||||
$newLength = $length - $diff;
|
||||
|
||||
// Shift elements to the left (left-to-right until the new end)
|
||||
// Max allowed offset to be shifted is such that
|
||||
// $offset + $diff < $length (otherwise invalid index access)
|
||||
// i.e. $offset < $length - $diff = $newLength
|
||||
for ($i = $offset; $i < $newLength; ++$i) {
|
||||
$this->elements[$i] = $this->elements[$i + $diff];
|
||||
$this->isIndex[$i] = $this->isIndex[$i + $diff];
|
||||
}
|
||||
|
||||
// All remaining elements should be removed
|
||||
for (; $i < $length; ++$i) {
|
||||
unset($this->elements[$i]);
|
||||
unset($this->isIndex[$i]);
|
||||
}
|
||||
} else {
|
||||
$diff = $insertionLength - $cutLength;
|
||||
|
||||
$newLength = $length + $diff;
|
||||
$indexAfterInsertion = $offset + $insertionLength;
|
||||
|
||||
// $diff <= $insertionLength
|
||||
// $indexAfterInsertion >= $insertionLength
|
||||
// => $diff <= $indexAfterInsertion
|
||||
|
||||
// In each of the following loops, $i >= $diff must hold,
|
||||
// otherwise ($i - $diff) becomes negative.
|
||||
|
||||
// Shift old elements to the right to make up space for the
|
||||
// inserted elements. This needs to be done left-to-right in
|
||||
// order to preserve an ascending array index order
|
||||
// Since $i = max($length, $indexAfterInsertion) and $indexAfterInsertion >= $diff,
|
||||
// $i >= $diff is guaranteed.
|
||||
for ($i = max($length, $indexAfterInsertion); $i < $newLength; ++$i) {
|
||||
$this->elements[$i] = $this->elements[$i - $diff];
|
||||
$this->isIndex[$i] = $this->isIndex[$i - $diff];
|
||||
}
|
||||
|
||||
// Shift remaining elements to the right. Do this right-to-left
|
||||
// so we don't overwrite elements before copying them
|
||||
// The last written index is the immediate index after the inserted
|
||||
// string, because the indices before that will be overwritten
|
||||
// anyway.
|
||||
// Since $i >= $indexAfterInsertion and $indexAfterInsertion >= $diff,
|
||||
// $i >= $diff is guaranteed.
|
||||
for ($i = $length - 1; $i >= $indexAfterInsertion; --$i) {
|
||||
$this->elements[$i] = $this->elements[$i - $diff];
|
||||
$this->isIndex[$i] = $this->isIndex[$i - $diff];
|
||||
}
|
||||
}
|
||||
trigger_error('\Symfony\Component\Form\Util\PropertyPathBuilder is deprecated since version 2.2 and will be removed in 2.3. Use \Symfony\Component\PropertyAccess\PropertyPathBuilder instead.', E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
|
@ -11,74 +11,17 @@
|
||||
|
||||
namespace Symfony\Component\Form\Util;
|
||||
|
||||
use Symfony\Component\PropertyAccess\PropertyPathInterface as BasePropertyPathInterface;
|
||||
|
||||
/**
|
||||
* Alias for {@link \Symfony\Component\PropertyAccess\PropertyPathInterface}.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated deprecated since version 2.2, to be removed in 2.3. Use
|
||||
* {@link \Symfony\Component\PropertyAccess\PropertyPathInterface}
|
||||
* instead.
|
||||
*/
|
||||
interface PropertyPathInterface extends \Traversable
|
||||
interface PropertyPathInterface extends BasePropertyPathInterface
|
||||
{
|
||||
/**
|
||||
* Returns the string representation of the property path
|
||||
*
|
||||
* @return string The path as string.
|
||||
*/
|
||||
public function __toString();
|
||||
|
||||
/**
|
||||
* Returns the length of the property path, i.e. the number of elements.
|
||||
*
|
||||
* @return integer The path length.
|
||||
*/
|
||||
public function getLength();
|
||||
|
||||
/**
|
||||
* Returns the parent property path.
|
||||
*
|
||||
* The parent property path is the one that contains the same items as
|
||||
* this one except for the last one.
|
||||
*
|
||||
* If this property path only contains one item, null is returned.
|
||||
*
|
||||
* @return PropertyPath The parent path or null.
|
||||
*/
|
||||
public function getParent();
|
||||
|
||||
/**
|
||||
* Returns the elements of the property path as array
|
||||
*
|
||||
* @return array An array of property/index names
|
||||
*/
|
||||
public function getElements();
|
||||
|
||||
/**
|
||||
* Returns the element at the given index in the property path
|
||||
*
|
||||
* @param integer $index The index key
|
||||
*
|
||||
* @return string A property or index name
|
||||
*
|
||||
* @throws \OutOfBoundsException If the offset is invalid.
|
||||
*/
|
||||
public function getElement($index);
|
||||
|
||||
/**
|
||||
* Returns whether the element at the given index is a property
|
||||
*
|
||||
* @param integer $index The index in the property path
|
||||
*
|
||||
* @return Boolean Whether the element at this index is a property
|
||||
*
|
||||
* @throws \OutOfBoundsException If the offset is invalid.
|
||||
*/
|
||||
public function isProperty($index);
|
||||
|
||||
/**
|
||||
* Returns whether the element at the given index is an array index
|
||||
*
|
||||
* @param integer $index The index in the property path
|
||||
*
|
||||
* @return Boolean Whether the element at this index is an array index
|
||||
*
|
||||
* @throws \OutOfBoundsException If the offset is invalid.
|
||||
*/
|
||||
public function isIndex($index);
|
||||
}
|
||||
|
@ -11,45 +11,26 @@
|
||||
|
||||
namespace Symfony\Component\Form\Util;
|
||||
|
||||
use Symfony\Component\PropertyAccess\PropertyPathIterator as BasePropertyPathIterator;
|
||||
|
||||
/**
|
||||
* Traverses a property path and provides additional methods to find out
|
||||
* information about the current element
|
||||
* Alias for {@link \Symfony\Component\PropertyAccess\PropertyPathIterator}.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated deprecated since version 2.2, to be removed in 2.3. Use
|
||||
* {@link \Symfony\Component\PropertyAccess\PropertyPathIterator}
|
||||
* instead.
|
||||
*/
|
||||
class PropertyPathIterator extends \ArrayIterator implements PropertyPathIteratorInterface
|
||||
class PropertyPathIterator extends BasePropertyPathIterator
|
||||
{
|
||||
/**
|
||||
* The traversed property path
|
||||
* @var PropertyPathInterface
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param PropertyPathInterface $path The property path to traverse
|
||||
*/
|
||||
public function __construct(PropertyPathInterface $path)
|
||||
{
|
||||
parent::__construct($path->getElements());
|
||||
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isIndex()
|
||||
public function __construct($propertyPath)
|
||||
{
|
||||
return $this->path->isIndex($this->key());
|
||||
}
|
||||
parent::__construct($propertyPath);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isProperty()
|
||||
{
|
||||
return $this->path->isProperty($this->key());
|
||||
trigger_error('\Symfony\Component\Form\Util\PropertyPathIterator is deprecated since version 2.2 and will be removed in 2.3. Use \Symfony\Component\PropertyAccess\PropertyPathIterator instead.', E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
|
@ -11,24 +11,17 @@
|
||||
|
||||
namespace Symfony\Component\Form\Util;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
interface PropertyPathIteratorInterface extends \Iterator, \SeekableIterator
|
||||
{
|
||||
/**
|
||||
* Returns whether the current element in the property path is an array
|
||||
* index.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isIndex();
|
||||
use Symfony\Component\PropertyAccess\PropertyPathIteratorInterface as BasePropertyPathIteratorInterface;
|
||||
|
||||
/**
|
||||
* Returns whether the current element in the property path is a property
|
||||
* name.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isProperty();
|
||||
/**
|
||||
* Alias for {@link \Symfony\Component\PropertyAccess\PropertyPathIteratorInterface}.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated deprecated since version 2.2, to be removed in 2.3. Use
|
||||
* {@link \Symfony\Component\PropertyAccess\PropertyPathIterator}
|
||||
* instead.
|
||||
*/
|
||||
interface PropertyPathIteratorInterface extends BasePropertyPathIteratorInterface
|
||||
{
|
||||
}
|
||||
|
@ -19,7 +19,8 @@
|
||||
"php": ">=5.3.3",
|
||||
"symfony/event-dispatcher": "2.2.*",
|
||||
"symfony/locale": "2.2.*",
|
||||
"symfony/options-resolver": "2.2.*"
|
||||
"symfony/options-resolver": "2.2.*",
|
||||
"symfony/property-access": "2.2.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/validator": "2.2.*",
|
||||
|
2
src/Symfony/Component/PropertyAccess/.gitattributes
vendored
Normal file
2
src/Symfony/Component/PropertyAccess/.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/Tests export-ignore
|
||||
phpunit.xml.dist export-ignore
|
4
src/Symfony/Component/PropertyAccess/.gitignore
vendored
Normal file
4
src/Symfony/Component/PropertyAccess/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
vendor/
|
||||
composer.lock
|
||||
phpunit.xml
|
||||
|
@ -9,8 +9,13 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Exception;
|
||||
namespace Symfony\Component\PropertyAccess\Exception;
|
||||
|
||||
class InvalidPropertyException extends Exception
|
||||
/**
|
||||
* Marker interface for the PropertyAccess component.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
interface ExceptionInterface
|
||||
{
|
||||
}
|
@ -9,8 +9,13 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Exception;
|
||||
namespace Symfony\Component\PropertyAccess\Exception;
|
||||
|
||||
class PropertyAccessDeniedException extends Exception
|
||||
/**
|
||||
* Thrown when a property path is malformed.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class InvalidPropertyPathException extends RuntimeException
|
||||
{
|
||||
}
|
@ -9,8 +9,13 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Exception;
|
||||
namespace Symfony\Component\PropertyAccess\Exception;
|
||||
|
||||
class InvalidPropertyPathException extends Exception
|
||||
/**
|
||||
* Thrown when a property cannot be found.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class NoSuchPropertyException extends RuntimeException
|
||||
{
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?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\Exception;
|
||||
|
||||
/**
|
||||
* Base OutOfBoundsException for the PropertyAccess component.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface
|
||||
{
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?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\Exception;
|
||||
|
||||
/**
|
||||
* Thrown when a property cannot be accessed because it is not public.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class PropertyAccessDeniedException extends RuntimeException
|
||||
{
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?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\Exception;
|
||||
|
||||
/**
|
||||
* Base RuntimeException for the PropertyAccess component.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?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\Exception;
|
||||
|
||||
/**
|
||||
* Thrown when a value does not match an expected type.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class UnexpectedTypeException extends RuntimeException
|
||||
{
|
||||
public function __construct($value, $expectedType)
|
||||
{
|
||||
parent::__construct(sprintf('Expected argument of type "%s", "%s" given', $expectedType, is_object($value) ? get_class($value) : gettype($value)));
|
||||
}
|
||||
}
|
19
src/Symfony/Component/PropertyAccess/LICENSE
Normal file
19
src/Symfony/Component/PropertyAccess/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2004-2013 Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
37
src/Symfony/Component/PropertyAccess/PropertyAccess.php
Normal file
37
src/Symfony/Component/PropertyAccess/PropertyAccess.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Entry point of the PropertyAccess component.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
final class PropertyAccess
|
||||
{
|
||||
/**
|
||||
* Creates a property accessor with the default configuration.
|
||||
*
|
||||
* @return PropertyAccessor The new property accessor.
|
||||
*/
|
||||
public static function getPropertyAccessor()
|
||||
{
|
||||
return new PropertyAccessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* This class cannot be instantiated.
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
}
|
399
src/Symfony/Component/PropertyAccess/PropertyAccessor.php
Normal file
399
src/Symfony/Component/PropertyAccess/PropertyAccessor.php
Normal file
@ -0,0 +1,399 @@
|
||||
<?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;
|
||||
|
||||
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
|
||||
use Symfony\Component\PropertyAccess\Exception\PropertyAccessDeniedException;
|
||||
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link PropertyAccessorInterface}.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class PropertyAccessor implements PropertyAccessorInterface
|
||||
{
|
||||
const VALUE = 0;
|
||||
const IS_REF = 1;
|
||||
|
||||
/**
|
||||
* Should not be used by application code. Use
|
||||
* {@link PropertyAccess::getPropertyAccessor()} instead.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getValue($objectOrArray, $propertyPath)
|
||||
{
|
||||
if (is_string($propertyPath)) {
|
||||
$propertyPath = new PropertyPath($propertyPath);
|
||||
} elseif (!$propertyPath instanceof PropertyPathInterface) {
|
||||
throw new UnexpectedTypeException($propertyPath, 'string or Symfony\Component\PropertyAccess\PropertyPathInterface');
|
||||
}
|
||||
|
||||
$propertyValues =& $this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength() - 1);
|
||||
|
||||
return $propertyValues[count($propertyValues) - 1][self::VALUE];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setValue(&$objectOrArray, $propertyPath, $value)
|
||||
{
|
||||
if (is_string($propertyPath)) {
|
||||
$propertyPath = new PropertyPath($propertyPath);
|
||||
} elseif (!$propertyPath instanceof PropertyPathInterface) {
|
||||
throw new UnexpectedTypeException($propertyPath, 'string or Symfony\Component\PropertyAccess\PropertyPathInterface');
|
||||
}
|
||||
|
||||
$propertyValues =& $this->readPropertiesUntil($objectOrArray, $propertyPath, $propertyPath->getLength() - 2);
|
||||
$overwrite = true;
|
||||
|
||||
// Add the root object to the list
|
||||
array_unshift($propertyValues, array(
|
||||
self::VALUE => &$objectOrArray,
|
||||
self::IS_REF => true,
|
||||
));
|
||||
|
||||
for ($i = count($propertyValues) - 1; $i >= 0; --$i) {
|
||||
$objectOrArray =& $propertyValues[$i][self::VALUE];
|
||||
|
||||
if ($overwrite) {
|
||||
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
|
||||
throw new UnexpectedTypeException($objectOrArray, 'object or array');
|
||||
}
|
||||
|
||||
$property = $propertyPath->getElement($i);
|
||||
//$singular = $propertyPath->singulars[$i];
|
||||
$singular = null;
|
||||
$isIndex = $propertyPath->isIndex($i);
|
||||
|
||||
$this->writeProperty($objectOrArray, $propertyPath, $property, $singular, $isIndex, $value);
|
||||
}
|
||||
|
||||
$value =& $objectOrArray;
|
||||
$overwrite = !$propertyValues[$i][self::IS_REF];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the path from an object up to a given path index.
|
||||
*
|
||||
* @param object|array $objectOrArray The object or array to read from.
|
||||
* @param PropertyPathInterface $propertyPath The property path to read.
|
||||
* @param integer $lastIndex The integer up to which should be read.
|
||||
*
|
||||
* @return array The values read in the path.
|
||||
*
|
||||
* @throws UnexpectedTypeException If a value within the path is neither object nor array.
|
||||
*/
|
||||
private function &readPropertiesUntil(&$objectOrArray, PropertyPathInterface $propertyPath, $lastIndex)
|
||||
{
|
||||
$propertyValues = array();
|
||||
|
||||
for ($i = 0; $i <= $lastIndex; ++$i) {
|
||||
if (!is_object($objectOrArray) && !is_array($objectOrArray)) {
|
||||
throw new UnexpectedTypeException($objectOrArray, 'object or array');
|
||||
}
|
||||
|
||||
$property = $propertyPath->getElement($i);
|
||||
$isIndex = $propertyPath->isIndex($i);
|
||||
$isArrayAccess = is_array($objectOrArray) || $objectOrArray instanceof \ArrayAccess;
|
||||
|
||||
// Create missing nested arrays on demand
|
||||
if ($isIndex && $isArrayAccess && !isset($objectOrArray[$property])) {
|
||||
$objectOrArray[$property] = $i + 1 < $propertyPath->getLength() ? array() : null;
|
||||
}
|
||||
|
||||
$propertyValue =& $this->readProperty($objectOrArray, $propertyPath, $property, $isIndex);
|
||||
$objectOrArray =& $propertyValue[self::VALUE];
|
||||
|
||||
$propertyValues[] =& $propertyValue;
|
||||
}
|
||||
|
||||
return $propertyValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the a property from an object or array.
|
||||
*
|
||||
* @param object|array $objectOrArray The object or array to read from.
|
||||
* @param PropertyPathInterface $propertyPath The property path to read.
|
||||
* @param string $property The property to read.
|
||||
* @param Boolean $isIndex Whether to interpret the property as index.
|
||||
*
|
||||
* @return mixed The value of the read property
|
||||
*
|
||||
* @throws NoSuchPropertyException If the property does not exist.
|
||||
* @throws PropertyAccessDeniedException If the property cannot be accessed due to
|
||||
* access restrictions (private or protected).
|
||||
*/
|
||||
private function &readProperty(&$objectOrArray, PropertyPathInterface $propertyPath, $property, $isIndex)
|
||||
{
|
||||
// Use an array instead of an object since performance is
|
||||
// very crucial here
|
||||
$result = array(
|
||||
self::VALUE => null,
|
||||
self::IS_REF => false
|
||||
);
|
||||
|
||||
if ($isIndex) {
|
||||
if (!$objectOrArray instanceof \ArrayAccess && !is_array($objectOrArray)) {
|
||||
throw new NoSuchPropertyException(sprintf('Index "%s" cannot be read from object of type "%s" because it doesn\'t implement \ArrayAccess', $property, get_class($objectOrArray)));
|
||||
}
|
||||
|
||||
if (isset($objectOrArray[$property])) {
|
||||
if (is_array($objectOrArray)) {
|
||||
$result[self::VALUE] =& $objectOrArray[$property];
|
||||
$result[self::IS_REF] = true;
|
||||
} else {
|
||||
$result[self::VALUE] = $objectOrArray[$property];
|
||||
}
|
||||
}
|
||||
} elseif (is_object($objectOrArray)) {
|
||||
$camelProp = $this->camelize($property);
|
||||
$reflClass = new \ReflectionClass($objectOrArray);
|
||||
$getter = 'get'.$camelProp;
|
||||
$isser = 'is'.$camelProp;
|
||||
$hasser = 'has'.$camelProp;
|
||||
|
||||
if ($reflClass->hasMethod($getter)) {
|
||||
if (!$reflClass->getMethod($getter)->isPublic()) {
|
||||
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $getter, $reflClass->name));
|
||||
}
|
||||
|
||||
$result[self::VALUE] = $objectOrArray->$getter();
|
||||
} elseif ($reflClass->hasMethod($isser)) {
|
||||
if (!$reflClass->getMethod($isser)->isPublic()) {
|
||||
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $isser, $reflClass->name));
|
||||
}
|
||||
|
||||
$result[self::VALUE] = $objectOrArray->$isser();
|
||||
} elseif ($reflClass->hasMethod($hasser)) {
|
||||
if (!$reflClass->getMethod($hasser)->isPublic()) {
|
||||
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $hasser, $reflClass->name));
|
||||
}
|
||||
|
||||
$result[self::VALUE] = $objectOrArray->$hasser();
|
||||
} elseif ($reflClass->hasMethod('__get')) {
|
||||
// needed to support magic method __get
|
||||
$result[self::VALUE] = $objectOrArray->$property;
|
||||
} elseif ($reflClass->hasProperty($property)) {
|
||||
if (!$reflClass->getProperty($property)->isPublic()) {
|
||||
throw new PropertyAccessDeniedException(sprintf('Property "%s" is not public in class "%s". Maybe you should create the method "%s()" or "%s()" or "%s()"?', $property, $reflClass->name, $getter, $isser, $hasser));
|
||||
}
|
||||
|
||||
$result[self::VALUE] =& $objectOrArray->$property;
|
||||
$result[self::IS_REF] = true;
|
||||
} elseif (property_exists($objectOrArray, $property)) {
|
||||
// needed to support \stdClass instances
|
||||
$result[self::VALUE] =& $objectOrArray->$property;
|
||||
$result[self::IS_REF] = true;
|
||||
} else {
|
||||
throw new NoSuchPropertyException(sprintf('Neither property "%s" nor method "%s()" nor method "%s()" exists in class "%s"', $property, $getter, $isser, $reflClass->name));
|
||||
}
|
||||
} else {
|
||||
throw new NoSuchPropertyException(sprintf('Cannot read property "%s" from an array. Maybe you should write the property path as "[%s]" instead?', $property, $property));
|
||||
}
|
||||
|
||||
// Objects are always passed around by reference
|
||||
if (is_object($result[self::VALUE])) {
|
||||
$result[self::IS_REF] = true;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the property at the given index in the path
|
||||
*
|
||||
* @param object|array $objectOrArray The object or array to write to.
|
||||
* @param PropertyPathInterface $propertyPath The property path to write.
|
||||
* @param string $property The property to write.
|
||||
* @param string|null $singular The singular form of the property name or null.
|
||||
* @param Boolean $isIndex Whether to interpret the property as index.
|
||||
* @param mixed $value The value to write.
|
||||
*
|
||||
* @throws NoSuchPropertyException If the property does not exist.
|
||||
* @throws PropertyAccessDeniedException If the property cannot be accessed due to
|
||||
* access restrictions (private or protected).
|
||||
*/
|
||||
private function writeProperty(&$objectOrArray, PropertyPathInterface $propertyPath, $property, $singular, $isIndex, $value)
|
||||
{
|
||||
$adderRemoverError = null;
|
||||
|
||||
if ($isIndex) {
|
||||
if (!$objectOrArray instanceof \ArrayAccess && !is_array($objectOrArray)) {
|
||||
throw new NoSuchPropertyException(sprintf('Index "%s" cannot be modified in object of type "%s" because it doesn\'t implement \ArrayAccess', $property, get_class($objectOrArray)));
|
||||
}
|
||||
|
||||
$objectOrArray[$property] = $value;
|
||||
} elseif (is_object($objectOrArray)) {
|
||||
$reflClass = new \ReflectionClass($objectOrArray);
|
||||
|
||||
// The plural form is the last element of the property path
|
||||
$plural = $this->camelize($propertyPath->getElement($propertyPath->getLength() - 1));
|
||||
|
||||
// Any of the two methods is required, but not yet known
|
||||
$singulars = null !== $singular ? array($singular) : (array) StringUtil::singularify($plural);
|
||||
|
||||
if (is_array($value) || $value instanceof \Traversable) {
|
||||
$methods = $this->findAdderAndRemover($reflClass, $singulars);
|
||||
if (null !== $methods) {
|
||||
// At this point the add and remove methods have been found
|
||||
// Use iterator_to_array() instead of clone in order to prevent side effects
|
||||
// see https://github.com/symfony/symfony/issues/4670
|
||||
$itemsToAdd = is_object($value) ? iterator_to_array($value) : $value;
|
||||
$itemToRemove = array();
|
||||
$propertyValue = $this->readProperty($objectOrArray, $propertyPath, $property, $isIndex);
|
||||
$previousValue = $propertyValue[self::VALUE];
|
||||
|
||||
if (is_array($previousValue) || $previousValue instanceof \Traversable) {
|
||||
foreach ($previousValue as $previousItem) {
|
||||
foreach ($value as $key => $item) {
|
||||
if ($item === $previousItem) {
|
||||
// Item found, don't add
|
||||
unset($itemsToAdd[$key]);
|
||||
|
||||
// Next $previousItem
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Item not found, add to remove list
|
||||
$itemToRemove[] = $previousItem;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($itemToRemove as $item) {
|
||||
call_user_func(array($objectOrArray, $methods[1]), $item);
|
||||
}
|
||||
|
||||
foreach ($itemsToAdd as $item) {
|
||||
call_user_func(array($objectOrArray, $methods[0]), $item);
|
||||
}
|
||||
|
||||
return;
|
||||
} else {
|
||||
$adderRemoverError = ', nor could adders and removers be found based on the ';
|
||||
if (null === $singular) {
|
||||
// $adderRemoverError .= 'guessed singulars: '.implode(', ', $singulars).' (provide a singular by suffixing the property path with "|{singular}" to override the guesser)';
|
||||
$adderRemoverError .= 'guessed singulars: '.implode(', ', $singulars);
|
||||
} else {
|
||||
$adderRemoverError .= 'passed singular: '.$singular;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$setter = 'set'.$this->camelize($property);
|
||||
if ($reflClass->hasMethod($setter)) {
|
||||
if (!$reflClass->getMethod($setter)->isPublic()) {
|
||||
throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $setter, $reflClass->name));
|
||||
}
|
||||
|
||||
$objectOrArray->$setter($value);
|
||||
} elseif ($reflClass->hasMethod('__set')) {
|
||||
// needed to support magic method __set
|
||||
$objectOrArray->$property = $value;
|
||||
} elseif ($reflClass->hasProperty($property)) {
|
||||
if (!$reflClass->getProperty($property)->isPublic()) {
|
||||
throw new PropertyAccessDeniedException(sprintf('Property "%s" is not public in class "%s"%s. Maybe you should create the method "%s()"?', $property, $reflClass->name, $adderRemoverError, $setter));
|
||||
}
|
||||
|
||||
$objectOrArray->$property = $value;
|
||||
} elseif (property_exists($objectOrArray, $property)) {
|
||||
// needed to support \stdClass instances
|
||||
$objectOrArray->$property = $value;
|
||||
} else {
|
||||
throw new NoSuchPropertyException(sprintf('Neither element "%s" nor method "%s()" exists in class "%s"%s', $property, $setter, $reflClass->name, $adderRemoverError));
|
||||
}
|
||||
} else {
|
||||
throw new NoSuchPropertyException(sprintf('Cannot write property "%s" in an array. Maybe you should write the property path as "[%s]" instead?', $property, $property));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Camelizes a given string.
|
||||
*
|
||||
* @param string $string Some string.
|
||||
*
|
||||
* @return string The camelized version of the string.
|
||||
*/
|
||||
private function camelize($string)
|
||||
{
|
||||
return preg_replace_callback('/(^|_|\.)+(.)/', function ($match) { return ('.' === $match[1] ? '_' : '').strtoupper($match[2]); }, $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for add and remove methods.
|
||||
*
|
||||
* @param \ReflectionClass $reflClass The reflection class for the given object
|
||||
* @param array $singulars The singular form of the property name or null.
|
||||
*
|
||||
* @return array|null An array containing the adder and remover when found, null otherwise.
|
||||
*
|
||||
* @throws NoSuchPropertyException If the property does not exist.
|
||||
*/
|
||||
private function findAdderAndRemover(\ReflectionClass $reflClass, array $singulars)
|
||||
{
|
||||
foreach ($singulars as $singular) {
|
||||
$addMethod = 'add' . $singular;
|
||||
$removeMethod = 'remove' . $singular;
|
||||
|
||||
$addMethodFound = $this->isAccessible($reflClass, $addMethod, 1);
|
||||
$removeMethodFound = $this->isAccessible($reflClass, $removeMethod, 1);
|
||||
|
||||
if ($addMethodFound && $removeMethodFound) {
|
||||
return array($addMethod, $removeMethod);
|
||||
}
|
||||
|
||||
if ($addMethodFound xor $removeMethodFound) {
|
||||
throw new NoSuchPropertyException(sprintf(
|
||||
'Found the public method "%s", but did not find a public "%s" on class %s',
|
||||
$addMethodFound ? $addMethod : $removeMethod,
|
||||
$addMethodFound ? $removeMethod : $addMethod,
|
||||
$reflClass->name
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a method is public and has a specific number of required parameters.
|
||||
*
|
||||
* @param \ReflectionClass $class The class of the method.
|
||||
* @param string $methodName The method name.
|
||||
* @param integer $parameters The number of parameters.
|
||||
*
|
||||
* @return Boolean Whether the method is public and has $parameters
|
||||
* required parameters.
|
||||
*/
|
||||
private function isAccessible(\ReflectionClass $class, $methodName, $parameters)
|
||||
{
|
||||
if ($class->hasMethod($methodName)) {
|
||||
$method = $class->getMethod($methodName);
|
||||
|
||||
if ($method->isPublic() && $method->getNumberOfRequiredParameters() === $parameters) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Writes and reads values to/from an object/array graph.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
interface PropertyAccessorInterface
|
||||
{
|
||||
/**
|
||||
* Sets the value at the end of the property path of the object
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
*
|
||||
* $propertyAccessor = PropertyAccess::getPropertyAccessor();
|
||||
*
|
||||
* echo $propertyAccessor->setValue($object, 'child.name', 'Fabien');
|
||||
* // equals echo $object->getChild()->setName('Fabien');
|
||||
*
|
||||
* This method first tries to find a public setter for each property in the
|
||||
* path. The name of the setter must be the camel-cased property name
|
||||
* prefixed with "set".
|
||||
*
|
||||
* If the setter does not exist, this method tries to find a public
|
||||
* property. The value of the property is then changed.
|
||||
*
|
||||
* If neither is found, an exception is thrown.
|
||||
*
|
||||
* @param object|array $objectOrArray The object or array to modify.
|
||||
* @param string|PropertyPathInterface $propertyPath The property path to modify.
|
||||
* @param mixed $value The value to set at the end of the property path.
|
||||
*
|
||||
* @throws Exception\NoSuchPropertyException If a property does not exist.
|
||||
* @throws Exception\PropertyAccessDeniedException If a property cannot be accessed due to
|
||||
* access restrictions (private or protected).
|
||||
* @throws Exception\UnexpectedTypeException If a value within the path is neither object
|
||||
* nor array.
|
||||
*/
|
||||
public function setValue(&$objectOrArray, $propertyPath, $value);
|
||||
|
||||
/**
|
||||
* Returns the value at the end of the property path of the object
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
*
|
||||
* $propertyAccessor = PropertyAccess::getPropertyAccessor();
|
||||
*
|
||||
* echo $propertyAccessor->getValue($object, 'child.name);
|
||||
* // equals echo $object->getChild()->getName();
|
||||
*
|
||||
* This method first tries to find a public getter for each property in the
|
||||
* path. The name of the getter must be the camel-cased property name
|
||||
* prefixed with "get", "is", or "has".
|
||||
*
|
||||
* If the getter does not exist, this method tries to find a public
|
||||
* property. The value of the property is then returned.
|
||||
*
|
||||
* If none of them are found, an exception is thrown.
|
||||
*
|
||||
* @param object|array $objectOrArray The object or array to traverse
|
||||
* @param string|PropertyPathInterface $propertyPath The property path to modify.
|
||||
*
|
||||
* @return mixed The value at the end of the property path
|
||||
*
|
||||
* @throws Exception\NoSuchPropertyException If the property/getter does not exist
|
||||
* @throws Exception\PropertyAccessDeniedException If the property/getter exists but is not public
|
||||
*/
|
||||
public function getValue($objectOrArray, $propertyPath);
|
||||
}
|
225
src/Symfony/Component/PropertyAccess/PropertyPath.php
Normal file
225
src/Symfony/Component/PropertyAccess/PropertyPath.php
Normal file
@ -0,0 +1,225 @@
|
||||
<?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;
|
||||
|
||||
use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException;
|
||||
use Symfony\Component\PropertyAccess\Exception\OutOfBoundsException;
|
||||
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link PropertyPathInterface}.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class PropertyPath implements \IteratorAggregate, PropertyPathInterface
|
||||
{
|
||||
/**
|
||||
* Character used for separating between plural and singular of an element.
|
||||
* @var string
|
||||
*/
|
||||
const SINGULAR_SEPARATOR = '|';
|
||||
|
||||
/**
|
||||
* The elements of the property path
|
||||
* @var array
|
||||
*/
|
||||
private $elements = array();
|
||||
|
||||
/**
|
||||
* The singular forms of the elements in the property path.
|
||||
* @var array
|
||||
*/
|
||||
private $singulars = array();
|
||||
|
||||
/**
|
||||
* The number of elements in the property path
|
||||
* @var integer
|
||||
*/
|
||||
private $length;
|
||||
|
||||
/**
|
||||
* Contains a Boolean for each property in $elements denoting whether this
|
||||
* element is an index. It is a property otherwise.
|
||||
* @var array
|
||||
*/
|
||||
private $isIndex = array();
|
||||
|
||||
/**
|
||||
* String representation of the path
|
||||
* @var string
|
||||
*/
|
||||
private $pathAsString;
|
||||
|
||||
/**
|
||||
* Constructs a property path from a string.
|
||||
*
|
||||
* @param PropertyPath|string $propertyPath The property path as string or instance.
|
||||
*
|
||||
* @throws UnexpectedTypeException If the given path is not a string.
|
||||
* @throws InvalidPropertyPathException If the syntax of the property path is not valid.
|
||||
*/
|
||||
public function __construct($propertyPath)
|
||||
{
|
||||
// Can be used as copy constructor
|
||||
if ($propertyPath instanceof PropertyPath) {
|
||||
/* @var PropertyPath $propertyPath */
|
||||
$this->elements = $propertyPath->elements;
|
||||
$this->singulars = $propertyPath->singulars;
|
||||
$this->length = $propertyPath->length;
|
||||
$this->isIndex = $propertyPath->isIndex;
|
||||
$this->pathAsString = $propertyPath->pathAsString;
|
||||
|
||||
return;
|
||||
}
|
||||
if (!is_string($propertyPath)) {
|
||||
throw new UnexpectedTypeException($propertyPath, 'string or Symfony\Component\PropertyAccess\PropertyPath');
|
||||
}
|
||||
|
||||
if ('' === $propertyPath) {
|
||||
throw new InvalidPropertyPathException('The property path should not be empty.');
|
||||
}
|
||||
|
||||
$this->pathAsString = $propertyPath;
|
||||
$position = 0;
|
||||
$remaining = $propertyPath;
|
||||
|
||||
// first element is evaluated differently - no leading dot for properties
|
||||
$pattern = '/^(([^\.\[]+)|\[([^\]]+)\])(.*)/';
|
||||
|
||||
while (preg_match($pattern, $remaining, $matches)) {
|
||||
if ('' !== $matches[2]) {
|
||||
$element = $matches[2];
|
||||
$this->isIndex[] = false;
|
||||
} else {
|
||||
$element = $matches[3];
|
||||
$this->isIndex[] = true;
|
||||
}
|
||||
// Disabled this behaviour as the syntax is not yet final
|
||||
//$pos = strpos($element, self::SINGULAR_SEPARATOR);
|
||||
$pos = false;
|
||||
$singular = null;
|
||||
|
||||
if (false !== $pos) {
|
||||
$singular = substr($element, $pos + 1);
|
||||
$element = substr($element, 0, $pos);
|
||||
}
|
||||
|
||||
$this->elements[] = $element;
|
||||
$this->singulars[] = $singular;
|
||||
|
||||
$position += strlen($matches[1]);
|
||||
$remaining = $matches[4];
|
||||
$pattern = '/^(\.(\w+)|\[([^\]]+)\])(.*)/';
|
||||
}
|
||||
|
||||
if ('' !== $remaining) {
|
||||
throw new InvalidPropertyPathException(sprintf(
|
||||
'Could not parse property path "%s". Unexpected token "%s" at position %d',
|
||||
$propertyPath,
|
||||
$remaining{0},
|
||||
$position
|
||||
));
|
||||
}
|
||||
|
||||
$this->length = count($this->elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->pathAsString;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLength()
|
||||
{
|
||||
return $this->length;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
if ($this->length <= 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parent = clone $this;
|
||||
|
||||
--$parent->length;
|
||||
$parent->pathAsString = substr($parent->pathAsString, 0, max(strrpos($parent->pathAsString, '.'), strrpos($parent->pathAsString, '[')));
|
||||
array_pop($parent->elements);
|
||||
array_pop($parent->singulars);
|
||||
array_pop($parent->isIndex);
|
||||
|
||||
return $parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new iterator for this path
|
||||
*
|
||||
* @return PropertyPathIteratorInterface
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new PropertyPathIterator($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getElements()
|
||||
{
|
||||
return $this->elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getElement($index)
|
||||
{
|
||||
if (!isset($this->elements[$index])) {
|
||||
throw new OutOfBoundsException('The index ' . $index . ' is not within the property path');
|
||||
}
|
||||
|
||||
return $this->elements[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isProperty($index)
|
||||
{
|
||||
if (!isset($this->isIndex[$index])) {
|
||||
throw new OutOfBoundsException('The index ' . $index . ' is not within the property path');
|
||||
}
|
||||
|
||||
return !$this->isIndex[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isIndex($index)
|
||||
{
|
||||
if (!isset($this->isIndex[$index])) {
|
||||
throw new OutOfBoundsException('The index ' . $index . ' is not within the property path');
|
||||
}
|
||||
|
||||
return $this->isIndex[$index];
|
||||
}
|
||||
}
|
296
src/Symfony/Component/PropertyAccess/PropertyPathBuilder.php
Normal file
296
src/Symfony/Component/PropertyAccess/PropertyPathBuilder.php
Normal file
@ -0,0 +1,296 @@
|
||||
<?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;
|
||||
|
||||
use Symfony\Component\PropertyAccess\Exception\OutOfBoundsException;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class PropertyPathBuilder
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $elements = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $isIndex = array();
|
||||
|
||||
/**
|
||||
* Creates a new property path builder.
|
||||
*
|
||||
* @param null|PropertyPathInterface $path The path to initially store
|
||||
* in the builder. Optional.
|
||||
*/
|
||||
public function __construct(PropertyPathInterface $path = null)
|
||||
{
|
||||
if (null !== $path) {
|
||||
$this->append($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a (sub-) path to the current path.
|
||||
*
|
||||
* @param PropertyPathInterface $path The path to append.
|
||||
* @param integer $offset The offset where the appended piece
|
||||
* starts in $path.
|
||||
* @param integer $length The length of the appended piece.
|
||||
* If 0, the full path is appended.
|
||||
*/
|
||||
public function append(PropertyPathInterface $path, $offset = 0, $length = 0)
|
||||
{
|
||||
if (0 === $length) {
|
||||
$end = $path->getLength();
|
||||
} else {
|
||||
$end = $offset + $length;
|
||||
}
|
||||
|
||||
for (; $offset < $end; ++$offset) {
|
||||
$this->elements[] = $path->getElement($offset);
|
||||
$this->isIndex[] = $path->isIndex($offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an index element to the current path.
|
||||
*
|
||||
* @param string $name The name of the appended index.
|
||||
*/
|
||||
public function appendIndex($name)
|
||||
{
|
||||
$this->elements[] = $name;
|
||||
$this->isIndex[] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a property element to the current path.
|
||||
*
|
||||
* @param string $name The name of the appended property.
|
||||
*/
|
||||
public function appendProperty($name)
|
||||
{
|
||||
$this->elements[] = $name;
|
||||
$this->isIndex[] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes elements from the current path.
|
||||
*
|
||||
* @param integer $offset The offset at which to remove.
|
||||
* @param integer $length The length of the removed piece.
|
||||
*
|
||||
* @throws OutOfBoundsException if offset is invalid
|
||||
*/
|
||||
public function remove($offset, $length = 1)
|
||||
{
|
||||
if (!isset($this->elements[$offset])) {
|
||||
throw new OutOfBoundsException('The offset ' . $offset . ' is not within the property path');
|
||||
}
|
||||
|
||||
$this->resize($offset, $length, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces a sub-path by a different (sub-) path.
|
||||
*
|
||||
* @param integer $offset The offset at which to replace.
|
||||
* @param integer $length The length of the piece to replace.
|
||||
* @param PropertyPathInterface $path The path to insert.
|
||||
* @param integer $pathOffset The offset where the inserted piece
|
||||
* starts in $path.
|
||||
* @param integer $pathLength The length of the inserted piece.
|
||||
* If 0, the full path is inserted.
|
||||
*
|
||||
* @throws OutOfBoundsException If the offset is invalid.
|
||||
*/
|
||||
public function replace($offset, $length, PropertyPathInterface $path, $pathOffset = 0, $pathLength = 0)
|
||||
{
|
||||
if (!isset($this->elements[$offset])) {
|
||||
throw new OutOfBoundsException('The offset ' . $offset . ' is not within the property path');
|
||||
}
|
||||
|
||||
if (0 === $pathLength) {
|
||||
$pathLength = $path->getLength() - $pathOffset;
|
||||
}
|
||||
|
||||
$this->resize($offset, $length, $pathLength);
|
||||
|
||||
for ($i = 0; $i < $pathLength; ++$i) {
|
||||
$this->elements[$offset + $i] = $path->getElement($pathOffset + $i);
|
||||
$this->isIndex[$offset + $i] = $path->isIndex($pathOffset + $i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces a property element by an index element.
|
||||
*
|
||||
* @param integer $offset The offset at which to replace.
|
||||
* @param string $name The new name of the element. Optional.
|
||||
*
|
||||
* @throws OutOfBoundsException If the offset is invalid.
|
||||
*/
|
||||
public function replaceByIndex($offset, $name = null)
|
||||
{
|
||||
if (!isset($this->elements[$offset])) {
|
||||
throw new OutOfBoundsException('The offset ' . $offset . ' is not within the property path');
|
||||
}
|
||||
|
||||
if (null !== $name) {
|
||||
$this->elements[$offset] = $name;
|
||||
}
|
||||
|
||||
$this->isIndex[$offset] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an index element by a property element.
|
||||
*
|
||||
* @param integer $offset The offset at which to replace.
|
||||
* @param string $name The new name of the element. Optional.
|
||||
*
|
||||
* @throws OutOfBoundsException If the offset is invalid.
|
||||
*/
|
||||
public function replaceByProperty($offset, $name = null)
|
||||
{
|
||||
if (!isset($this->elements[$offset])) {
|
||||
throw new OutOfBoundsException('The offset ' . $offset . ' is not within the property path');
|
||||
}
|
||||
|
||||
if (null !== $name) {
|
||||
$this->elements[$offset] = $name;
|
||||
}
|
||||
|
||||
$this->isIndex[$offset] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the current path.
|
||||
*
|
||||
* @return integer The path length.
|
||||
*/
|
||||
public function getLength()
|
||||
{
|
||||
return count($this->elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current property path.
|
||||
*
|
||||
* @return PropertyPathInterface The constructed property path.
|
||||
*/
|
||||
public function getPropertyPath()
|
||||
{
|
||||
$pathAsString = $this->__toString();
|
||||
|
||||
return '' !== $pathAsString ? new PropertyPath($pathAsString) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current property path as string.
|
||||
*
|
||||
* @return string The property path as string.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$string = '';
|
||||
|
||||
foreach ($this->elements as $offset => $element) {
|
||||
if ($this->isIndex[$offset]) {
|
||||
$element = '[' . $element . ']';
|
||||
} elseif ('' !== $string) {
|
||||
$string .= '.';
|
||||
}
|
||||
|
||||
$string .= $element;
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the path so that a chunk of length $cutLength is
|
||||
* removed at $offset and another chunk of length $insertionLength
|
||||
* can be inserted.
|
||||
*
|
||||
* @param integer $offset The offset where the removed chunk starts.
|
||||
* @param integer $cutLength The length of the removed chunk.
|
||||
* @param integer $insertionLength The length of the inserted chunk.
|
||||
*/
|
||||
private function resize($offset, $cutLength, $insertionLength)
|
||||
{
|
||||
// Nothing else to do in this case
|
||||
if ($insertionLength === $cutLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
$length = count($this->elements);
|
||||
|
||||
if ($cutLength > $insertionLength) {
|
||||
// More elements should be removed than inserted
|
||||
$diff = $cutLength - $insertionLength;
|
||||
$newLength = $length - $diff;
|
||||
|
||||
// Shift elements to the left (left-to-right until the new end)
|
||||
// Max allowed offset to be shifted is such that
|
||||
// $offset + $diff < $length (otherwise invalid index access)
|
||||
// i.e. $offset < $length - $diff = $newLength
|
||||
for ($i = $offset; $i < $newLength; ++$i) {
|
||||
$this->elements[$i] = $this->elements[$i + $diff];
|
||||
$this->isIndex[$i] = $this->isIndex[$i + $diff];
|
||||
}
|
||||
|
||||
// All remaining elements should be removed
|
||||
for (; $i < $length; ++$i) {
|
||||
unset($this->elements[$i]);
|
||||
unset($this->isIndex[$i]);
|
||||
}
|
||||
} else {
|
||||
$diff = $insertionLength - $cutLength;
|
||||
|
||||
$newLength = $length + $diff;
|
||||
$indexAfterInsertion = $offset + $insertionLength;
|
||||
|
||||
// $diff <= $insertionLength
|
||||
// $indexAfterInsertion >= $insertionLength
|
||||
// => $diff <= $indexAfterInsertion
|
||||
|
||||
// In each of the following loops, $i >= $diff must hold,
|
||||
// otherwise ($i - $diff) becomes negative.
|
||||
|
||||
// Shift old elements to the right to make up space for the
|
||||
// inserted elements. This needs to be done left-to-right in
|
||||
// order to preserve an ascending array index order
|
||||
// Since $i = max($length, $indexAfterInsertion) and $indexAfterInsertion >= $diff,
|
||||
// $i >= $diff is guaranteed.
|
||||
for ($i = max($length, $indexAfterInsertion); $i < $newLength; ++$i) {
|
||||
$this->elements[$i] = $this->elements[$i - $diff];
|
||||
$this->isIndex[$i] = $this->isIndex[$i - $diff];
|
||||
}
|
||||
|
||||
// Shift remaining elements to the right. Do this right-to-left
|
||||
// so we don't overwrite elements before copying them
|
||||
// The last written index is the immediate index after the inserted
|
||||
// string, because the indices before that will be overwritten
|
||||
// anyway.
|
||||
// Since $i >= $indexAfterInsertion and $indexAfterInsertion >= $diff,
|
||||
// $i >= $diff is guaranteed.
|
||||
for ($i = $length - 1; $i >= $indexAfterInsertion; --$i) {
|
||||
$this->elements[$i] = $this->elements[$i - $diff];
|
||||
$this->isIndex[$i] = $this->isIndex[$i - $diff];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* A sequence of property names or array indices.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
interface PropertyPathInterface extends \Traversable
|
||||
{
|
||||
/**
|
||||
* Returns the string representation of the property path
|
||||
*
|
||||
* @return string The path as string.
|
||||
*/
|
||||
public function __toString();
|
||||
|
||||
/**
|
||||
* Returns the length of the property path, i.e. the number of elements.
|
||||
*
|
||||
* @return integer The path length.
|
||||
*/
|
||||
public function getLength();
|
||||
|
||||
/**
|
||||
* Returns the parent property path.
|
||||
*
|
||||
* The parent property path is the one that contains the same items as
|
||||
* this one except for the last one.
|
||||
*
|
||||
* If this property path only contains one item, null is returned.
|
||||
*
|
||||
* @return PropertyPath The parent path or null.
|
||||
*/
|
||||
public function getParent();
|
||||
|
||||
/**
|
||||
* Returns the elements of the property path as array
|
||||
*
|
||||
* @return array An array of property/index names
|
||||
*/
|
||||
public function getElements();
|
||||
|
||||
/**
|
||||
* Returns the element at the given index in the property path
|
||||
*
|
||||
* @param integer $index The index key
|
||||
*
|
||||
* @return string A property or index name
|
||||
*
|
||||
* @throws Exception\OutOfBoundsException If the offset is invalid.
|
||||
*/
|
||||
public function getElement($index);
|
||||
|
||||
/**
|
||||
* Returns whether the element at the given index is a property
|
||||
*
|
||||
* @param integer $index The index in the property path
|
||||
*
|
||||
* @return Boolean Whether the element at this index is a property
|
||||
*
|
||||
* @throws Exception\OutOfBoundsException If the offset is invalid.
|
||||
*/
|
||||
public function isProperty($index);
|
||||
|
||||
/**
|
||||
* Returns whether the element at the given index is an array index
|
||||
*
|
||||
* @param integer $index The index in the property path
|
||||
*
|
||||
* @return Boolean Whether the element at this index is an array index
|
||||
*
|
||||
* @throws Exception\OutOfBoundsException If the offset is invalid.
|
||||
*/
|
||||
public function isIndex($index);
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Traverses a property path and provides additional methods to find out
|
||||
* information about the current element
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class PropertyPathIterator extends \ArrayIterator implements PropertyPathIteratorInterface
|
||||
{
|
||||
/**
|
||||
* The traversed property path
|
||||
* @var PropertyPathInterface
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param PropertyPathInterface $path The property path to traverse
|
||||
*/
|
||||
public function __construct(PropertyPathInterface $path)
|
||||
{
|
||||
parent::__construct($path->getElements());
|
||||
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isIndex()
|
||||
{
|
||||
return $this->path->isIndex($this->key());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isProperty()
|
||||
{
|
||||
return $this->path->isProperty($this->key());
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
interface PropertyPathIteratorInterface extends \Iterator, \SeekableIterator
|
||||
{
|
||||
/**
|
||||
* Returns whether the current element in the property path is an array
|
||||
* index.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isIndex();
|
||||
|
||||
/**
|
||||
* Returns whether the current element in the property path is a property
|
||||
* name.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isProperty();
|
||||
}
|
14
src/Symfony/Component/PropertyAccess/README.md
Normal file
14
src/Symfony/Component/PropertyAccess/README.md
Normal file
@ -0,0 +1,14 @@
|
||||
PropertyAccess Component
|
||||
========================
|
||||
|
||||
PropertyAccess reads/writes values from/to object/array graphs using a simple
|
||||
string notation.
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
You can run the unit tests with the following command:
|
||||
|
||||
$ cd path/to/Symfony/Component/PropertyAccess/
|
||||
$ composer.phar install --dev
|
||||
$ phpunit
|
192
src/Symfony/Component/PropertyAccess/StringUtil.php
Normal file
192
src/Symfony/Component/PropertyAccess/StringUtil.php
Normal file
@ -0,0 +1,192 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Creates singulars from plurals.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class StringUtil
|
||||
{
|
||||
/**
|
||||
* Map english plural to singular suffixes
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @see http://english-zone.com/spelling/plurals.html
|
||||
* @see http://www.scribd.com/doc/3271143/List-of-100-Irregular-Plural-Nouns-in-English
|
||||
*/
|
||||
private static $pluralMap = array(
|
||||
// First entry: plural suffix, reversed
|
||||
// Second entry: length of plural suffix
|
||||
// Third entry: Whether the suffix may succeed a vocal
|
||||
// Fourth entry: Whether the suffix may succeed a consonant
|
||||
// Fifth entry: singular suffix, normal
|
||||
|
||||
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
|
||||
array('a', 1, true, true, array('on', 'um')),
|
||||
|
||||
// nebulae (nebula)
|
||||
array('ea', 2, true, true, 'a'),
|
||||
|
||||
// mice (mouse), lice (louse)
|
||||
array('eci', 3, false, true, 'ouse'),
|
||||
|
||||
// geese (goose)
|
||||
array('esee', 4, false, true, 'oose'),
|
||||
|
||||
// fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
|
||||
array('i', 1, true, true, 'us'),
|
||||
|
||||
// men (man), women (woman)
|
||||
array('nem', 3, true, true, 'man'),
|
||||
|
||||
// children (child)
|
||||
array('nerdlihc', 8, true, true, 'child'),
|
||||
|
||||
// oxen (ox)
|
||||
array('nexo', 4, false, false, 'ox'),
|
||||
|
||||
// indices (index), appendices (appendix), prices (price)
|
||||
array('seci', 4, false, true, array('ex', 'ix', 'ice')),
|
||||
|
||||
// babies (baby)
|
||||
array('sei', 3, false, true, 'y'),
|
||||
|
||||
// analyses (analysis), ellipses (ellipsis), funguses (fungus),
|
||||
// neuroses (neurosis), theses (thesis), emphases (emphasis),
|
||||
// oases (oasis), crises (crisis), houses (house), bases (base),
|
||||
// atlases (atlas), kisses (kiss)
|
||||
array('ses', 3, true, true, array('s', 'se', 'sis')),
|
||||
|
||||
// lives (life), wives (wife)
|
||||
array('sevi', 4, false, true, 'ife'),
|
||||
|
||||
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
|
||||
array('sev', 3, true, true, 'f'),
|
||||
|
||||
// axes (axis), axes (ax), axes (axe)
|
||||
array('sexa', 4, false, false, array('ax', 'axe', 'axis')),
|
||||
|
||||
// indexes (index), matrixes (matrix)
|
||||
array('sex', 3, true, false, 'x'),
|
||||
|
||||
// quizzes (quiz)
|
||||
array('sezz', 4, true, false, 'z'),
|
||||
|
||||
// bureaus (bureau)
|
||||
array('suae', 4, false, true, 'eau'),
|
||||
|
||||
// roses (rose), garages (garage), cassettes (cassette),
|
||||
// waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
|
||||
// shoes (shoe)
|
||||
array('se', 2, true, true, array('', 'e')),
|
||||
|
||||
// tags (tag)
|
||||
array('s', 1, true, true, ''),
|
||||
|
||||
// chateaux (chateau)
|
||||
array('xuae', 4, false, true, 'eau'),
|
||||
);
|
||||
|
||||
/**
|
||||
* This class should not be instantiated
|
||||
*/
|
||||
private function __construct() {}
|
||||
|
||||
/**
|
||||
* Returns the singular form of a word
|
||||
*
|
||||
* If the method can't determine the form with certainty, an array of the
|
||||
* possible singulars is returned.
|
||||
*
|
||||
* @param string $plural A word in plural form
|
||||
* @return string|array The singular form or an array of possible singular
|
||||
* forms
|
||||
*/
|
||||
public static function singularify($plural)
|
||||
{
|
||||
$pluralRev = strrev($plural);
|
||||
$lowerPluralRev = strtolower($pluralRev);
|
||||
$pluralLength = strlen($lowerPluralRev);
|
||||
|
||||
// The outer loop iterates over the entries of the plural table
|
||||
// The inner loop $j iterates over the characters of the plural suffix
|
||||
// in the plural table to compare them with the characters of the actual
|
||||
// given plural suffix
|
||||
foreach (self::$pluralMap as $map) {
|
||||
$suffix = $map[0];
|
||||
$suffixLength = $map[1];
|
||||
$j = 0;
|
||||
|
||||
// Compare characters in the plural table and of the suffix of the
|
||||
// given plural one by one
|
||||
while ($suffix[$j] === $lowerPluralRev[$j]) {
|
||||
// Let $j point to the next character
|
||||
++$j;
|
||||
|
||||
// Successfully compared the last character
|
||||
// Add an entry with the singular suffix to the singular array
|
||||
if ($j === $suffixLength) {
|
||||
// Is there any character preceding the suffix in the plural string?
|
||||
if ($j < $pluralLength) {
|
||||
$nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]);
|
||||
|
||||
if (!$map[2] && $nextIsVocal) {
|
||||
// suffix may not succeed a vocal but next char is one
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$map[3] && !$nextIsVocal) {
|
||||
// suffix may not succeed a consonant but next char is one
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$newBase = substr($plural, 0, $pluralLength - $suffixLength);
|
||||
$newSuffix = $map[4];
|
||||
|
||||
// Check whether the first character in the plural suffix
|
||||
// is uppercased. If yes, uppercase the first character in
|
||||
// the singular suffix too
|
||||
$firstUpper = ctype_upper($pluralRev[$j - 1]);
|
||||
|
||||
if (is_array($newSuffix)) {
|
||||
$singulars = array();
|
||||
|
||||
foreach ($newSuffix as $newSuffixEntry) {
|
||||
$singulars[] = $newBase . ($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
|
||||
}
|
||||
|
||||
return $singulars;
|
||||
}
|
||||
|
||||
return $newBase . ($firstUpper ? ucFirst($newSuffix) : $newSuffix);
|
||||
}
|
||||
|
||||
// Suffix is longer than word
|
||||
if ($j === $pluralLength) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert teeth to tooth, feet to foot
|
||||
if (false !== ($pos = strpos($plural, 'ee'))) {
|
||||
return substr_replace($plural, 'oo', $pos, 2);
|
||||
}
|
||||
|
||||
// Assume that plural and singular is identical
|
||||
return $plural;
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
<?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 Author
|
||||
{
|
||||
public $firstName;
|
||||
private $lastName;
|
||||
private $australian;
|
||||
public $child;
|
||||
private $readPermissions;
|
||||
|
||||
private $privateProperty;
|
||||
|
||||
public function setLastName($lastName)
|
||||
{
|
||||
$this->lastName = $lastName;
|
||||
}
|
||||
|
||||
public function getLastName()
|
||||
{
|
||||
return $this->lastName;
|
||||
}
|
||||
|
||||
private function getPrivateGetter()
|
||||
{
|
||||
return 'foobar';
|
||||
}
|
||||
|
||||
public function setAustralian($australian)
|
||||
{
|
||||
$this->australian = $australian;
|
||||
}
|
||||
|
||||
public function isAustralian()
|
||||
{
|
||||
return $this->australian;
|
||||
}
|
||||
|
||||
public function setReadPermissions($bool)
|
||||
{
|
||||
$this->readPermissions = $bool;
|
||||
}
|
||||
|
||||
public function hasReadPermissions()
|
||||
{
|
||||
return $this->readPermissions;
|
||||
}
|
||||
|
||||
private function isPrivateIsser()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getPrivateSetter()
|
||||
{
|
||||
}
|
||||
|
||||
private function setPrivateSetter($data)
|
||||
{
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Fixtures;
|
||||
namespace Symfony\Component\PropertyAccess\Tests\Fixtures;
|
||||
|
||||
class Magician
|
||||
{
|
@ -9,9 +9,9 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Util;
|
||||
namespace Symfony\Component\PropertyAccess\Tests;
|
||||
|
||||
class PropertyPathArrayObjectTest extends PropertyPathCollectionTest
|
||||
class PropertyAccessorArrayObjectTest extends PropertyAccessorCollectionTest
|
||||
{
|
||||
protected function getCollection(array $array)
|
||||
{
|
@ -9,9 +9,9 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Util;
|
||||
namespace Symfony\Component\PropertyAccess\Tests;
|
||||
|
||||
class PropertyPathArrayTest extends PropertyPathCollectionTest
|
||||
class PropertyAccessorArrayTest extends PropertyAccessorCollectionTest
|
||||
{
|
||||
protected function getCollection(array $array)
|
||||
{
|
@ -9,12 +9,13 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Util;
|
||||
namespace Symfony\Component\PropertyAccess\Tests;
|
||||
|
||||
use Symfony\Component\Form\Util\PropertyPath;
|
||||
use Symfony\Component\Form\Util\FormUtil;
|
||||
use Symfony\Component\PropertyAccess\Exception\ExceptionInterface;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||
use Symfony\Component\PropertyAccess\StringUtil;
|
||||
|
||||
class PropertyPathCollectionTest_Car
|
||||
class PropertyAccessorCollectionTest_Car
|
||||
{
|
||||
private $axes;
|
||||
|
||||
@ -23,7 +24,7 @@ class PropertyPathCollectionTest_Car
|
||||
$this->axes = $axes;
|
||||
}
|
||||
|
||||
// In the test, use a name that FormUtil can't uniquely singularify
|
||||
// In the test, use a name that StringUtil can't uniquely singularify
|
||||
public function addAxis($axis)
|
||||
{
|
||||
$this->axes[] = $axis;
|
||||
@ -46,7 +47,7 @@ class PropertyPathCollectionTest_Car
|
||||
}
|
||||
}
|
||||
|
||||
class PropertyPathCollectionTest_CarCustomSingular
|
||||
class PropertyAccessorCollectionTest_CarCustomSingular
|
||||
{
|
||||
public function addFoo($axis) {}
|
||||
|
||||
@ -55,44 +56,44 @@ class PropertyPathCollectionTest_CarCustomSingular
|
||||
public function getAxes() {}
|
||||
}
|
||||
|
||||
class PropertyPathCollectionTest_Engine
|
||||
class PropertyAccessorCollectionTest_Engine
|
||||
{
|
||||
}
|
||||
|
||||
class PropertyPathCollectionTest_CarOnlyAdder
|
||||
class PropertyAccessorCollectionTest_CarOnlyAdder
|
||||
{
|
||||
public function addAxis($axis) {}
|
||||
|
||||
public function getAxes() {}
|
||||
}
|
||||
|
||||
class PropertyPathCollectionTest_CarOnlyRemover
|
||||
class PropertyAccessorCollectionTest_CarOnlyRemover
|
||||
{
|
||||
public function removeAxis($axis) {}
|
||||
|
||||
public function getAxes() {}
|
||||
}
|
||||
|
||||
class PropertyPathCollectionTest_CarNoAdderAndRemover
|
||||
class PropertyAccessorCollectionTest_CarNoAdderAndRemover
|
||||
{
|
||||
public function getAxes() {}
|
||||
}
|
||||
|
||||
class PropertyPathCollectionTest_CarNoAdderAndRemoverWithProperty
|
||||
class PropertyAccessorCollectionTest_CarNoAdderAndRemoverWithProperty
|
||||
{
|
||||
protected $axes = array();
|
||||
|
||||
public function getAxes() {}
|
||||
}
|
||||
|
||||
class PropertyPathCollectionTest_CompositeCar
|
||||
class PropertyAccessorCollectionTest_CompositeCar
|
||||
{
|
||||
public function getStructure() {}
|
||||
|
||||
public function setStructure($structure) {}
|
||||
}
|
||||
|
||||
class PropertyPathCollectionTest_CarStructure
|
||||
class PropertyAccessorCollectionTest_CarStructure
|
||||
{
|
||||
public function addAxis($axis) {}
|
||||
|
||||
@ -101,44 +102,47 @@ class PropertyPathCollectionTest_CarStructure
|
||||
public function getAxes() {}
|
||||
}
|
||||
|
||||
abstract class PropertyPathCollectionTest extends \PHPUnit_Framework_TestCase
|
||||
abstract class PropertyAccessorCollectionTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var PropertyAccessor
|
||||
*/
|
||||
private $propertyAccessor;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->propertyAccessor = new PropertyAccessor();
|
||||
}
|
||||
|
||||
abstract protected function getCollection(array $array);
|
||||
|
||||
public function testGetValueReadsArrayAccess()
|
||||
{
|
||||
$object = $this->getCollection(array('firstName' => 'Bernhard'));
|
||||
|
||||
$path = new PropertyPath('[firstName]');
|
||||
|
||||
$this->assertEquals('Bernhard', $path->getValue($object));
|
||||
$this->assertEquals('Bernhard', $this->propertyAccessor->getValue($object, '[firstName]'));
|
||||
}
|
||||
|
||||
public function testGetValueReadsNestedArrayAccess()
|
||||
{
|
||||
$object = $this->getCollection(array('person' => array('firstName' => 'Bernhard')));
|
||||
|
||||
$path = new PropertyPath('[person][firstName]');
|
||||
|
||||
$this->assertEquals('Bernhard', $path->getValue($object));
|
||||
$this->assertEquals('Bernhard', $this->propertyAccessor->getValue($object, '[person][firstName]'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\InvalidPropertyException
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfArrayAccessExpected()
|
||||
{
|
||||
$path = new PropertyPath('[firstName]');
|
||||
|
||||
$path->getValue(new \stdClass());
|
||||
$this->propertyAccessor->getValue(new \stdClass(), '[firstName]');
|
||||
}
|
||||
|
||||
public function testSetValueUpdatesArrayAccess()
|
||||
{
|
||||
$object = $this->getCollection(array());
|
||||
|
||||
$path = new PropertyPath('[firstName]');
|
||||
$path->setValue($object, 'Bernhard');
|
||||
$this->propertyAccessor->setValue($object, '[firstName]', 'Bernhard');
|
||||
|
||||
$this->assertEquals('Bernhard', $object['firstName']);
|
||||
}
|
||||
@ -147,20 +151,17 @@ abstract class PropertyPathCollectionTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
$object = $this->getCollection(array());
|
||||
|
||||
$path = new PropertyPath('[person][firstName]');
|
||||
$path->setValue($object, 'Bernhard');
|
||||
$this->propertyAccessor->setValue($object, '[person][firstName]', 'Bernhard');
|
||||
|
||||
$this->assertEquals('Bernhard', $object['person']['firstName']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\InvalidPropertyException
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
||||
*/
|
||||
public function testSetValueThrowsExceptionIfArrayAccessExpected()
|
||||
{
|
||||
$path = new PropertyPath('[firstName]');
|
||||
|
||||
$path->setValue(new \stdClass(), 'Bernhard');
|
||||
$this->propertyAccessor->setValue(new \stdClass(), '[firstName]', 'Bernhard');
|
||||
}
|
||||
|
||||
public function testSetValueCallsAdderAndRemoverForCollections()
|
||||
@ -172,11 +173,9 @@ abstract class PropertyPathCollectionTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
// Don't use a mock in order to test whether the collections are
|
||||
// modified while iterating them
|
||||
$car = new PropertyPathCollectionTest_Car($axesBefore);
|
||||
$car = new PropertyAccessorCollectionTest_Car($axesBefore);
|
||||
|
||||
$path = new PropertyPath('axes');
|
||||
|
||||
$path->setValue($car, $axesMerged);
|
||||
$this->propertyAccessor->setValue($car, 'axes', $axesMerged);
|
||||
|
||||
$this->assertEquals($axesAfter, $car->getAxes());
|
||||
|
||||
@ -191,8 +190,6 @@ abstract class PropertyPathCollectionTest extends \PHPUnit_Framework_TestCase
|
||||
$axesBefore = $this->getCollection(array(1 => 'second', 3 => 'fourth'));
|
||||
$axesAfter = $this->getCollection(array(0 => 'first', 1 => 'second', 2 => 'third'));
|
||||
|
||||
$path = new PropertyPath('structure.axes');
|
||||
|
||||
$car->expects($this->any())
|
||||
->method('getStructure')
|
||||
->will($this->returnValue($structure));
|
||||
@ -210,7 +207,7 @@ abstract class PropertyPathCollectionTest extends \PHPUnit_Framework_TestCase
|
||||
->method('addAxis')
|
||||
->with('third');
|
||||
|
||||
$path->setValue($car, $axesAfter);
|
||||
$this->propertyAccessor->setValue($car, 'structure.axes', $axesAfter);
|
||||
}
|
||||
|
||||
public function testSetValueCallsCustomAdderAndRemover()
|
||||
@ -221,8 +218,6 @@ abstract class PropertyPathCollectionTest extends \PHPUnit_Framework_TestCase
|
||||
$axesBefore = $this->getCollection(array(1 => 'second', 3 => 'fourth'));
|
||||
$axesAfter = $this->getCollection(array(0 => 'first', 1 => 'second', 2 => 'third'));
|
||||
|
||||
$path = new PropertyPath('axes|foo');
|
||||
|
||||
$car->expects($this->at(0))
|
||||
->method('getAxes')
|
||||
->will($this->returnValue($axesBefore));
|
||||
@ -236,43 +231,39 @@ abstract class PropertyPathCollectionTest extends \PHPUnit_Framework_TestCase
|
||||
->method('addFoo')
|
||||
->with('third');
|
||||
|
||||
$path->setValue($car, $axesAfter);
|
||||
$this->propertyAccessor->setValue($car, 'axes|foo', $axesAfter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\InvalidPropertyException
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
||||
*/
|
||||
public function testMapFormToDataFailsIfOnlyAdderFound()
|
||||
public function testSetValueFailsIfOnlyAdderFound()
|
||||
{
|
||||
$car = $this->getMock(__CLASS__ . '_CarOnlyAdder');
|
||||
$axesBefore = $this->getCollection(array(1 => 'second', 3 => 'fourth'));
|
||||
$axesAfter = $this->getCollection(array(0 => 'first', 1 => 'second', 2 => 'third'));
|
||||
|
||||
$path = new PropertyPath('axes');
|
||||
|
||||
$car->expects($this->any())
|
||||
->method('getAxes')
|
||||
->will($this->returnValue($axesBefore));
|
||||
|
||||
$path->setValue($car, $axesAfter);
|
||||
$this->propertyAccessor->setValue($car, 'axes', $axesAfter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\InvalidPropertyException
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
||||
*/
|
||||
public function testMapFormToDataFailsIfOnlyRemoverFound()
|
||||
public function testSetValueFailsIfOnlyRemoverFound()
|
||||
{
|
||||
$car = $this->getMock(__CLASS__ . '_CarOnlyRemover');
|
||||
$axesBefore = $this->getCollection(array(1 => 'second', 3 => 'fourth'));
|
||||
$axesAfter = $this->getCollection(array(0 => 'first', 1 => 'second', 2 => 'third'));
|
||||
|
||||
$path = new PropertyPath('axes');
|
||||
|
||||
$car->expects($this->any())
|
||||
->method('getAxes')
|
||||
->will($this->returnValue($axesBefore));
|
||||
|
||||
$path->setValue($car, $axesAfter);
|
||||
$this->propertyAccessor->setValue($car, 'axes', $axesAfter);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -283,9 +274,9 @@ abstract class PropertyPathCollectionTest extends \PHPUnit_Framework_TestCase
|
||||
$axes = $this->getCollection(array(0 => 'first', 1 => 'second', 2 => 'third'));
|
||||
|
||||
try {
|
||||
$path->setValue($car, $axes);
|
||||
$this->propertyAccessor->setValue($car, $path, $axes);
|
||||
$this->fail('An expected exception was not thrown!');
|
||||
} catch (\Symfony\Component\Form\Exception\Exception $e) {
|
||||
} catch (ExceptionInterface $e) {
|
||||
$this->assertEquals($message, $e->getMessage());
|
||||
}
|
||||
}
|
||||
@ -295,7 +286,7 @@ abstract class PropertyPathCollectionTest extends \PHPUnit_Framework_TestCase
|
||||
$data = array();
|
||||
|
||||
$car = $this->getMock(__CLASS__ . '_CarNoAdderAndRemover');
|
||||
$propertyPath = new PropertyPath('axes');
|
||||
$propertyPath = 'axes';
|
||||
$expectedMessage = sprintf(
|
||||
'Neither element "axes" nor method "setAxes()" exists in class '
|
||||
.'"%s", nor could adders and removers be found based on the '
|
||||
@ -304,7 +295,7 @@ abstract class PropertyPathCollectionTest extends \PHPUnit_Framework_TestCase
|
||||
// .'property path with "|{singular}" to override the guesser)'
|
||||
,
|
||||
get_class($car),
|
||||
implode(', ', (array) $singulars = FormUtil::singularify('Axes'))
|
||||
implode(', ', (array) $singulars = StringUtil::singularify('Axes'))
|
||||
);
|
||||
$data[] = array($car, $propertyPath, $expectedMessage);
|
||||
|
||||
@ -323,7 +314,7 @@ abstract class PropertyPathCollectionTest extends \PHPUnit_Framework_TestCase
|
||||
*/
|
||||
|
||||
$car = $this->getMock(__CLASS__ . '_CarNoAdderAndRemoverWithProperty');
|
||||
$propertyPath = new PropertyPath('axes');
|
||||
$propertyPath = 'axes';
|
||||
$expectedMessage = sprintf(
|
||||
'Property "axes" is not public in class "%s", nor could adders and '
|
||||
.'removers be found based on the guessed singulars: %s'
|
||||
@ -332,7 +323,7 @@ abstract class PropertyPathCollectionTest extends \PHPUnit_Framework_TestCase
|
||||
. '. Maybe you should '
|
||||
.'create the method "setAxes()"?',
|
||||
get_class($car),
|
||||
implode(', ', (array) $singulars = FormUtil::singularify('Axes'))
|
||||
implode(', ', (array) $singulars = StringUtil::singularify('Axes'))
|
||||
);
|
||||
$data[] = array($car, $propertyPath, $expectedMessage);
|
||||
|
@ -9,11 +9,11 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Util;
|
||||
namespace Symfony\Component\PropertyAccess\Tests;
|
||||
|
||||
use Symfony\Component\Form\Tests\Fixtures\CustomArrayObject;
|
||||
|
||||
class PropertyPathCustomArrayObjectTest extends PropertyPathCollectionTest
|
||||
class PropertyAccessorCustomArrayObjectTest extends PropertyAccessorCollectionTest
|
||||
{
|
||||
protected function getCollection(array $array)
|
||||
{
|
@ -0,0 +1,334 @@
|
||||
<?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;
|
||||
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\Author;
|
||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\Magician;
|
||||
|
||||
class PropertyAccessorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var PropertyAccessor
|
||||
*/
|
||||
private $propertyAccessor;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->propertyAccessor = new PropertyAccessor();
|
||||
}
|
||||
|
||||
public function testGetValueReadsArray()
|
||||
{
|
||||
$array = array('firstName' => 'Bernhard');
|
||||
|
||||
$this->assertEquals('Bernhard', $this->propertyAccessor->getValue($array, '[firstName]'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfIndexNotationExpected()
|
||||
{
|
||||
$array = array('firstName' => 'Bernhard');
|
||||
|
||||
$this->propertyAccessor->getValue($array, 'firstName');
|
||||
}
|
||||
|
||||
public function testGetValueReadsZeroIndex()
|
||||
{
|
||||
$array = array('Bernhard');
|
||||
|
||||
$this->assertEquals('Bernhard', $this->propertyAccessor->getValue($array, '[0]'));
|
||||
}
|
||||
|
||||
public function testGetValueReadsIndexWithSpecialChars()
|
||||
{
|
||||
$array = array('%!@$§.' => 'Bernhard');
|
||||
|
||||
$this->assertEquals('Bernhard', $this->propertyAccessor->getValue($array, '[%!@$§.]'));
|
||||
}
|
||||
|
||||
public function testGetValueReadsNestedIndexWithSpecialChars()
|
||||
{
|
||||
$array = array('root' => array('%!@$§.' => 'Bernhard'));
|
||||
|
||||
$this->assertEquals('Bernhard', $this->propertyAccessor->getValue($array, '[root][%!@$§.]'));
|
||||
}
|
||||
|
||||
public function testGetValueReadsArrayWithCustomPropertyPath()
|
||||
{
|
||||
$array = array('child' => array('index' => array('firstName' => 'Bernhard')));
|
||||
|
||||
$this->assertEquals('Bernhard', $this->propertyAccessor->getValue($array, '[child][index][firstName]'));
|
||||
}
|
||||
|
||||
public function testGetValueReadsArrayWithMissingIndexForCustomPropertyPath()
|
||||
{
|
||||
$array = array('child' => array('index' => array()));
|
||||
|
||||
$this->assertNull($this->propertyAccessor->getValue($array, '[child][index][firstName]'));
|
||||
}
|
||||
|
||||
public function testGetValueReadsProperty()
|
||||
{
|
||||
$object = new Author();
|
||||
$object->firstName = 'Bernhard';
|
||||
|
||||
$this->assertEquals('Bernhard', $this->propertyAccessor->getValue($object, 'firstName'));
|
||||
}
|
||||
|
||||
public function testGetValueIgnoresSingular()
|
||||
{
|
||||
$this->markTestSkipped('This feature is temporarily disabled as of 2.1');
|
||||
|
||||
$object = (object) array('children' => 'Many');
|
||||
|
||||
$this->assertEquals('Many', $this->propertyAccessor->getValue($object, 'children|child'));
|
||||
}
|
||||
|
||||
public function testGetValueReadsPropertyWithSpecialCharsExceptDot()
|
||||
{
|
||||
$array = (object) array('%!@$§' => 'Bernhard');
|
||||
|
||||
$this->assertEquals('Bernhard', $this->propertyAccessor->getValue($array, '%!@$§'));
|
||||
}
|
||||
|
||||
public function testGetValueReadsPropertyWithCustomPropertyPath()
|
||||
{
|
||||
$object = new Author();
|
||||
$object->child = array();
|
||||
$object->child['index'] = new Author();
|
||||
$object->child['index']->firstName = 'Bernhard';
|
||||
|
||||
$this->assertEquals('Bernhard', $this->propertyAccessor->getValue($object, 'child[index].firstName'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\PropertyAccessDeniedException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfPropertyIsNotPublic()
|
||||
{
|
||||
$this->propertyAccessor->getValue(new Author(), 'privateProperty');
|
||||
}
|
||||
|
||||
public function testGetValueReadsGetters()
|
||||
{
|
||||
$object = new Author();
|
||||
$object->setLastName('Schussek');
|
||||
|
||||
$this->assertEquals('Schussek', $this->propertyAccessor->getValue($object, 'lastName'));
|
||||
}
|
||||
|
||||
public function testGetValueCamelizesGetterNames()
|
||||
{
|
||||
$object = new Author();
|
||||
$object->setLastName('Schussek');
|
||||
|
||||
$this->assertEquals('Schussek', $this->propertyAccessor->getValue($object, 'last_name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\PropertyAccessDeniedException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfGetterIsNotPublic()
|
||||
{
|
||||
$this->propertyAccessor->getValue(new Author(), 'privateGetter');
|
||||
}
|
||||
|
||||
public function testGetValueReadsIssers()
|
||||
{
|
||||
$object = new Author();
|
||||
$object->setAustralian(false);
|
||||
|
||||
$this->assertFalse($this->propertyAccessor->getValue($object, 'australian'));
|
||||
}
|
||||
|
||||
public function testGetValueReadHassers()
|
||||
{
|
||||
$object = new Author();
|
||||
$object->setReadPermissions(true);
|
||||
|
||||
$this->assertTrue($this->propertyAccessor->getValue($object, 'read_permissions'));
|
||||
}
|
||||
|
||||
public function testGetValueReadsMagicGet()
|
||||
{
|
||||
$object = new Magician();
|
||||
$object->__set('magicProperty', 'foobar');
|
||||
|
||||
$this->assertSame('foobar', $this->propertyAccessor->getValue($object, 'magicProperty'));
|
||||
}
|
||||
|
||||
/*
|
||||
* https://github.com/symfony/symfony/pull/4450
|
||||
*/
|
||||
public function testGetValueReadsMagicGetThatReturnsConstant()
|
||||
{
|
||||
$object = new Magician();
|
||||
|
||||
$this->assertNull($this->propertyAccessor->getValue($object, 'magicProperty'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\PropertyAccessDeniedException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfIsserIsNotPublic()
|
||||
{
|
||||
$this->propertyAccessor->getValue(new Author(), 'privateIsser');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfPropertyDoesNotExist()
|
||||
{
|
||||
$this->propertyAccessor->getValue(new Author(), 'foobar');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfNotObjectOrArray()
|
||||
{
|
||||
$this->propertyAccessor->getValue('baz', 'foobar');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfNull()
|
||||
{
|
||||
$this->propertyAccessor->getValue(null, 'foobar');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testGetValueThrowsExceptionIfEmpty()
|
||||
{
|
||||
$this->propertyAccessor->getValue('', 'foobar');
|
||||
}
|
||||
|
||||
public function testSetValueUpdatesArrays()
|
||||
{
|
||||
$array = array();
|
||||
|
||||
$this->propertyAccessor->setValue($array, '[firstName]', 'Bernhard');
|
||||
|
||||
$this->assertEquals(array('firstName' => 'Bernhard'), $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException
|
||||
*/
|
||||
public function testSetValueThrowsExceptionIfIndexNotationExpected()
|
||||
{
|
||||
$array = array();
|
||||
|
||||
$this->propertyAccessor->setValue($array, 'firstName', 'Bernhard');
|
||||
}
|
||||
|
||||
public function testSetValueUpdatesArraysWithCustomPropertyPath()
|
||||
{
|
||||
$array = array();
|
||||
|
||||
$this->propertyAccessor->setValue($array, '[child][index][firstName]', 'Bernhard');
|
||||
|
||||
$this->assertEquals(array('child' => array('index' => array('firstName' => 'Bernhard'))), $array);
|
||||
}
|
||||
|
||||
public function testSetValueUpdatesProperties()
|
||||
{
|
||||
$object = new Author();
|
||||
|
||||
$this->propertyAccessor->setValue($object, 'firstName', 'Bernhard');
|
||||
|
||||
$this->assertEquals('Bernhard', $object->firstName);
|
||||
}
|
||||
|
||||
public function testSetValueUpdatesPropertiesWithCustomPropertyPath()
|
||||
{
|
||||
$object = new Author();
|
||||
$object->child = array();
|
||||
$object->child['index'] = new Author();
|
||||
|
||||
$this->propertyAccessor->setValue($object, 'child[index].firstName', 'Bernhard');
|
||||
|
||||
$this->assertEquals('Bernhard', $object->child['index']->firstName);
|
||||
}
|
||||
|
||||
public function testSetValueUpdateMagicSet()
|
||||
{
|
||||
$object = new Magician();
|
||||
|
||||
$this->propertyAccessor->setValue($object, 'magicProperty', 'foobar');
|
||||
|
||||
$this->assertEquals('foobar', $object->__get('magicProperty'));
|
||||
}
|
||||
|
||||
public function testSetValueUpdatesSetters()
|
||||
{
|
||||
$object = new Author();
|
||||
|
||||
$this->propertyAccessor->setValue($object, 'lastName', 'Schussek');
|
||||
|
||||
$this->assertEquals('Schussek', $object->getLastName());
|
||||
}
|
||||
|
||||
public function testSetValueCamelizesSetterNames()
|
||||
{
|
||||
$object = new Author();
|
||||
|
||||
$this->propertyAccessor->setValue($object, 'last_name', 'Schussek');
|
||||
|
||||
$this->assertEquals('Schussek', $object->getLastName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\PropertyAccessDeniedException
|
||||
*/
|
||||
public function testSetValueThrowsExceptionIfGetterIsNotPublic()
|
||||
{
|
||||
$this->propertyAccessor->setValue(new Author(), 'privateSetter', 'foobar');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testSetValueThrowsExceptionIfNotObjectOrArray()
|
||||
{
|
||||
$value = 'baz';
|
||||
|
||||
$this->propertyAccessor->setValue($value, 'foobar', 'bam');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testSetValueThrowsExceptionIfNull()
|
||||
{
|
||||
$value = null;
|
||||
|
||||
$this->propertyAccessor->setValue($value, 'foobar', 'bam');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testSetValueThrowsExceptionIfEmpty()
|
||||
{
|
||||
$value = '';
|
||||
|
||||
$this->propertyAccessor->setValue($value, 'foobar', 'bam');
|
||||
}
|
||||
}
|
@ -9,10 +9,10 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Util;
|
||||
namespace Symfony\Component\PropertyAccess\Tests;
|
||||
|
||||
use Symfony\Component\Form\Util\PropertyPath;
|
||||
use Symfony\Component\Form\Util\PropertyPathBuilder;
|
||||
use Symfony\Component\PropertyAccess\PropertyPath;
|
||||
use Symfony\Component\PropertyAccess\PropertyPathBuilder;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
191
src/Symfony/Component/PropertyAccess/Tests/PropertyPathTest.php
Normal file
191
src/Symfony/Component/PropertyAccess/Tests/PropertyPathTest.php
Normal file
@ -0,0 +1,191 @@
|
||||
<?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;
|
||||
|
||||
use Symfony\Component\PropertyAccess\PropertyPath;
|
||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\Author;
|
||||
use Symfony\Component\PropertyAccess\Tests\Fixtures\Magician;
|
||||
|
||||
class PropertyPathTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testToString()
|
||||
{
|
||||
$path = new PropertyPath('reference.traversable[index].property');
|
||||
|
||||
$this->assertEquals('reference.traversable[index].property', $path->__toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException
|
||||
*/
|
||||
public function testInvalidPropertyPath_noDotBeforeProperty()
|
||||
{
|
||||
new PropertyPath('[index]property');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException
|
||||
*/
|
||||
public function testInvalidPropertyPath_dotAtTheBeginning()
|
||||
{
|
||||
new PropertyPath('.property');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException
|
||||
*/
|
||||
public function testInvalidPropertyPath_unexpectedCharacters()
|
||||
{
|
||||
new PropertyPath('property.$foo');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException
|
||||
*/
|
||||
public function testInvalidPropertyPath_empty()
|
||||
{
|
||||
new PropertyPath('');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testInvalidPropertyPath_null()
|
||||
{
|
||||
new PropertyPath(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testInvalidPropertyPath_false()
|
||||
{
|
||||
new PropertyPath(false);
|
||||
}
|
||||
|
||||
public function testValidPropertyPath_zero()
|
||||
{
|
||||
new PropertyPath('0');
|
||||
}
|
||||
|
||||
public function testGetParent_dot()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent.child');
|
||||
|
||||
$this->assertEquals(new PropertyPath('grandpa.parent'), $propertyPath->getParent());
|
||||
}
|
||||
|
||||
public function testGetParent_index()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$this->assertEquals(new PropertyPath('grandpa.parent'), $propertyPath->getParent());
|
||||
}
|
||||
|
||||
public function testGetParent_noParent()
|
||||
{
|
||||
$propertyPath = new PropertyPath('path');
|
||||
|
||||
$this->assertNull($propertyPath->getParent());
|
||||
}
|
||||
|
||||
public function testCopyConstructor()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
$copy = new PropertyPath($propertyPath);
|
||||
|
||||
$this->assertEquals($propertyPath, $copy);
|
||||
}
|
||||
|
||||
public function testGetElement()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$this->assertEquals('child', $propertyPath->getElement(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OutOfBoundsException
|
||||
*/
|
||||
public function testGetElementDoesNotAcceptInvalidIndices()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$propertyPath->getElement(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OutOfBoundsException
|
||||
*/
|
||||
public function testGetElementDoesNotAcceptNegativeIndices()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$propertyPath->getElement(-1);
|
||||
}
|
||||
|
||||
public function testIsProperty()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$this->assertTrue($propertyPath->isProperty(1));
|
||||
$this->assertFalse($propertyPath->isProperty(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OutOfBoundsException
|
||||
*/
|
||||
public function testIsPropertyDoesNotAcceptInvalidIndices()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$propertyPath->isProperty(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OutOfBoundsException
|
||||
*/
|
||||
public function testIsPropertyDoesNotAcceptNegativeIndices()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$propertyPath->isProperty(-1);
|
||||
}
|
||||
|
||||
public function testIsIndex()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$this->assertFalse($propertyPath->isIndex(1));
|
||||
$this->assertTrue($propertyPath->isIndex(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OutOfBoundsException
|
||||
*/
|
||||
public function testIsIndexDoesNotAcceptInvalidIndices()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$propertyPath->isIndex(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OutOfBoundsException
|
||||
*/
|
||||
public function testIsIndexDoesNotAcceptNegativeIndices()
|
||||
{
|
||||
$propertyPath = new PropertyPath('grandpa.parent[child]');
|
||||
|
||||
$propertyPath->isIndex(-1);
|
||||
}
|
||||
}
|
@ -9,11 +9,11 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Util;
|
||||
namespace Symfony\Component\PropertyAccess\Tests;
|
||||
|
||||
use Symfony\Component\Form\Util\FormUtil;
|
||||
use Symfony\Component\PropertyAccess\StringUtil;
|
||||
|
||||
class FormUtilTest extends \PHPUnit_Framework_TestCase
|
||||
class StringUtilTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function singularifyProvider()
|
||||
{
|
||||
@ -130,6 +130,6 @@ class FormUtilTest extends \PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testSingularify($plural, $singular)
|
||||
{
|
||||
$this->assertEquals($singular, FormUtil::singularify($plural));
|
||||
$this->assertEquals($singular, StringUtil::singularify($plural));
|
||||
}
|
||||
}
|
31
src/Symfony/Component/PropertyAccess/composer.json
Normal file
31
src/Symfony/Component/PropertyAccess/composer.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "symfony/property-access",
|
||||
"type": "library",
|
||||
"description": "Symfony PropertyAccess Component",
|
||||
"keywords": ["property", "index", "access", "object", "array", "extraction", "injection", "reflection", "property path"],
|
||||
"homepage": "http://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "http://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "Symfony\\Component\\PropertyAccess\\": "" }
|
||||
},
|
||||
"target-dir": "Symfony/Component/PropertyAccess",
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.2-dev"
|
||||
}
|
||||
}
|
||||
}
|
29
src/Symfony/Component/PropertyAccess/phpunit.xml.dist
Normal file
29
src/Symfony/Component/PropertyAccess/phpunit.xml.dist
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Symfony PropertyAccess Component Test Suite">
|
||||
<directory>./Tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./</directory>
|
||||
<exclude>
|
||||
<directory>./Resources</directory>
|
||||
<directory>./Tests</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
Reference in New Issue
Block a user