Communicate which element fails in ArraySerializableHydrator
meidlinga opened this issue · 2 comments
Feature Request
Q | A |
---|---|
New Feature | yes |
RFC | no |
BC Break | no |
Summary
When using the ArraySerializableHydrator and one of the strategies fails, it is impossible to see without debugging, which element failed. To achive this, I would see two options:
- The strategy could throw or log an error. This is currently impossible, since the strategy does not know the name of the value to extract during extraction.
- The ArraySerializableHydrator could catch throwables during extraction and rethrow them with the field name as information included in the message.
I would prefer the second option, since it would not change an existing interface.
Example
try {
$data[$name] = $this->extractValue($name, $value, $object);
} catch (\Throwable $t) {
throw new \Exception("Could not extract field " . $name, 0, $t);
}
instead of
$data[$name] = $this->extractValue($name, $value, $object);
@meidlinga
Can you provide a code example which illustrates the problem?
Thanks in advance! 👍
Hi @froschdesign ,
Thanks for the fast response. If this is accepted, I would be happy to provide a PR.
Please consider the following executable example.
It contains two different cases. One throwing an exception and a second one generating a type error. Additionally to extract, I included the same showcase for hydrate
Exceptions
Without the patch, the exception will be:
Exception: value cant be null in src/hydratortest.php:29
Stack trace:
#0 vendor/laminas/laminas-hydrator/src/AbstractHydrator.php(124): ExceptionStrategy->extract()
#1 vendor/laminas/laminas-hydrator/src/ArraySerializableHydrator.php(55): Laminas\Hydrator\AbstractHydrator->extractValue()
#2 src/hydratortest.php(54): Laminas\Hydrator\ArraySerializableHydrator->extract()
#3 {main}
It is not visible, which field was responsible for the error,
With the suggested patch it looks like this:
Exception: value cant be null in src/hydratortest.php:29
Stack trace:
#0 vendor/laminas/laminas-hydrator/src/AbstractHydrator.php(124): ExceptionStrategy->extract()
#1 vendor/laminas/laminas-hydrator/src/ArraySerializableHydrator.php(55): Laminas\Hydrator\AbstractHydrator->extractValue()
#2 src/hydratortest.php(54): Laminas\Hydrator\ArraySerializableHydrator->extract()
#3 {main}
Next Exception: Could not extract field bla in vendor/laminas/laminas-hydrator/src/ArraySerializableHydrator.php:57
Stack trace:
#0 src/hydratortest.php(54): Laminas\Hydrator\ArraySerializableHydrator->extract()
#1 {main}
Example code
<?php
include "../vendor/autoload.php";
use Laminas\Stdlib\ArraySerializableInterface;
use Laminas\Hydrator\ArraySerializableHydrator;
use Laminas\Hydrator\Strategy\DefaultStrategy;
class TestEntity implements ArraySerializableInterface
{
public function exchangeArray(array $array)
{
// Omitted
}
public function getArrayCopy()
{
return ['foo' => 'foo', 'bar' => 'bar', 'bla' => null];
}
}
$testArray = ['foo' => 'foo', 'bar' => null, 'bla' => 'bla'];
class ExceptionStrategy extends DefaultStrategy
{
public function extract($value, ?object $object = null)
{
if (is_null($value)) throw new \Exception("value cant be null");
return $value;
}
public function hydrate($value, ?array $data = null)
{
if (is_null($value)) throw new \Exception("value cant be null");
return $value;
}
}
class ExceptionHydrator extends ArraySerializableHydrator
{
public function __construct()
{
$this->addStrategy('foo', new ExceptionStrategy());
$this->addStrategy('bar', new ExceptionStrategy());
$this->addStrategy('bla', new ExceptionStrategy());
}
}
$exceptionHydrator = new ExceptionHydrator();
echo "# Extract\n";
try {
$exceptionHydrator->extract(new TestEntity());
} catch (\Throwable $t) {
echo $t;
}
echo "\n\n\n# Hydrate\n";
try {
$exceptionHydrator->hydrate($testArray, new TestEntity());
} catch (\Throwable $t) {
echo $t;
}
class TypeErrorStrategy extends DefaultStrategy
{
function typeConstraint(object $value)
{
return value;
}
public function extract($value, ?object $object = null)
{
return $this->typeConstraint($value);
}
public function hydrate($value, ?array $data = null)
{
return $this->typeConstraint($value);
}
}
class TypeErrorHydrator extends ArraySerializableHydrator
{
public function __construct()
{
$this->addStrategy('foo', new TypeErrorStrategy());
$this->addStrategy('bar', new TypeErrorStrategy());
$this->addStrategy('bla', new TypeErrorStrategy());
}
}
$typeErrorHydrator = new TypeErrorHydrator();
echo "\n\n\n# Extract\n";
try {
$res = $typeErrorHydrator->extract(new TestEntity());
print_r($res);
} catch (\Exception $e) {
echo $e->getTraceAsString();
}
echo "\n\n\n# Hydrate\n";
try {
$typeErrorHydrator->hydrate($testArray, new TestEntity());
} catch (\Exception $e) {
echo $e->getTraceAsString();
}