如何为JmsSerializer配置MySQL与ElasticSearch差异化序列化逻辑?
Got it, let's break down how to handle this scenario where we need different serialization/deserialization rules for MySQL and ElasticSearch using JMS Serializer. The core idea is to create a custom handler that checks a context flag to decide whether to apply the empty string ↔ "def" conversion for ElasticSearch, while leaving MySQL data untouched.
Step 1: Create a Custom String Handler
We'll build a handler that overrides the default string serialization/deserialization logic, but only applies our custom rules when the target storage is ElasticSearch.
use JMS\Serializer\Handler\BaseHandler; use JMS\Serializer\Context; use JMS\Serializer\VisitorInterface; class TargetSpecificStringHandler extends BaseHandler { private const TARGET_ELASTICSEARCH = 'elasticsearch'; private const DEFAULT_VALUE = 'def'; public function serializeString(VisitorInterface $visitor, string $value, array $type, Context $context) { // Check if we're targeting ElasticSearch if ($context->get('target_storage') === self::TARGET_ELASTICSEARCH) { // Replace empty string with "def" $value = $value === '' ? self::DEFAULT_VALUE : $value; } // Delegate to the default string serialization return $visitor->visitString($value, $type, $context); } public function deserializeString(VisitorInterface $visitor, string $value, array $type, Context $context) { // Check if we're targeting ElasticSearch if ($context->get('target_storage') === self::TARGET_ELASTICSEARCH) { // Replace "def" back to empty string $value = $value === self::DEFAULT_VALUE ? '' : $value; } // Delegate to the default string deserialization return $visitor->visitString($value, $type, $context); } }
Step 2: Register the Handler with JMS Serializer
Next, we need to register our custom handler with the JMS Serializer instance. Here's how to do it in a typical setup:
use JMS\Serializer\SerializerBuilder; // Build the serializer and register our handler $serializer = SerializerBuilder::create() ->addDefaultHandlers() ->configureHandlers(function ($handlerRegistry) { $handlerRegistry->registerHandler('serialization', 'string', 'json', [new TargetSpecificStringHandler(), 'serializeString']); $handlerRegistry->registerHandler('deserialization', 'string', 'json', [new TargetSpecificStringHandler(), 'deserializeString']); // If you use other formats (like xml), add those too $handlerRegistry->registerHandler('serialization', 'string', 'xml', [new TargetSpecificStringHandler(), 'serializeString']); $handlerRegistry->registerHandler('deserialization', 'string', 'xml', [new TargetSpecificStringHandler(), 'deserializeString']); }) ->build();
Step 3: Use the Serializer with Context Parameters
Now, when you need to serialize/deserialize for MySQL or ElasticSearch, just pass the target_storage parameter in the context:
Example Entity
First, let's define a sample entity to test with:
use JMS\Serializer\Annotation as Serializer; class Product { #[Serializer\Type('string')] private string $name = ''; #[Serializer\Type('string')] private string $description = ''; // Constructor, getters, setters... public function __construct(string $name = '', string $description = '') { $this->name = $name; $this->description = $description; } public function getName(): string { return $this->name; } public function getDescription(): string { return $this->description; } }
Serialization for MySQL
Empty strings stay as-is:
$product = new Product('', 'Some description'); // Serialize for MySQL $mysqlJson = $serializer->serialize($product, 'json', [ 'target_storage' => 'mysql' ]); // Output: {"name":"","description":"Some description"} echo $mysqlJson;
Serialization for ElasticSearch
Empty strings are replaced with "def":
// Serialize for ElasticSearch $esJson = $serializer->serialize($product, 'json', [ 'target_storage' => 'elasticsearch' ]); // Output: {"name":"def","description":"Some description"} echo $esJson;
Deserialization for ElasticSearch
"def" gets converted back to empty string:
// Deserialize from ElasticSearch JSON $deserializedProduct = $serializer->deserialize($esJson, Product::class, 'json', [ 'target_storage' => 'elasticsearch' ]); // Will output empty string echo $deserializedProduct->getName();
Deserialization for MySQL
No conversion happens:
// Deserialize from MySQL JSON $deserializedMysqlProduct = $serializer->deserialize($mysqlJson, Product::class, 'json', [ 'target_storage' => 'mysql' ]); // Will output empty string (matches original) echo $deserializedMysqlProduct->getName();
Alternative: Using Serialization Groups (Optional)
If you prefer, you could also use serialization groups instead of context parameters. For example, define groups like mysql and elasticsearch, then modify the handler to check the active groups instead of the context parameter. But the context parameter approach is more flexible for dynamic target switching.
内容的提问来源于stack exchange,提问作者Serhii Smirnov

