Skip to content

Commit

Permalink
Object translator target class is read from its typehint
Browse files Browse the repository at this point in the history
  • Loading branch information
milo authored and dg committed Feb 2, 2023
1 parent a54a801 commit 19e349e
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 20 deletions.
34 changes: 32 additions & 2 deletions src/Dibi/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -533,9 +533,39 @@ public function substitute(string $value): string
/**
* @param callable(object): Expression $translator
*/
public function setObjectTranslator(string $class, callable $translator): void
public function setObjectTranslator(callable $translator): void
{
$this->translators[$class] = $translator;
if (!$translator instanceof \Closure) {
$translator = \Closure::fromCallable($translator);
}

$param = (new \ReflectionFunction($translator))->getParameters();
if (count($param) !== 1) {
throw new Exception('Object translator must have exactly one parameter.');
}

$name = '$' . $param[0]->getName();
$type = $param[0]->getType();
if ($type === null) {
throw new Exception("Object translator parameter '$name' must be declared with typehint.");
} elseif ($type->allowsNull()) {
throw new Exception("Object translator parameter '$name' must not be nullable.");
}

if ($type instanceof \ReflectionNamedType) {
$types = [$type];
} elseif ($type instanceof \ReflectionUnionType) {
$types = $type->getTypes();
} else {
throw new Exception("Object translator parameter '$name' type must be simple or union.");
}

foreach ($types as $type) {
if ($type->isBuiltin()) {
throw new Exception("Object translator parameter '$name' type must be class or interface but '$type' declared.");
}
$this->translators[$type->getName()] = $translator;
}
$this->sortTranslators = true;
}

Expand Down
43 changes: 25 additions & 18 deletions tests/dibi/Connection.objectTranslator.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ test('Without object translator', function () use ($conn) {


test('Basics', function () use ($conn) {
$conn->setObjectTranslator(
Email::class,
fn(Email $email) => new Dibi\Expression('?', $email->address),
);
$conn->setObjectTranslator(fn(Email $email) => new Dibi\Expression('?', $email->address));
Assert::same(
reformat([
'sqlsrv' => "N'[email protected]'",
Expand All @@ -56,10 +53,7 @@ test('DateTime', function () use ($conn) {


// With object translator
$conn->setObjectTranslator(
Time::class,
fn(Time $time) => new Dibi\Expression('OwnTime(?)', $time->format('H:i:s')),
);
$conn->setObjectTranslator(fn(Time $time) => new Dibi\Expression('OwnTime(?)', $time->format('H:i:s')));
Assert::same(
reformat([
'sqlsrv' => "OwnTime(N'12:13:14')",
Expand Down Expand Up @@ -92,10 +86,7 @@ test('DateTime', function () use ($conn) {
);

// But DateTime translation can be overloaded
$conn->setObjectTranslator(
DateTimeInterface::class,
fn (DateTimeInterface $dt) => new Dibi\Expression('OwnDateTime'),
);
$conn->setObjectTranslator(fn(DateTimeInterface $dt) => new Dibi\Expression('OwnDateTime'));
Assert::same(
'OwnDateTime',
$conn->translate('?', $dt),
Expand All @@ -104,9 +95,9 @@ test('DateTime', function () use ($conn) {


test('Complex structures', function () use ($conn) {
$conn->setObjectTranslator(Email::class, fn(Email $email) => new Dibi\Expression('?', $email->address));
$conn->setObjectTranslator(Time::class, fn (Time $time) => new Dibi\Expression('OwnTime(?)', $time->format('H:i:s')));
$conn->setObjectTranslator(DateTimeInterface::class, fn (DateTimeInterface $dt) => new Dibi\Expression('OwnDateTime'));
$conn->setObjectTranslator(fn(Email $email) => new Dibi\Expression('?', $email->address));
$conn->setObjectTranslator(fn(Time $time) => new Dibi\Expression('OwnTime(?)', $time->format('H:i:s')));
$conn->setObjectTranslator(fn(DateTimeInterface $dt) => new Dibi\Expression('OwnDateTime'));

$time = Time::createFromFormat('Y-m-d H:i:s', '2022-11-22 12:13:14');
Assert::same(
Expand All @@ -129,11 +120,27 @@ test('Complex structures', function () use ($conn) {


test('Invalid translator', function () use ($conn) {
$conn->setObjectTranslator(
Email::class,
fn(Email $email) => 'foo',
Assert::exception(
fn() => $conn->setObjectTranslator(fn($email) => 'foo'),
Dibi\Exception::class, "Object translator parameter '\$email' must be declared with typehint.",
);

Assert::exception(
fn() => $conn->setObjectTranslator(fn(string $email) => 'foo'),
Dibi\Exception::class, "Object translator parameter '\$email' type must be class or interface but 'string' declared.",
);

Assert::exception(
fn() => $conn->setObjectTranslator(fn(Email|bool $email) => 'foo'),
Dibi\Exception::class, "Object translator parameter '\$email' type must be class or interface but 'bool' declared.",
);

Assert::exception(
fn() => $conn->setObjectTranslator(fn(Email|null $email) => 'foo'),
Dibi\Exception::class, "Object translator parameter '\$email' must not be nullable.",
);

$conn->setObjectTranslator(fn(Email $email) => 'foo');
Assert::exception(
fn() => $conn->translate('?', new Email),
Dibi\Exception::class, "Object translator for class 'Email' returned 'string' but Dibi\Expression expected.",
Expand Down

0 comments on commit 19e349e

Please sign in to comment.