Laravel Beauty: Tìm hiểu về Service Provider
https://viblo.asia/p/laravel-beauty-tim-hieu-ve-service-provider-zb7vDVJnMjKd
Last updated
https://viblo.asia/p/laravel-beauty-tim-hieu-ve-service-provider-zb7vDVJnMjKd
Last updated
Trong bài viết lần trước, chúng ta đã cùng tìm hiểu về thành phần trung tâm của Laravel Framework, đó là Service Container.
Nhìn chung thì Service Container trong Laravel là nơi quản lý class dependency và thực hiện dependency injection.
Bạn chỉ cần bind
một Class, Interface hay một từ khoá bất kỳ với Service Container là có thể resolve
chúng ra ở bất cứ nơi đâu.
Bản thân chính Laravel cũng thực hiện bind
rất nhiều thứ vào trong Service Container để chúng ta có thể gọi ra sử dụng khi cần thiết, bằng nhiều cách khác nhau.
Và trong bài viết này, ta sẽ tìm hiểu xem Laravel thực hiện việc bind
đó khi nào, ở đâu và bằng cách nào nhé. Từ đó ta sẽ có thể tự hiểu và tìm ra cách để thay thế một service có sẵn của Laravel bằng một service do chính mình implement một cách dễ dàng.
config/app.php
Chắc hẳn bạn đã động chạm nhiều đến file config này rồi khi làm việc với Laravel. Không chỉ là nơi setup những thông số của project như debug mod, url, timezone, local ..., ở file config/app.php
, bạn sẽ còn thấy một mục quan trọng khác, đó là providers, với những dòng comment ở phía dưới là
Vâng, đây chính là nơi bắt đầu của việc registering service container bindings. Tuy nhiên thay vì luôn phải sử dụng app()->bind()
, app()->instance()
... như đã đề cập ở bài viết trước, thì ở phần khai báo bindings trong file config/app.php
này ta lại truyền vào tên các class. Mà các class đó thường được đặt tên dưới dạng là SomethingServiceProvider, chẳn hạn như Illuminate\Session\SessionServiceProvider
, Illuminate\View\ViewServiceProvider
, Illuminate\Routing\ControllerServiceProvider
...
Ngoài ra, chắc bạn cũng đã từng để ý là thường thì ở các package Laravel mà các bạn vẫn hay dùng, như Laravel Debugbar, Laravel Markdown, Laravel IDE Helper ..., khi cài đặt, các package đó đều yêu cầu các bạn add class ServiceProvider
của chúng vào phần providers trong config/app.php
. Hay nói các khác, các third-party package cho Laravel cũng thường xuyên cung cấp Service Provider để quá trình cài đặt và sử dụng được dễ dàng.
Vậy rốt cuộc Service Provider là gì?
Như đã nói ở trên, trong project được tạo ra, ngoài file bootstrap/app.php
có khai báo binding Kernel và Exception ra thì bạn sẽ không thấy nơi nào khai báo binding một cách trực tiếp nữa. Thay vào đó chỉ có nơi khai báo class sẽ thực hiện việc binding, những class đó chính là Service Provider.
Các Service Provider đều được extend từ một abstract class mà Laravel cung cấp, đó là Illuminate\Support\ServiceProvider
. Nếu bạn vào tìm hiểu code của class này thì sẽ thấy nó bao gồm một abstract function là register
, điều đó có nghĩa là Service Provider của bạn viết sẽ bắt buộc phải khai báo method register
này. Và đây chính là nơi bạn thực hiện việc binding vào Service Container. Chẳng hạn hãy cùng xem một Service Provider đơn giản là HashServiceProvider
xem nó có gì nhé.
Bình thường, vào file config/app.php
bạn sẽ thấy providers đó được khai báo dưới dạng Illuminate\Hashing\HashServiceProvider::class
, dựa trên cái tên đó ta có thể tìm ra file provider tại /vendor/laravel/framework/src/Illuminate/Hashing/HashServiceProvider.php
:
Như vậy ta có thể thấy trong HashServiceProvider
ta đã thực hiện việc binding từ khoá hash
, có thể coi đây là tên service, với một instance của class BcryptHasher
qua method singleton
. Như vậy thì:
get_class(app()->make('hash'))
sẽ trả ra kết quả là Illuminate\Hashing\BcryptHasher
;
app()->make('hash') === app()->make('hash')
sẽ trả ra kết quả true
, do các lần gọi app()->make('hash')
ta đều sẽ nhận về cùng một object.
Bên cạnh đó, các bạn có thể thấy trong class HashServiceProvider
ở trên, ta còn có một biến protected là $defer
. Việc khai báo $defer = true
sẽ giúp cho quá trình binding sẽ không được thực hiện cho đến khi service được gọi. Hay nói cách khác, theo ví dụ ở trên thì với mỗi request, một instance của BcryptHasher
sẽ không phải được tạo ra ngay từ đầu, mà chỉ khi nào ta gọi app()->make('hash')
lần đầu thì việc binding mới diễn ra (và cũng sẽ chỉ diễn ra một lần, từ những lần gọi sau thì đã có trong Service Container rồi nên chỉ việc lấy ra mà thôi). Để thực hiện được việc này thì bên cạnh hàm register
, trong trường hợp $defer = true
thì ta cần phải có thêm một hàm nữa báo trước cho Laravel là cái provider này sẽ trả về service gì, đó là hàm provides
. Cụ thể thì hàm provides
của HashServiceProvider
trông như sau:
Như vậy thì ta có thể tóm tắt lại quá trình Laravel thực hiện xử lý các Service Provider như sau:
Check xem biến $defer
là true
hay false
. Nếu là false
thì thực hiện ngay việc binding thông qua việc chạy hàm register
.
Nếu $defer
là true
thì không chạy hàm register
, thay vào đó sẽ lưu lại thông tin về việc service provider đó sẽ trả về service gì thông qua kết quả của hàm provides
. Như vậy việc binding đã không diễn ra.
Khi ta yêu cầu resolve một service nào đó từ Service Container, Laravel sẽ check xem đó có phải là deferred service hay không, nếu đúng thì Laravel sẽ thực hiện việc chạy hàm register
của provider tương ứng. Lúc này việc binding mới được diễn ra. Và đương nhiên sau khi binding xong thì service đó sẽ được remove ra khỏi danh sách các deferred service. Cuối cùng, việc resolve sẽ được thực hiện.
Việc load các service một cách defer như vậy sẽ giúp cái thiện performance cho application của bạn, khi mà những service chỉ được load khi mà nó là cần thiết. Đương nhiên nếu service của bạn là chắc chắn cần thiết, chắc chắn phải sử dụng thì bạn không cần khai báo $defer = true
làm gì, nó sẽ chỉ làm tăng thêm xử lý khi gọi ra lần đầu mà thôi.
Ngoài ra, còn một chú ý khác nữa là bạn chỉ nên thực hiện khai báo binding trong hàm register
, chứ không nên khai báo những event listener, routes hay các xử lý phức tạp khác. Bởi nên biết rằng Laravel sẽ duyệt qua một loạt các provider để thực hiện việc binding, thế nên có khả năng bạn sẽ gặp phải tình huống là gọi ra một service khi mà provider của nó còn chưa được xử lý.
Còn nếu muốn viết những xử lý có yêu cầu những service khác, thì bạn nên viết nó trong hàm boot
, bởi hàm này sẽ chỉ được gọi khi mà toàn bộ các service provider đã được duyệt qua, và vì thế, đương nhiên là bạn sẽ có thể access được vào toàn bộ các service.
Service Provider chính là chìa khoá cho quá trình bootstrapping một Laravel Application. Hãy tưởng tượng application của bạn như một cái Container, và khi khởi chạy, nó sẽ tiến hành đưa các service cần thiết vào trong container đó, rồi những gì bạn cần làm là lấy ra những service cần thiết vào thời điểm cần thiết từ container để xử lý một request gửi đến.
Việc được xây dựng dựa trên sự kết hợp của các service như vậy đã giúp Laravel có sự mềm dẻo và linh hoạt cần thiết. Trong quá trình làm việc với Laravel, khi đã nắm rõ khái niệm, vai trò và cách thức sử dụng Service Provider thì bạn hoàn toàn có thể ứng dụng nó vào các trường hợp chẳng hạn như:
Bootstrapping application: Khi nhìn vào phần khai báo providers trong config/app.php
, bạn sẽ thấy sẽ có phần cho Application Service Providers, và trong đó thì có App\Providers\AppServiceProvider
. Laravel đã tạo sẵn cho bạn một nơi lý tưởng để bạn có thể thêm vào những binding hay những bootstrapping config của mình. Chặng hạn như bạn muốn viết thêm những validation của riêng mình thông qua việc sử dụng Validator::extend
, hay muốn thêm vào những HTML Macro, hay bất cứ xử lý logic nào khác bạn cần chạy trước khi tiến hành xử lý request ..., thì hàm boot
trong AppServiceProvider
là nơi bạn nên thực hiện những việc đó việc đó.
Thay thế service mặc định của Laravel: Các service của Laravel giao tiếp với nhau thông qua Service Container. Ví dụ như một xử lý của bạn cần service Authentication thì bạn có thể lấy ra bằng cách dùng app()->make('auth')
rồi tiếp tục tiến hành các xử lý tiếp theo. Như vậy xử lý của bạn cần service auth để chạy, chứ nó không hoàn toàn phụ thuộc vào một class Auth cố định nào cả. Điều này cũng có nghĩa là bạn hoàn toàn có thể tự viết một service của mình, một provider của riêng mình rồi thay khai báo provider của Laravel trong config/app.php
bằng khai báo provider của bạn, miễn là class service của bạn cũng có các method cần thiết giống như class service mặc định của Laravel (thường là sẽ phải implement một interface nào đó). Ví dụ như một package Laravel mà mình vẫn hay sử dụng là MultiAuth (https://github.com/Kbwebs/MultiAuth) (package cho Laravel 5.1, còn lên 5.2 thì bản thân Laravel đã hỗ trợ Multi Auth rồi). Bằng việc sử dụng package trên, mình có thể thực hiện authenticate với nhiều bảng khác nhau, như users
và admins
chẳng hạn. Những gì mình cần làm để sử dụng nó chỉ đơn giản là thay đổi service provider mặc định, từ Illuminate\Auth\AuthServiceProvider
thành Kbwebs\MultiAuth\AuthServiceProvider
, hay Illuminate\Auth\Passwords\PasswordResetServiceProvider
thành Kbwebs\MultiAuth\PasswordResets\PasswordResetServiceProvider
... và một vài config nhỏ khác.
Như vậy là mình đã giới thiệu qua về Service Provider, vai trò, ý nghĩa, cũng như cách nó đang được sử dụng trong một project Laravel ra sao. Dĩ nhiên, cũng chỉ dừng ở mức cơ bản mà thôi. Bạn có thể tự vào đọc code của framework để hiểu rõ hơn về cách thức mà Laravel thực hiện cách load và chạy Service Provider như thế nào.
Hay như phần khai báo providers ở file config/app.php
, không phải tất cả các service đều được khai báo ở đó đâu. Chẳng hạn như bạn gõ app()->make('request')
, app()->make('events')
, hay app()->make('url')
thì đều sẽ nhận được object tương ứng, tuy nhiên ta lại không hề thấy những RequestServiceProvider
, EventsServiceProvider
hay UrlServiceProvider
nào trong phần khai báo ở config/app.php
cả. Thậm chí lục tung cả framework Laravel lên thì bạn cũng không tìm đâu ra cái gọi là RequestServiceProvider
hay UrlServiceProvider
đâu. Vậy những service đó được binding ở đâu, vào lúc nào, hay nếu có Service Provider tương ứng thì provider đó được gọi như thế nào? Hãy thử tự cố gắng tìm câu trả lời xem, chắc chắn khi tìm hiểu xong, bạn sẽ thấy mình hiểu nhiều hơn về cách thức hoạt động của Laravel đấy.
Hẹn gặp lại các bạn trong những bài viết giới thiệu về những khái niệm như Contracts hay Facades sắp tới ^^!
PHPLaravelService ContainerService Provider
All rights reserved
MỤC LỤC
BÀI VIẾT THUỘC SERIES
Laravel: The Beauty1. 2. 3. 4. 5.
CÁC TỔ CHỨC ĐƯỢC ĐỀ XUẤT
Laravel: Tìm hiểu về Service ContainerNguyen Ngoc Trung6 phút đọc 934 2 02Laravel Beauty: Recipes & Best PracticesTran Duc Thang17 phút đọc 18725 115 13137Laravel Beauty: Tìm hiểu về Service containerTran Duc Thang15 phút đọc 27711 85 41161Laravel Beauty: Tìm hiểu về FacadeTran Duc Thang12 phút đọc 18799 50 2177Laravel Service Container in Depth & Tips to Customize Your ApplicationVinh Nguyen45 phút đọc 2938 34 329Học Laravel: Service ContainerNgo Duy Son10 phút đọc 2760 15 111Tìm hiểu Service Provider trong LaravelHoàng Nguyễn5 phút đọc 16507 12 032Laravel và những điều cần biết - phần 3Hoàng Hữu Hợi17 phút đọc 2831 9 03Một vòng laravel (Part 4)Nguyễn Phúc Lương9 phút đọc 773 3 07Laravel: Tìm hiểu về Service ContainerNguyen Ngoc Trung6 phút đọc 934 2 02Laravel Beauty: Recipes & Best PracticesTran Duc Thang17 phút đọc 18725 115 13137Laravel Beauty: Tìm hiểu về Service containerTran Duc Thang15 phút đọc 27711 85 41161Laravel Beauty: Tìm hiểu về FacadeTran Duc Thang12 phút đọc 18799 50 2177Laravel Service Container in Depth & Tips to Customize Your ApplicationVinh Nguyen45 phút đọc 2938 34 329
Một số Design Principles trong lập trình mà bạn nên biếtTran Duc Thang22 phút đọc 5691 42 267[Become A SuperUser] Filesystem Hierarchy StandardTran Duc Thang19 phút đọc 1090 14 244Bài toán các vị tướng Byzantine và ứng dụng trong BlockchainTran Duc Thang19 phút đọc 7096 50 1584[Become a SuperUser] Debian vs Redhat: Package Management SystemTran Duc Thang14 phút đọc 2065 19 1135[Become a SuperUser] Manage file permissions and ownershipTran Duc Thang30 phút đọc 401 4 015[Become A SuperUser] Find system filesTran Duc Thang9 phút đọc 232 3 219[Review Sách] Cracking the Coding Interview - 189 Programming Questions & SolutionsTran Duc Thang16 phút đọc 1314 6 129Tìm hiểu về giải thuật Chia để Trị (Divide and Conquer)Tran Duc Thang18 phút đọc 3110 10 019Tìm hiểu về giải thuật Đệ QuyTran Duc Thang22 phút đọc 8558 6 018Cùng ôn lại các khái niệm về Cấu trúc dữ liệu, Giải thuật, Độ phức tạp thuật toán.Tran Duc Thang19 phút đọc 3221 34 158Simple Rules to make your code Cleaner (Part 2)Tran Duc Thang22 phút đọc 862 4 212Simple Rules to make your code Cleaner (Part 1)Tran Duc Thang15 phút đọc 1133 9 019Một số Design Principles trong lập trình mà bạn nên biếtTran Duc Thang22 phút đọc 5691 42 267[Become A SuperUser] Filesystem Hierarchy StandardTran Duc Thang19 phút đọc 1090 14 244Bài toán các vị tướng Byzantine và ứng dụng trong BlockchainTran Duc Thang19 phút đọc 7096 50 1584[Become a SuperUser] Debian vs Redhat: Package Management SystemTran Duc Thang14 phút đọc 2065 19 1135[Become a SuperUser] Manage file permissions and ownershipTran Duc Thang30 phút đọc 401 4 015[Become A SuperUser] Find system filesTran Duc Thang9 phút đọc 232 3 219[Review Sách] Cracking the Coding Interview - 189 Programming Questions & SolutionsTran Duc Thang16 phút đọc 1314 6 129Tìm hiểu về giải thuật Chia để Trị (Divide and Conquer)Tran Duc Thang18 phút đọc 3110 10 019
Cảm ơn anh. (thankyou) Bài viết rất hữu ích. Quả nhiên là ít người đi được tới bài này (hihi)
Nếu $defer là true thì không chạy hàm register. mình thấy $defer = true | false thì nó đều chạy hàm register à bạn
mình thấy defer = true | false thì nó đều chạy hàm register à bạn
Mà trong bài viết mình đề cập việc set $defer
là true
thì service sẽ được load khi nó lần đầu tiên được gọi đến, và lúc này hàm register
sẽ được chạy mà, chứ ý mình không phải là set $defer = true
thì hàm register
sẽ mãi mãi không được chạy.
Bạn có thể tìm hiểu thêm về Deferred Providers trên Document của Laravel, trên đó có nói rõ:
If your provider is only registering bindings in the service container, you may choose to defer its registration until one of the registered bindings is actually needed. Deferring the loading of such a provider will improve the performance of your application, since it is not loaded from the filesystem on every request. Laravel compiles and stores a list of all of the services supplied by deferred service providers, along with the name of its service provider class. Then, only when you attempt to resolve one of these services does Laravel load the service provider. To defer the loading of a provider, set the
defer
property totrue
and define aprovides
method. Theprovides
method should return the service container bindings registered by the provider
Ngoài ra, bạn cũng có thể xem thêm về code của Framework để kiểm tra cách nó load deferred providers
, cụ thể là ở file laravel/framework/src/Illuminate/Foundation/Application.php
.
Anh cho em hỏi tí về phần "defer = true | false" Nếu là true thì có nghĩa là hàm register lúc này chưa được gọi => class chưa được bind Nếu là false thì có nghĩa là hàm register lúc này được gọi => class được bind
Ở cả 2 trường hợp em gọi app()->make('hash') thì "defer = true | false" đều trả về class ( Theo em hiểu từ bài viết của anh thì nếu defer = false thì make nó sẽ get được luôn instance còn defer = true thì vào chay register để bind và get instance)
Kiến thức còn hạn hẹp nên mong được anh giúp đỡ !
Theo em hiểu từ bài viết của anh thì nếu defer = false thì make nó sẽ get được luôn instance còn defer = true thì vào chay register để bind và get instance
Không phải em ạ, như anh đã đề cập ở trong bài viết thì
Việc khai báo
$defer = true
sẽ giúp cho quá trình binding sẽ không được thực hiện cho đến khi service được gọi
cảm ơn anh vì câu trả lời !!
good job
Các Service Provider đều được extend từ một abstract class mà Laravel cung cấp, đó là Illuminate\Support\ServiceProvider. Nếu bạn vào tìm hiểu code của class này thì sẽ thấy nó bao gồm một abstract function là register()
như dùng biến $bindings
, biến $singletons
.
Em tham khảo mấy cách viết Service Provider này https://laravel.com/docs/5.7/providers#writing-service-providers , nhiều cái không cần đến hàm register
mà
Quả là kiến thức của em còn quá nhỏ bé, em luôn luôn dùng register để binding. Cảm ơn anh vì đã bỏ thời gian ra trả lời những thắc mắc vặt vãnh của em ạ (bow)
@thangtd90 Hợp lí anh ạ =)) Em cảm ơn anh
Em thấy a hau dùng thuật ngữ bootstrap, vậy bootstrap ở bài viết này là gì vậy ạ?
thanks anh ạ
Tự viết các service của mình: Nếu application của bạn có quá nhiều thứ cần bootstrap mà bạn cảm thấy viết hết vào hàm boot
trong AppServiceProvider
là không tốt thì bạn nên nghĩ đến viết nhiều Service Provider khác nhau. Ngoài ra khi mà application của bạn được mở rộng, và có cấu trúc lớn thì một việc quan trọng được đặt ra là cần phải tổ chức các business logic như thế nào cho hợp lý. Và đương nhiên việc viết thành những service rồi khai báo qua Service Provider là một trong những cách mà bạn nên thử. Chẳng hạn như application của bạn có nhiều chỗ đòi hỏi làm việc với image, và đi cùng với đó sẽ là những hàm, những xử lý với image. Có thể khi đó bạn sẽ cần đến một Image
class là nơi chứa các logic function, và một ImageServiceProvider
, với những xử lý binding và bootstrapping ở trong đó
Làm việc với các Laravel package: Như đã nói ở phần đầu thì các package Laravel thường có các Service Provider của riêng nó. Giờ thì bạn đã hiểu nó để làm gì, và tại sao cần phải khai báo trong file config/app.php
rồi chứ. Và ngược lại, khi bạn muốn viết một package Laravel cho những người khác sử dụng thì cũng nên nhớ viết một Service Provider cho nó nhé. Khi đó service của bạn sẽ có thể được dùng dễ dàng hơn, và theo đúng phong cách Laravel hơn.
PHP Education Team 6 40 364 25.9KAvengers Group 52 35 172 231.7KSun* R&D Product Development 2 39 68 2.1K
Đăng nhập để bình luậnNgo Duy Son @ngo.duy.sonthg 7 25, 2016 2:30 SA
0 |Trả lờiChia sẻLưu Xuân Trường @xuantruong1234thg 10 13, 2017 7:09 SA
0 |Trả lờiChia sẻTran Duc Thang @thangtd90thg 10 13, 2017 10:26 SA
Tại sao bạn lại thấy như vậy Cụ thể bạn đã làm những gì để ra kết quả đó?
0 |Trả lờiChia sẻNguyễn Văn Cường @cuong077thg 5 10, 2018 1:21 SA
0 |Trả lờiChia sẻTran Duc Thang @thangtd90thg 5 10, 2018 8:43 SA
tức nếu em để $defer = false
, thì ngay cả trước khi em resolve ra app()->make('hash')
thì instance của class HashServiceProvider
đã được tạo ra rồi (trong quá trình bootstrap Laravel Framework), còn nếu em để $defer = true
, thì chỉ khi nào em gọi app()->make('hash')
lần đầu, Laravel mới thực hiện tìm kiếm xem làm thể nào để resolve ra instance tương ứng, hay nói cách khác, chỉ khi nào em gọi app()->make('hash')
lần đầu, thì việc binding một instance của class HashServiceProvider
với từ khoá hash
mới được thực hiện
Thế nên dù em có gọi app()->make('hash')
trong trường hợp $defer
bằng true
hay false
đi chăng nữa thì kết quả nó vẫn sẽ giống nhau thôi, điểm khác nhau là kết quả của hàm app()->make('hash')
đó được tính toán sẵn và lưu lại từ lúc khởi chạy framework, hay lúc gọi đến nó mới tính toán
+2 |Trả lờiChia sẻNguyễn Văn Cường @cuong077thg 5 10, 2018 6:02 CH
+1 |Trả lờiChia sẻDat Dao @datagitthg 1 23, 2019 11:51 SA
0 |Trả lờiChia sẻQuiet @simple1805thg 1 30, 2019 2:58 CH
@thangtd90 Em mò vào Illuminate\Support\ServiceProvider
mà không thấy abstract protected function
nào tên là register()
. Không biết là Laravel nó đã định nghĩa hàm register()
này kiểu gì và ở đâu anh nhỉ
+1 |Trả lờiChia sẻ Xem thêm (7)Tran Duc Thang @thangtd90thg 1 30, 2019 3:57 CH
Còn nhiều cách để binding mà em
Hoặc là em chỉ cần chạy hàm boot
mà không cần register
0 |Trả lờiChia sẻQuiet @simple1805thg 1 30, 2019 4:00 CH
0 |Trả lờiChia sẻQuiet @simple1805thg 2 18, 2020 11:03 SA
Nay em vừa mò lại package anh làm https://github.com/wataridori/chatwork-sdk. Thật bất ngờ khi anh k make bất cứ 1 Service Provider nào trong này luôn Có tư tưởng hay lí do gì mà anh lai k làm như vậy không ạ
0 |Trả lờiChia sẻTran Duc Thang @thangtd90thg 2 26, 2020 4:44 CH
À cái này đơn giản là package của anh là dành cho các project PHP nói chung, và bất kỳ framework nào cũng có thể dùng. Do đó nó không require bất kỳ package nào của Laravel, cũng như có các Service Provider dành cho một project Laravel rồi em
Bao giờ tên repo phải là laravel-chatwork-sdk
thì em mới thấy có Service Provider trong đó được
+1 |Trả lờiChia sẻQuiet @simple1805thg 2 29, 2020 8:09 SA
0 |Trả lờiChia sẻTrần Đức @GSTviblothg 6 2, 2020 11:49 SA
0 |Trả lờiChia sẻQuiet @simple1805thg 6 2, 2020 2:22 CH
Đây bạn ạ
+1 |Trả lờiChia sẻTrần Đức @GSTviblothg 6 2, 2020 2:53 CH
0 |Trả lờiChia sẻVan Sau Bui @Mrseventhg 7 2, 2021 5:41 CH
Bài viết hay đấy! Nhưng nhiều người vẫn chưa hiểu không biết do tác giả không chia sẻ hết ý hay chưa thật sự hiểu sâu