Trong bài viết này mình sẽ thử implement Symfony Service Decoration
để giải quyết bài toán làm mình chán nản cả ngày hôm nay.
Bài toán:
Mô tả:
Giả sử chúng ta có Post
và PostRevision
entity. Trong hệ thống có rất nhiều nơi tác động để update Post
:
- Edit review của admin
- Edit của user
- Edit bằng api
- …
Yêu cầu là mỗi khi Post
updated thì sẽ tạo mới PostRevision
.
Giải quyết:
Để giải quyết vấn đề nêu ra thì phương án hiển nhiên là tạo ra 1 Listener
lắng nghe sự kiện Post updated
để tạo mới revision.
1 | // AppBundle/Event/PostUpdatedEvent.php |
1 | // AppBundle/EventSubscriber/PostRevisionSubscriber.php |
Vấn đề phát sinh:
Listener
đã sẵn sàng, nhưng công cuộc dispatch event
thì làm như thế nào? Có rất nhiều nơi trong hệ thống cần ném event ra, vậy ta sẽ:
1. Tay to:
Chỗ nào cần dispatch
thì cứ táng vào thôi :trollface: Với cách làm này thì code sẽ bị “đúp” khá nhiều và dễ bị bỏ sót nếu không cẩn thận khi thêm endpoint mới :facepalm:
1 | $post = updateContent(); |
2. Xài Service trung gian:
Tống phần flush db
và dispatch event
vào 1 service
trung gian. Đây là phương án mình cho là hợp lý nhất.
1 | // AppBundle/Manager/PostManager.php |
3. Service Decoration
Okay, phương án phía trên được xem là perfect để giải quyết bài toán (trong tầm hiểu biết của mình). Tuy nhiên vì tiêu đề bài viết nên ta sẽ phịa thêm cách này nữa để có cái cớ implement Service Decoration
Symfony Service Decoration
Trước tiên hãy xem qua về pattern này để có cái nhìn tổng quan: https://en.wikipedia.org/wiki/Decorator_pattern
Hiểu đơn giản, ta đang cố gắng viết lớp wrapper để thêm hành vi (behavior) cho class sẵn có
.
Ở đây, ta sẽ cố decorate
Repository
và thêm hành vi cho nó.
Ta có repository của Post
entity:
1 | // AppBundle/Repository/PostRepository.php |
Tạo class DecoratingPostRepository
với method save
:
- Save post
- Dispatch event
Lưu ý: Trong pattern này, PostRepository
không hề biết đến sự có mặt của DecoratingPostRepository
1 | // AppBundle/Repository/DecoratingPostRepository.php |
Như bạn thấy ở trên, constructor
của DecoratingPostRepository
gồm PostRepository
và EventDispatcherInterface
. Vậy làm sao để inject được 2 class này? Việc này rất đơn giản chỉ với vài dòng config service của Symfony:
1 | # services.yml |
Done, từ bây giờ, khi bạn gọi service app.repository.post
thì app.repository.post_decorating
sẽ có mặt để phục vụ. Và bây giờ vấn đề phía trên của chúng ta sẽ chỉ là:
1 | $post = updateContent(); |
Vài links để các bạn tìm hiểu thêm về vấn đề này:
http://symfony.com/blog/new-in-symfony-2-8-dependencyinjection-improvements
https://symfony.com/doc/current/service_container/alias_private.html
https://stovepipe.systems/post/service-decoration-in-practice
https://symfony.com/doc/2.7/service_container/service_decoration.html
http://www.marcelsud.com/service-decoration-with-symfony-2-3/