Skip to content

Commit 7b6c4dd

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 7b6c4dd

16 files changed

+806
-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 $decorated;
25+
26+
public function __construct(DataAccessorInterface $decorated)
27+
{
28+
$this->decorated = $decorated;
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->decorated->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->decorated->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->decorated->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->decorated->isWritable($data, $form);
77+
}
78+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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+
31+
public function __construct(PropertyAccessorInterface $propertyAccessor = null)
32+
{
33+
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
34+
}
35+
36+
/**
37+
* {@inheritdoc}
38+
*/
39+
public function getValue($data, FormInterface $form)
40+
{
41+
return $this->getPropertyValue($data, $form->getPropertyPath());
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
public function setValue(&$data, $propertyValue, FormInterface $form): void
48+
{
49+
$propertyPath = $form->getPropertyPath();
50+
51+
// If the field is of type DateTimeInterface and the data is the same skip the update to
52+
// keep the original object hash
53+
if ($propertyValue instanceof \DateTimeInterface && $propertyValue == $this->getPropertyValue($data, $propertyPath)) {
54+
return;
55+
}
56+
57+
// If the data is identical to the value in $data, we are
58+
// dealing with a reference
59+
if (!\is_object($data) || !$form->getConfig()->getByReference() || $propertyValue !== $this->getPropertyValue($data, $propertyPath)) {
60+
$this->propertyAccessor->setValue($data, $propertyPath, $propertyValue);
61+
}
62+
}
63+
64+
/**
65+
* {@inheritdoc}
66+
*/
67+
public function isReadable($data, FormInterface $form): bool
68+
{
69+
return null !== $form->getPropertyPath();
70+
}
71+
72+
/**
73+
* {@inheritdoc}
74+
*/
75+
public function isWritable($data, FormInterface $form): bool
76+
{
77+
return null !== $form->getPropertyPath();
78+
}
79+
80+
private function getPropertyValue($data, $propertyPath)
81+
{
82+
try {
83+
return $this->propertyAccessor->getValue($data, $propertyPath);
84+
} catch (AccessException $e) {
85+
if (!$e instanceof UninitializedPropertyException
86+
// For versions without UninitializedPropertyException check the exception message
87+
&& (class_exists(UninitializedPropertyException::class) || false === strpos($e->getMessage(), 'You should initialize it'))
88+
) {
89+
throw $e;
90+
}
91+
92+
return null;
93+
}
94+
}
95+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
class DataMapper implements DataMapperInterface
24+
{
25+
private $dataAccessor;
26+
27+
public function __construct(DataAccessorInterface $dataAccessor = null)
28+
{
29+
$this->dataAccessor = $dataAccessor ?? new CallbackAccessor(new PropertyPathAccessor());
30+
}
31+
32+
/**
33+
* {@inheritdoc}
34+
*/
35+
public function mapDataToForms($data, iterable $forms): void
36+
{
37+
$empty = null === $data || [] === $data;
38+
39+
if (!$empty && !\is_array($data) && !\is_object($data)) {
40+
throw new UnexpectedTypeException($data, 'object, array or empty');
41+
}
42+
43+
foreach ($forms as $form) {
44+
$config = $form->getConfig();
45+
46+
if (!$empty && $this->dataAccessor->isReadable($data, $form) && $config->getMapped()) {
47+
$form->setData($this->dataAccessor->getValue($data, $form));
48+
} else {
49+
$form->setData($config->getData());
50+
}
51+
}
52+
}
53+
54+
/**
55+
* {@inheritdoc}
56+
*/
57+
public function mapFormsToData(iterable $forms, &$data): void
58+
{
59+
if (null === $data) {
60+
return;
61+
}
62+
63+
if (!\is_array($data) && !\is_object($data)) {
64+
throw new UnexpectedTypeException($data, 'object, array or empty');
65+
}
66+
67+
foreach ($forms as $form) {
68+
$config = $form->getConfig();
69+
70+
// Write-back is disabled if the form is not synchronized (transformation failed),
71+
// if the form was not submitted and if the form is disabled (modification not allowed)
72+
if ($this->dataAccessor->isWritable($data, $form) && $config->getMapped() && $form->isSubmitted() && $form->isSynchronized() && !$form->isDisabled()) {
73+
$this->dataAccessor->setValue($data, $form->getData(), $form);
74+
}
75+
}
76+
}
77+
}

src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@
1818
use Symfony\Component\PropertyAccess\PropertyAccess;
1919
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
2020

21+
trigger_deprecation('symfony/form', '5.2', 'The "%s" class is deprecated.', PropertyPathMapper::class);
22+
2123
/**
2224
* Maps arrays/objects to/from forms using property paths.
2325
*
2426
* @author Bernhard Schussek <bschussek@gmail.com>
27+
*
28+
* @deprecated since symfony/form 5.2
2529
*/
2630
class PropertyPathMapper implements DataMapperInterface
2731
{

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