在消息队列中使用Symfony的序列化

PHP中,如果需要把内存中的变量进行保存,以便后续继续使用,那么就要把数据序列化。

PHP提供了 serialize()  和 unserialize()  这两个原生方法来完成。但是这样有个限制,就是要求写入和读取的时候使用同样的方法。就是说只能使用PHP原生的反序列化函数来读取PHP原生序列化的数据。 有时候我们写入是PHP,读取的时候可能是别语言,所以需要一种更加通用的格式。

比如,在项目的写入队列的过程中,我们打算用Symfony提供的序列化器代替PHP原生的。我们选择序列化的格式为JSON。但是出现了问题,发现某些属性的数据丢失了。原来需要对序列化的对象进行处理。

要让消息能被JSON序列化需要确认以下

  • 消息对象需要实现一下特定接口 (非必要)
  • 消息对象能被正确encode和decode
  • 消息对象的构造函数必须为public,且只使用标量值。如果必须要使用类,需要确保这些类实现了stringable接口。

消息需要实现特定接口

SyncCommandInterface::class,
AsyncCommandInterface::class,
EventInterface::class,
QueryInterface::class,

具体措施

消息的构造函数必须为public,且使用仅使用标量作为参数

为什么消息对象的构造函数必须为public,为啥必须用标量参数。

我猜想,因为当消息被反序列化的时候,系统只会用最简单的new方法来重新创建消息实例。

  • public类的属性

使用readonly修饰属性防止意外修改

使用标量值

对象必须实现stringable

减少复杂性。比如不使用实体对象entity,而使用id. 这点在Symfony的文档内也有介绍。

参考

在Symfony中如何获取所有的message handler

在测试的时候,需要获取symfony的里所有的的message handler,该如何做到呢?

通过 messenger.message_hanlder 就可以。

比如你的测试需要测试所有的 message handler

1 在容器中注册一个provider ,比如 MessengerHanlderProvider

  App\Tests\Functional\StaticCodeAnalysis\MessengerHandlerProvider:
    public: true
    arguments:
      $handlers: !tagged_iterator messenger.message_handler
2 然后实现该provider类 class App\Tests\Functional\StaticCodeAnalysis\MessengerHandlerProvider ,它只有一个作用,就是接受一个返回所有handler迭代器,它的构造函数的为 !tagged_iterator messenger.message_handler
  public function __construct(iterable $handlers)
    {
        $this->handlers = iterator_to_array($handlers);
    }

意思就是返回给我所有的tag标记部位MessageHandler的类。因为我们在用Messenger定义消息的hanlder的时候,都按照教程,给每个定义为hanlder的类加上这个标签。比如:

#[AsMessageHandler]
readonly class FinalizeCancellationsHandler
{... }

#[AsMessageHandler]
class SyncCancellationsHandler
{...}

这样就可以轻松获取所有的messenger 的handler了。

3 在测试中需要用到返回所有消息handler的时候,只要问容器去要就可以了。比如

static::getContainer()->get(MessengerHandlerProvider::class);

相关视频教程

https://symfonycasts.com/screencast/messenger/message-class

symfony scoped (http) client 的配置

在改进版本中,我们使用symfony的scoped的http客户端代替原来的guzzle client。这样做的优点是,把一些基本的host配置可以从代码中抽出来,放入yaml配置中。这样在使用中,只要使用对应的名字,symfony框架就会把对应的已经配置好host的http client注入。

在测试中,我们使用的symfony提供的mock client而不是真正的scoped client,免得发出真正的http请求。

目前的问题是,除了http client,我们还有一个考虑到不同stroefront的client,称为“storefrontAwareClient”。它是在http client外面再包裹的一层。主要是解决,不同的国家(storefront)使用不同的base url和header。

在集成测试用,发现注入的不是mock client,而是真的scoped client。所以需要对stroefront进行针对测试环境的配置。保证在测试中,它注入的也是mock client

StorefrontAwareClient. 它替代了本来MarketplaceLoyaltyAccountService的部分功能

找到原因
1 测试中使用“personalized_coupons.client”未配置。因为配置被改名了。加入该key,并指出它是mock的httpClient后,问题解决

关于scope配置

在创建scoped client的配置里,有一个选项是scope。一直到我看了源码后才明白它的作用。当请求一个URL的时候,可以匹配scope里的正则表达,只有匹配的情况下,才会使用该配置。如果不匹配,请求也能发出,但是配置里的东西不会被发出。这样保证,一些秘密不会因为错误的配置被发送到其他网站。

一开始我还以为是某种强制的检查,如果不匹配就会抛除异常。

记录一个已经快要失传的doctrine的脚本,从数据表生成实体

php bin/console doctrine:mapping:import "App\Entity" annotation --path=src/Entity

另外在使用的时候,doctrine老说 Malformed database connection URL 错误。 是数据库配置问题。后来查了一下这里,说是DATABASE_URL中的特殊符号,比如我的密码中的#导致解析失败。更改密码,去除特殊符号就ok了。