dg / dibi

Dibi - smart database abstraction layer
https://dibiphp.com
Other
487 stars 136 forks source link

Automatic object to Expression conversion #420

Closed milo closed 1 year ago

milo commented 2 years ago

Would be nice to have a capability of automatic object to Dibi expression conversion, similar to BackedEnum conversion.

Explanation

Let's start by date and time. Poor PHP support for separated date and time is one of the pain. Basic classes Date and Time are missing, everyting is DateTime and it is not allowed to implement DateTimeInterface. As a result, one has to specify data type for data modification in database:

$insert = [
   'name' => 'Milo',
   'dateOfBirth%d' => new DateTimeImmutable(...),  # <-- the %d modifier is required
]; 

The %d is always required. And in simple queries the ? modifier can't be used.

Next issue is a fetch-and-push-back workflow. Row is fetched from database and cannot be inserted back without modification. I mean:

$row = $db->query('SELECT name, dateOfBirth FROM ...')->fetch();
$db->insert('...', $row);  # fails

Inserting back fails, because the value for column dateOfBirth is a DateTime object and it is converted to Y-m-d H:i:s string which is not accepted by database as a value for date type.

This applies for all value objects. For example, I have custom implementations for IPv4Address, IPv4Subnet, dtto for IPv6..., EmailAddress and few more.

PHP to SQL

Edit: this part is solved by https://github.com/dg/dibi/pull/432

There was an issue #385 proposing to make Dibi\Expression an interface. I proposed to make a new interface with method toDibiExpression(): Expression. I think that both proposes are invalid. A value object should not be aware of Dibi layer.

I propose to create a new interface which will accept object and it will translate it into Dibi expression.

namespace Dibi;

interface ObjectTranslator
{
    function translate(object $object): ?Expression;
}

Such translator will be setter-injected into Dibi connection:

$db = new Dibi\Connection;
$db->setObjectTranslator(new MyTranslator);

And when Dibi\Translator hits the object of unknown class, it will call ObjectTranslator::translate().

SQL to PHP

(note: I'm not sure about this part I just want to mention that)

Data type translation of fetched rows now works fine. I think that row factory is sufficient. But there is a space for improvement.

Row fetched from a database layer is a set of scalars and dibi tries to convert them as best as possible. If it does not work for one, type can be set manually:

$db->query(...)->setType('column', Type::INT);

Maybe this can be extended to Type::OBJECT:

$db->query(...)->setType('column', Type::OBJECT, MyClass::class);

The ObjectTranslator will be extended by another method:

interface ObjectTranslator
{
    function hydrate(mixed $value, string $class): object;
}

Conclusions

It is all about PHP <-> SQL values translations and I think that this solution is clean enough. There are naming questions. Maybe there can be two separate interfaces for PHP -> SQL and SQL -> PHP.

If you think that whole concept is not completely wrong, I'll prepare proof of concept pull request at least for the 1st part, the PHP to SQL translation.

eydun commented 2 years ago

Looking forward to this improvement 👍

milo commented 1 year ago

I'm closing this issue as a PHP object to Dibi expression is solved by https://github.com/dg/dibi/pull/432

The second case - fetched scalar type to PHP object hydration can be solved by row factory for now and I'm satisfied with that. Maybe I'll open PR for some improvements later.

eydun commented 1 year ago

Hoping there soon will be a new dibi release with this feature 👍