How to prepare data from SQL for nested hydrator strategy

Hey guys,

let’s imagine we have a Mezzio based REST API app.
The data are stored in MariaDB so when retrieving those from DB we get a flat array:

Data from DB:

[
   'name',
   'surname',
   'sample_1',
   'sample_2',
   'sample_3'
]

But the response is supposed to be a nested object (which later on gets serialized to a json).

Expected response:

class Samples
{
   private string $sample_1;
   private string $sample_2;
   private string $sample_3;
}

class Someone
{
  private string $name;
  private string $surname;  
  private Samples $samples;  
}

Using the hydrator strategy allows only binding on some key in the array. So to make that work the original flat array would need to be modified to:

Modified response from DB:

[
   'name',
   'surname',
   'samples' = 
   [
      'sample_1',
      'sample_2',
      'sample_3'
   ]
]

Am I missing something or am I am looking at the issue from a wrong perspective? And of course ideally the above should work nicely with DbSelect so it can be passed to a Paginator.

This can be done in a custom hydrator which uses one of the standard hydrators with added strategies. This custom hydrator modifies the array structure before it is passed to standard hydrator.
Another option can be the AggregateHydrator which handles different parts of the array. But I suggest to try the custom hydrator first.

Thanks a lot @froschdesign - I give it a go and let you know.

So I have created this one way Hydrator to convert the array to a nested array.

class NestedArrayHydrator extends AbstractHydrator
{
    private string $nestedKey;

    public function __construct(string $nestedKey)
    {
        $this->nestedKey = $nestedKey;
    }

    public function hydrate(array $data, object $object): array
    {
        $reflect = new ReflectionClass($object);
        $events = $reflect->getProperties(ReflectionProperty::IS_PUBLIC);

        foreach($events as $event)
        {
            $eventName = $event->getName();
            if (!array_key_exists($eventName, $data))
            {
                throw new InvalidKeyProvided("Key '{$eventName}' not found in the array.");
            }

            $data[$this->nestedKey][$eventName] = $data[$eventName];
            unset($data[$eventName]);
        }

        return $data;
    }

    public function extract(object $object): array
    {
        throw new RuntimeException("Not implemented.");
    }
}

I hope I am not violating the design :slight_smile:

The usage itself can look like this:

$customHydrator = new NestedArrayHydrator('samples');
$restructuredData = $customHydrator->hydrate($data, new Samples());

$hydrator = new ReflectionHydrator();
$hydrator->addStrategy(
    'samples',
    new HydratorStrategy(new ReflectionHydrator(), Samples::class)
);

$hydratedResult = $hydrator->hydrate($restructuredData, new Someone());

I would be more than happy to get your thoughts guys, thx.

Your class NestedArrayHydrator is not a hydrator because nothing is “hydrated”. In this form this makes no sense.

Here an untested example:

final class MyHydrator implements Laminas\Hydrator\HydratorInterface
{
    private Laminas\Hydrator\HydratorInterface $baseHydrator;

    public function __construct(Laminas\Hydrator\HydratorInterface $baseHydrator)
    {
        $this->baseHydrator = $baseHydrator;
    }

    public function extract(object $object): array
    {
        $data = $this->baseHydrator->extract($object);

        // Reformat data here…

        return $data;
    }

    public function hydrate(array $data, object $object)
    {
        // Reformat data here…

        return $this->baseHydrator->hydrate($data, $object);
    }
}

The reformatting can be:

$data['samples'] = [];
if (array_key_exists('sample_1', $data)
    && array_key_exists('sample_2', $data)
    && array_key_exists('sample_3', $data)
) {
    $data['samples'] = [
        $data['sample_1'],
        $data['sample_2'],
        $data['sample_3'],
    ];
}

The usage of the hydrator:

$baseHydrator = new Laminas\Hydrator\ReflectionHydrator();
$baseHydrator->addStrategy(
    'samples',
    new Laminas\Hydrator\Strategy\HydratorStrategy(
        new Laminas\Hydrator\ReflectionHydrator(),
        Samples::class
    )
);
// Add more strategies here if required

$myHydrator = new MyHydrator($baseHydrator);
$myHydrator->hydrate($flatDataArrayFromDatabase, new Someone());
1 Like

Thanks a lot. I have updated the design based on your example. Again, thanks a lot for great support!