Skip to content

Commit c58671f

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 c58671f

16 files changed

+804
-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: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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+
interface DataAccessorInterface
18+
{
19+
/**
20+
* Returns the value at the end of the object graph.
21+
*
22+
* @param object|array $objectOrArray The object or array to traverse
23+
* @param FormInterface $form The {@link FormInterface()} instance to check
24+
*/
25+
public function getValue($objectOrArray, FormInterface $form);
26+
27+
/**
28+
* Sets the value at the end of the object graph.
29+
*
30+
* @param object|array $objectOrArray The object or array to modify
31+
* @param mixed $value The value to set at the end of the object graph
32+
* @param FormInterface $form The {@link FormInterface()} instance to check
33+
*/
34+
public function setValue(&$objectOrArray, $value, FormInterface $form): void;
35+
36+
/**
37+
* Returns whether a value can be read from an object graph.
38+
*
39+
* Whenever this method returns true, {@link getValue()} is guaranteed not
40+
* to throw an exception when called with the same arguments.
41+
*
42+
* @param object|array $objectOrArray The object or array to check
43+
* @param FormInterface $form The {@link FormInterface()} instance to check
44+
*
45+
* @return bool Whether the value can be read
46+
*/
47+
public function isReadable($objectOrArray, FormInterface $form): bool;
48+
49+
/**
50+
* Returns whether a value can be written at a given object graph.
51+
*
52+
* Whenever this method returns true, {@link setValue()} is guaranteed not
53+
* to throw an exception when called with the same arguments.
54+
*
55+
* @param object|array $objectOrArray The object or array to check
56+
* @param FormInterface $form The {@link FormInterface()} instance to check
57+
*
58+
* @return bool Whether the value can be set
59+
*/
60+
public function isWritable($objectOrArray, FormInterface $form): bool;
61+
}
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