Skip to content

Commit 8b14495

Browse files
committed
add new way to write and/or read values to/from an object or array using callback functions
1 parent bd26785 commit 8b14495

16 files changed

+832
-27
lines changed

UPGRADE-5.2.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,28 @@ FrameworkBundle
1212
* Deprecated the public `form.factory`, `form.type.file`, `translator`, `security.csrf.token_manager`, `serializer`,
1313
`cache_clearer`, `filesystem` and `validator` services to private.
1414

15+
Form
16+
----
17+
18+
* Deprecated `PropertyPathMapper` in favor of `DataMapper` and `PropertyPathAccessor`.
19+
20+
Before:
21+
22+
```php
23+
use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper;
24+
25+
$builder->setDataMapper(new PropertyPathMapper());
26+
```
27+
28+
After:
29+
30+
```php
31+
use Symfony\Component\Form\Extension\Core\DataAccessor\PropertyPathAccessor;
32+
use Symfony\Component\Form\Extension\Core\DataMapper\DataMapper;
33+
34+
$builder->setDataMapper(new DataMapper(new PropertyPathAccessor()));
35+
```
36+
1537
Mime
1638
----
1739

UPGRADE-6.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Form
4848
* Added argument `callable|null $filter` to `ChoiceListFactoryInterface::createListFromChoices()` and `createListFromLoader()`.
4949
* The `Symfony\Component\Form\Extension\Validator\Util\ServerParams` class has been removed, use its parent `Symfony\Component\Form\Util\ServerParams` instead.
5050
* The `NumberToLocalizedStringTransformer::ROUND_*` constants have been removed, use `\NumberFormatter::ROUND_*` instead.
51+
* Removed `PropertyPathMapper` in favor of `DataMapper` and `PropertyPathAccessor`.
5152

5253
FrameworkBundle
5354
---------------

src/Symfony/Component/Form/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ CHANGELOG
44
5.2.0
55
-----
66

7-
* added `FormErrorNormalizer`
7+
* added `FormErrorNormalizer`
8+
* added `DataMapper`, `PropertyPathAccessor` and `CallbackAccessor` with new callable `get` and `set` options for each form type
9+
* deprecated `PropertyPathMapper` in favor of `DataMapper` and `PropertyPathAccessor`
810

911
5.1.0
1012
-----
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form;
13+
14+
/**
15+
* Writes and reads values to/from an object or array bound to a form.
16+
*
17+
* @author Yonel Ceruto <yonelceruto@gmail.com>
18+
*/
19+
interface DataAccessorInterface
20+
{
21+
/**
22+
* Returns the value at the end of the property of the object graph.
23+
*
24+
* @param object|array $objectOrArray The object or array to traverse
25+
* @param FormInterface $form The {@link FormInterface()} instance to check
26+
*/
27+
public function getValue($objectOrArray, FormInterface $form);
28+
29+
/**
30+
* Sets the value at the end of the property of the object graph.
31+
*
32+
* @param object|array $objectOrArray The object or array to modify
33+
* @param mixed $value The value to set at the end of the object graph
34+
* @param FormInterface $form The {@link FormInterface()} instance to check
35+
*/
36+
public function setValue(&$objectOrArray, $value, FormInterface $form): void;
37+
38+
/**
39+
* Returns whether a value can be read from an object graph.
40+
*
41+
* Whenever this method returns true, {@link getValue()} is guaranteed not
42+
* to throw an exception when called with the same arguments.
43+
*
44+
* @param object|array $objectOrArray The object or array to check
45+
* @param FormInterface $form The {@link FormInterface()} instance to check
46+
*
47+
* @return bool Whether the value can be read
48+
*/
49+
public function isReadable($objectOrArray, FormInterface $form): bool;
50+
51+
/**
52+
* Returns whether a value can be written at a given object graph.
53+
*
54+
* Whenever this method returns true, {@link setValue()} is guaranteed not
55+
* to throw an exception when called with the same arguments.
56+
*
57+
* @param object|array $objectOrArray The object or array to check
58+
* @param FormInterface $form The {@link FormInterface()} instance to check
59+
*
60+
* @return bool Whether the value can be set
61+
*/
62+
public function isWritable($objectOrArray, FormInterface $form): bool;
63+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Core\DataAccessor;
13+
14+
use Symfony\Component\Form\DataAccessorInterface;
15+
use Symfony\Component\Form\FormInterface;
16+
17+
/**
18+
* Writes and reads values to/from an object or array using callback functions.
19+
*
20+
* @author Yonel Ceruto <yonelceruto@gmail.com>
21+
*/
22+
class CallbackAccessor implements DataAccessorInterface
23+
{
24+
private $fallbackAccessor;
25+
26+
public function __construct(DataAccessorInterface $fallbackAccessor)
27+
{
28+
$this->fallbackAccessor = $fallbackAccessor;
29+
}
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function getValue($data, FormInterface $form)
35+
{
36+
if (null !== $getter = $form->getConfig()->getOption('get')) {
37+
return ($getter)($data, $form);
38+
}
39+
40+
return $this->fallbackAccessor->getValue($data, $form);
41+
}
42+
43+
/**
44+
* {@inheritdoc}
45+
*/
46+
public function setValue(&$data, $value, FormInterface $form): void
47+
{
48+
if (null !== $setter = $form->getConfig()->getOption('set')) {
49+
($setter)($data, $form->getData(), $form);
50+
} else {
51+
$this->fallbackAccessor->setValue($data, $value, $form);
52+
}
53+
}
54+
55+
/**
56+
* {@inheritdoc}
57+
*/
58+
public function isReadable($data, FormInterface $form): bool
59+
{
60+
if (null !== $form->getConfig()->getOption('get')) {
61+
return true;
62+
}
63+
64+
return $this->fallbackAccessor->isReadable($data, $form);
65+
}
66+
67+
/**
68+
* {@inheritdoc}
69+
*/
70+
public function isWritable($data, FormInterface $form): bool
71+
{
72+
if (null !== $form->getConfig()->getOption('set')) {
73+
return true;
74+
}
75+
76+
return $this->fallbackAccessor->isWritable($data, $form);
77+
}
78+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Core\DataAccessor;
13+
14+
use Symfony\Component\Form\DataAccessorInterface;
15+
use Symfony\Component\Form\FormInterface;
16+
use Symfony\Component\PropertyAccess\Exception\AccessException;
17+
use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
18+
use Symfony\Component\PropertyAccess\PropertyAccess;
19+
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
20+
21+
/**
22+
* Writes and reads values to/from an object or array using property path.
23+
*
24+
* @author Yonel Ceruto <yonelceruto@gmail.com>
25+
* @author Bernhard Schussek <bschussek@gmail.com>
26+
*/
27+
class PropertyPathAccessor implements DataAccessorInterface
28+
{
29+
private $propertyAccessor;
30+
private $fallbackAccessor;
31+
32+
public function __construct(PropertyAccessorInterface $propertyAccessor = null, DataAccessorInterface $fallbackAccessor = null)
33+
{
34+
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
35+
$this->fallbackAccessor = $fallbackAccessor;
36+
}
37+
38+
/**
39+
* {@inheritdoc}
40+
*/
41+
public function getValue($data, FormInterface $form)
42+
{
43+
if (null !== $propertyPath = $form->getPropertyPath()) {
44+
return $this->getPropertyValue($data, $propertyPath);
45+
}
46+
47+
return $this->fallbackAccessor->getValue($data, $form);
48+
}
49+
50+
/**
51+
* {@inheritdoc}
52+
*/
53+
public function setValue(&$data, $propertyValue, FormInterface $form): void
54+
{
55+
if (null !== $propertyPath = $form->getPropertyPath()) {
56+
// If the field is of type DateTimeInterface and the data is the same skip the update to
57+
// keep the original object hash
58+
if ($propertyValue instanceof \DateTimeInterface && $propertyValue == $this->getPropertyValue($data, $propertyPath)) {
59+
return;
60+
}
61+
62+
// If the data is identical to the value in $data, we are
63+
// dealing with a reference
64+
if (!\is_object($data) || !$form->getConfig()->getByReference() || $propertyValue !== $this->getPropertyValue($data, $propertyPath)) {
65+
$this->propertyAccessor->setValue($data, $propertyPath, $propertyValue);
66+
}
67+
} else {
68+
$this->fallbackAccessor->setValue($data, $propertyValue, $form);
69+
}
70+
}
71+
72+
/**
73+
* {@inheritdoc}
74+
*/
75+
public function isReadable($data, FormInterface $form): bool
76+
{
77+
if (null !== $form->getPropertyPath()) {
78+
return true;
79+
}
80+
81+
if (null === $this->fallbackAccessor) {
82+
return false;
83+
}
84+
85+
return $this->fallbackAccessor->isReadable($data, $form);
86+
}
87+
88+
/**
89+
* {@inheritdoc}
90+
*/
91+
public function isWritable($data, FormInterface $form): bool
92+
{
93+
if (null !== $form->getPropertyPath()) {
94+
return true;
95+
}
96+
97+
if (null === $this->fallbackAccessor) {
98+
return false;
99+
}
100+
101+
return $this->fallbackAccessor->isWritable($data, $form);
102+
}
103+
104+
private function getPropertyValue($data, $propertyPath)
105+
{
106+
try {
107+
return $this->propertyAccessor->getValue($data, $propertyPath);
108+
} catch (AccessException $e) {
109+
if (!$e instanceof UninitializedPropertyException
110+
// For versions without UninitializedPropertyException check the exception message
111+
&& (class_exists(UninitializedPropertyException::class) || false === strpos($e->getMessage(), 'You should initialize it'))
112+
) {
113+
throw $e;
114+
}
115+
116+
return null;
117+
}
118+
}
119+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Core\DataMapper;
13+
14+
use Symfony\Component\Form\DataAccessorInterface;
15+
use Symfony\Component\Form\DataMapperInterface;
16+
use Symfony\Component\Form\Exception\UnexpectedTypeException;
17+
use Symfony\Component\Form\Extension\Core\DataAccessor\CallbackAccessor;
18+
use Symfony\Component\Form\Extension\Core\DataAccessor\PropertyPathAccessor;
19+
20+
/**
21+
* Maps arrays/objects to/from forms using data accessors.
22+
*
23+
* @author Bernhard Schussek <bschussek@gmail.com>
24+
*/
25+
class DataMapper implements DataMapperInterface
26+
{
27+
private $dataAccessor;
28+
29+
public function __construct(DataAccessorInterface $dataAccessor = null)
30+
{
31+
$this->dataAccessor = $dataAccessor ?? new CallbackAccessor(new PropertyPathAccessor());
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function mapDataToForms($data, iterable $forms): void
38+
{
39+
$empty = null === $data || [] === $data;
40+
41+
if (!$empty && !\is_array($data) && !\is_object($data)) {
42+
throw new UnexpectedTypeException($data, 'object, array or empty');
43+
}
44+
45+
foreach ($forms as $form) {
46+
$config = $form->getConfig();
47+
48+
if (!$empty && $this->dataAccessor->isReadable($data, $form) && $config->getMapped()) {
49+
$form->setData($this->dataAccessor->getValue($data, $form));
50+
} else {
51+
$form->setData($config->getData());
52+
}
53+
}
54+
}
55+
56+
/**
57+
* {@inheritdoc}
58+
*/
59+
public function mapFormsToData(iterable $forms, &$data): void
60+
{
61+
if (null === $data) {
62+
return;
63+
}
64+
65+
if (!\is_array($data) && !\is_object($data)) {
66+
throw new UnexpectedTypeException($data, 'object, array or empty');
67+
}
68+
69+
foreach ($forms as $form) {
70+
$config = $form->getConfig();
71+
72+
// Write-back is disabled if the form is not synchronized (transformation failed),
73+
// if the form was not submitted and if the form is disabled (modification not allowed)
74+
if ($this->dataAccessor->isWritable($data, $form) && $config->getMapped() && $form->isSubmitted() && $form->isSynchronized() && !$form->isDisabled()) {
75+
$this->dataAccessor->setValue($data, $form->getData(), $form);
76+
}
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy