One technology evolution sparks naturally another one. When electricity became accessible to masses, a huge industry of home-electric tools became possible. Like this tool, I currently write on.
The same thing happens in software, just exponentially faster. Like tokens and AST sparked tools that change your code.
Recently, I introduced Symfony Static Dumper that uses YAML to store data in your Symfony application. You where this goes... how can we turn this YAML into objects?
Disclaimer: this post is not about array vs. object performance. If you still prefer arrays, check this talk by Nikita Popov that changed my mind.
This post is about the luxury of object IDE autocompletion everywhere in your code. And how to make it happen, when all you have in the start are arrays (JSON, YAML...).
Do you work with Doctrine entities? Then you're probably used to use Repository service and Entity object:
<?php
declare(strict_types=1);
namespace Pehapkari\Blog\Repository;
use App\Entity\Post;
use Doctrine\Persistence\ObjectRepository;
use Doctrine\ORM\EntityManagerInterface;
final class PostRepository
{
private ObjectRepository $repository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->repository = $entityManager->getRepository(Post::class);
}
/**
* @return Post[]
*/
public function fetchAll(): array
{
return $this->repository->fetchAll();
}
}
Then we can use reliable objects everywhere in application, like controllers:
final class BlogController
{
// ...
public function __invoke(): Response
{
return $this->render('blog/post.twig', [
'posts' => $this->postRepository->fetchAll(),
]);
}
}
And we also have autocomplete in TWIG templates (thanks to amazing Symfony plugin/):
Each local PHP community produces videos, livestreams, or talk recordings. We make such videos too, and we store them in YAML format. How can we get objects from that?
Let's use the most straightforward example possible.
parameters:
videos:
-
title: 'How to Hydrate objects to Arrays'
created_at: '2020-04-20'
In the application, we want to use an object:
<?php
declare(strict_types=1);
namespace App\ValueObject;
use DateTimeInterface;
final class Video
{
private string $name;
private DateTimeInterface $createdAt;
public function __construct(string $name, DateTimeInterface $createdAt)
{
$this->name = $name;
$this->createdAt = $createdAt;
}
public function getName(): string
{
return $this->name;
}
public function getCreatedAt(): DateTimeInterface
{
return $this->createdAt;
}
}
We want our application to be:
Saying that, the code should look like 1:1 to repositories we know from Doctrine:
final class VideoController
{
// ...
public function __invoke(): Response
{
return $this->render('video/videos.twig', [
'videos' => $this->videoRepository->fetchAll(),
]);
}
}
You can hydrate input of guzzle, arrays from YAML database, or just local data in your PHP code.
This is how we solved our use case:
<?php
declare(strict_types=1);
namespace App\Repository;
use App\ValueObject\Video;
use Symplify\EasyHydrator\ArrayToValueObjectHydrator;
final class VideoRepository
{
/**
* @var Video[]
*/
private $videos = [];
public function __construct(
array $videos,
ArrayToValueObjectHydrator $arrayToValueObjectHydrator
) {
$this->videos = $arrayToValueObjectHydrator->hydrateArrays($videos, Video::class);
}
/**
* @return Video[]
*/
public function fetchAll(): array
{
return $this->videos;
}
}
What is Symplify\EasyHydrator\ArrayToValueObjectHydrator
? Its from new package symplify/easy-hydrator that hydrates arrays to object. Easy.
composer require symplify/easy-hydrator
With Symfony Flex and type in composer.json
, this section became boring.
Then require Symplify\EasyHydrator\ArrayToValueObjectHydrator
in the constructor and use it anywhere.
DateTime
and int
retypes<?php
declare(strict_types=1);
final class Person
{
// ...
public function __construct(string $name, int $age, DateTimeInterface $metAt)
{
// ...
}
}
$person = $this->arrayToValueObjectHydrator->hydrateArray([
'name' => 'Tom',
// will be retyped to int
'age' => '30',
// will be retyped to DateTimeInterface
'metAt' => '2020-02-02',
], Person::class);
Typed properties + initialized are must-have. PHP 7.4 is around for ~6 months now, and people use this feature.
I also looked at Ocramius/GeneratedHydrator and tried to use it, but it doesn't work with PHP 7.4 objects correctly.
This package tries to be 1:1 with the rest of the clean code, so hydrated object must use constructor injection. Private property reflection magic won't work here.
This package hydrates arrays to object via constructor. Nothing more, nothing less. It's for easy and clear use.
I use it in 3 PHP projects now and works great. Also we could get rid of fake object that only autocomplete twig and stopped relying on $video['title']
or $video['name']
guessing all over the code. Win win :)
Happy coding!
Do you learn from my contents or use open-souce packages like Rector every day?
Consider supporting it on GitHub Sponsors.
I'd really appreciate it!