mirror of
https://github.com/danog/Valinor.git
synced 2025-01-22 13:21:35 +01:00
feat: introduce helper class MessageMapFormatter
Can be used to customize the content of messages added during a mapping. An implementation is provided by the library — `MessageMapFormatter`: The constructor parameter is an array where each key represents either: - The code of the message to be replaced - The content of the message to be replaced - The class name of the message to be replaced If none of those is found, the content of the message will stay unchanged unless a default one is given to this class. If one of these keys is found, the array entry will be used to replace the content of the message. This entry can be either a plain text or a callable that takes the message as a parameter and returns a string; it is for instance advised to use a callable in cases where a translation service is used — to avoid useless greedy operations. In any case, the content can contain placeholders that can be used the same way as `\CuyZ\Valinor\Mapper\Tree\Message\NodeMessage::format()`. See usage examples below: ``` $formatter = (new MessageMapFormatter([ // Will match if the given message has this exact code 'some_code' => 'new content / previous code was: %1$s', // Will match if the given message has this exact content 'Some message content' => 'new content / previous message: %2$s', // Will match if the given message is an instance of this class SomeError::class => ' - Original code of the message: %1$s - Original content of the message: %2$s - Node type: %3$s - Node name: %4$s - Node path: %5$s ', // A callback can be used to get access to the message instance OtherError::class => function (NodeMessage $message): string { if ((string)$message->type() === 'string|int') { // … } return 'Some message content'; }, // For greedy operation, it is advised to use a lazy-callback 'bar' => fn () => $this->translator->translate('foo.bar'), ])) ->defaultsTo('some default message') // …or… ->defaultsTo(fn () => $this->translator->translate('default')); $content = $formatter->format($message); ```
This commit is contained in:
parent
cc1bc66bbe
commit
ddf69efaaa
12
src/Mapper/Tree/Message/Formatter/MessageFormatter.php
Normal file
12
src/Mapper/Tree/Message/Formatter/MessageFormatter.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Mapper\Tree\Message\Formatter;
|
||||
|
||||
use CuyZ\Valinor\Mapper\Tree\Message\NodeMessage;
|
||||
|
||||
interface MessageFormatter
|
||||
{
|
||||
public function format(NodeMessage $message): string;
|
||||
}
|
116
src/Mapper/Tree/Message/Formatter/MessageMapFormatter.php
Normal file
116
src/Mapper/Tree/Message/Formatter/MessageMapFormatter.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Mapper\Tree\Message\Formatter;
|
||||
|
||||
use CuyZ\Valinor\Mapper\Tree\Message\NodeMessage;
|
||||
|
||||
use function get_class;
|
||||
use function is_callable;
|
||||
|
||||
/**
|
||||
* Can be used to customize the content of messages added during a mapping.
|
||||
*
|
||||
* The constructor parameter is an array where each key represents either:
|
||||
* - The code of the message to be replaced
|
||||
* - The content of the message to be replaced
|
||||
* - The class name of the message to be replaced
|
||||
*
|
||||
* If none of those is found, the content of the message will stay unchanged
|
||||
* unless a default one is given to this class.
|
||||
*
|
||||
* If one of these keys is found, the array entry will be used to replace the
|
||||
* content of the message. This entry can be either a plain text or a callable
|
||||
* that takes the message as a parameter and returns a string; it is for
|
||||
* instance advised to use a callable in cases where a translation service is
|
||||
* used — to avoid useless greedy operations.
|
||||
*
|
||||
* In any case, the content can contain placeholders that can be used the same
|
||||
* way as @see \CuyZ\Valinor\Mapper\Tree\Message\NodeMessage::format()
|
||||
*
|
||||
* See usage examples below:
|
||||
*
|
||||
* ```
|
||||
* $formatter = (new MessageMapFormatter([
|
||||
* // Will match if the given message has this exact code
|
||||
* 'some_code' => 'new content / previous code was: %1$s',
|
||||
*
|
||||
* // Will match if the given message has this exact content
|
||||
* 'Some message content' => 'new content / previous message: %2$s',
|
||||
*
|
||||
* // Will match if the given message is an instance of `SomeError`
|
||||
* SomeError::class => '
|
||||
* - Original code of the message: %1$s
|
||||
* - Original content of the message: %2$s
|
||||
* - Node type: %3$s
|
||||
* - Node name: %4$s
|
||||
* - Node path: %5$s
|
||||
* ',
|
||||
*
|
||||
* // A callback can be used to get access to the message instance
|
||||
* OtherError::class => function (NodeMessage $message): string {
|
||||
* if ((string)$message->type() === 'string|int') {
|
||||
* // …
|
||||
* }
|
||||
*
|
||||
* return 'Some message content';
|
||||
* },
|
||||
*
|
||||
* // For greedy operation, it is advised to use a lazy-callback
|
||||
* 'foo' => fn () => $this->translator->translate('foo.bar'),
|
||||
* ]))
|
||||
* ->defaultsTo('some default message')
|
||||
* // …or…
|
||||
* ->defaultsTo(fn () => $this->translator->translate('default_message'));
|
||||
*
|
||||
* $content = $formatter->format($message);
|
||||
* ```
|
||||
*/
|
||||
final class MessageMapFormatter implements MessageFormatter
|
||||
{
|
||||
/** @var array<string|callable(NodeMessage): string> */
|
||||
private array $map;
|
||||
|
||||
/** @var null|string|callable(NodeMessage): string */
|
||||
private $default;
|
||||
|
||||
/**
|
||||
* @param array<string|callable(NodeMessage): string> $map
|
||||
*/
|
||||
public function __construct(array $map)
|
||||
{
|
||||
$this->map = $map;
|
||||
}
|
||||
|
||||
public function format(NodeMessage $message): string
|
||||
{
|
||||
$target = $this->target($message);
|
||||
$text = is_callable($target) ? $target($message) : $target;
|
||||
|
||||
return $message->format($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|callable(NodeMessage): string $default
|
||||
*/
|
||||
public function defaultsTo($default): self
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->default = $default;
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|callable(NodeMessage): string
|
||||
*/
|
||||
private function target(NodeMessage $message)
|
||||
{
|
||||
return $this->map[$message->code()]
|
||||
?? $this->map[(string)$message]
|
||||
?? $this->map[get_class($message->originalMessage())]
|
||||
?? $this->default
|
||||
?? (string)$message;
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Unit\Mapper\Tree\Message\Formatter;
|
||||
|
||||
use CuyZ\Valinor\Mapper\Tree\Message\Formatter\MessageMapFormatter;
|
||||
use CuyZ\Valinor\Mapper\Tree\Message\NodeMessage;
|
||||
use CuyZ\Valinor\Tests\Fake\Mapper\FakeNode;
|
||||
use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeMessage;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class MessageMapFormatterTest extends TestCase
|
||||
{
|
||||
public function test_format_finds_code_returns_formatted_content(): void
|
||||
{
|
||||
$message = new NodeMessage(FakeNode::any(), new FakeMessage());
|
||||
$formatter = new MessageMapFormatter([
|
||||
'some_code' => 'foo',
|
||||
]);
|
||||
|
||||
self::assertSame('foo', $formatter->format($message));
|
||||
}
|
||||
|
||||
public function test_format_finds_code_returns_formatted_content_from_callback(): void
|
||||
{
|
||||
$message = new NodeMessage(FakeNode::any(), new FakeMessage());
|
||||
$formatter = new MessageMapFormatter([
|
||||
'some_code' => fn (NodeMessage $message) => "foo $message",
|
||||
]);
|
||||
|
||||
self::assertSame('foo some message', $formatter->format($message));
|
||||
}
|
||||
|
||||
public function test_format_finds_content_returns_formatted_content(): void
|
||||
{
|
||||
$message = new NodeMessage(FakeNode::any(), new FakeMessage());
|
||||
$formatter = new MessageMapFormatter([
|
||||
'some message' => 'foo',
|
||||
]);
|
||||
|
||||
self::assertSame('foo', $formatter->format($message));
|
||||
}
|
||||
|
||||
public function test_format_finds_class_name_returns_formatted_content(): void
|
||||
{
|
||||
$message = new NodeMessage(FakeNode::any(), new FakeMessage());
|
||||
$formatter = new MessageMapFormatter([
|
||||
FakeMessage::class => 'foo',
|
||||
]);
|
||||
|
||||
self::assertSame('foo', $formatter->format($message));
|
||||
}
|
||||
|
||||
public function test_format_does_not_find_any_returns_default(): void
|
||||
{
|
||||
$message = new NodeMessage(FakeNode::any(), new FakeMessage());
|
||||
$formatter = (new MessageMapFormatter([]))->defaultsTo('foo');
|
||||
|
||||
self::assertSame('foo', $formatter->format($message));
|
||||
}
|
||||
|
||||
public function test_format_does_not_find_any_returns_message_content(): void
|
||||
{
|
||||
$message = new NodeMessage(FakeNode::any(), new FakeMessage());
|
||||
$formatter = new MessageMapFormatter([]);
|
||||
|
||||
self::assertSame('some message', $formatter->format($message));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user