Tự động tiêm phụ thuộc
Trong webman, chức năng tự động tiêm phụ thuộc là tùy chọn và mặc định bị tắt. Nếu bạn cần tiêm phụ thuộc tự động, chúng tôi khuyên bạn nên sử dụng php-di, dưới đây là cách sử dụng webman kết hợp với php-di
.
Cài đặt
composer require psr/container ^1.1.1 php-di/php-di ^6.3 doctrine/annotations ^1.14
Chỉnh sửa cấu hình config/container.php
, nội dung cuối cùng như sau:
$builder = new \DI\ContainerBuilder();
$builder->addDefinitions(config('dependence', []));
$builder->useAutowiring(true);
$builder->useAnnotations(true);
return $builder->build();
config/container.php
sẽ trả về một thể hiện container tuân theo tiêu chuẩnPSR-11
. Nếu bạn không muốn sử dụngphp-di
, bạn có thể tạo và trả về một thể hiện container khác tuân theo tiêu chuẩnPSR-11
ở đây.
Tiêm phụ thuộc qua hàm khởi tạo
Tạo app/service/Mailer.php
(nếu thư mục không tồn tại, vui lòng tự tạo) nội dung như sau:
<?php
namespace app\service;
class Mailer
{
public function mail($email, $content)
{
// Mã gửi email bị lược bỏ
}
}
Nội dung của app/controller/UserController.php
như sau:
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
class UserController
{
private $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function register(Request $request)
{
$this->mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
Trong trường hợp bình thường, cần mã sau đây để hoàn thành việc khởi tạo app\controller\UserController
:
$mailer = new Mailer;
$user = new UserController($mailer);
Khi sử dụng php-di
, nhà phát triển không cần phải tự tay khởi tạo Mailer
trong controller, webman sẽ tự động giúp bạn hoàn thành điều đó. Nếu trong quá trình khởi tạo Mailer
có các phụ thuộc đến các lớp khác, webman cũng sẽ tự động khởi tạo và tiêm chúng. Nhà phát triển không cần bất kỳ công việc khởi tạo nào.
Lưu ý
Chỉ có các thể hiện do framework hoặcphp-di
tạo mới có thể hoàn thành tiêm phụ thuộc tự động, các thể hiện được tạo bằng tay bằngnew
sẽ không thể hoàn thành tiêm phụ thuộc tự động. Nếu cần tiêm, bạn cần sử dụng giao diệnsupport\Container
thay thế câu lệnhnew
, ví dụ:
use app\service\UserService;
use app\service\LogService;
use support\Container;
// Các thể hiện được tạo bằng từ khóa new sẽ không thể tiêm phụ thuộc
$user_service = new UserService;
// Các thể hiện được tạo bằng từ khóa new sẽ không thể tiêm phụ thuộc
$log_service = new LogService($path, $name);
// Các thể hiện do Container tạo có thể tiêm phụ thuộc
$user_service = Container::get(UserService::class);
// Các thể hiện do Container tạo có thể tiêm phụ thuộc
$log_service = Container::make(LogService::class, [$path, $name]);
Tiêm phụ thuộc qua chú thích
Ngoài tiêm phụ thuộc qua hàm khởi tạo, chúng ta còn có thể sử dụng tiêm phụ thuộc qua chú thích. Tiếp tục với ví dụ ở trên, app\controller\UserController
thay đổi như sau:
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
use DI\Annotation\Inject;
class UserController
{
/**
* @Inject
* @var Mailer
*/
private $mailer;
public function register(Request $request)
{
$this->mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
Ví dụ này tiêm qua chú thích @Inject
và được khai báo loại đối tượng bằng chú thích @var
. Ví dụ này có hiệu ứng giống như tiêm qua hàm khởi tạo, nhưng mã ngắn gọn hơn.
Lưu ý
Webman không hỗ trợ tiêm tham số controller trước phiên bản 1.4.6, ví dụ mã sau đây không được hỗ trợ khi webman <= 1.4.6
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
class UserController
{
// Không hỗ trợ tiêm tham số controller trước phiên bản 1.4.6
public function register(Request $request, Mailer $mailer)
{
$mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
Tiêm phụ thuộc qua hàm khởi tạo tùy chỉnh
Đôi khi, tham số được truyền vào hàm khởi tạo có thể không phải là thể hiện của lớp, mà là chuỗi, số, mảng và các loại dữ liệu khác. Ví dụ, hàm khởi tạo của Mailer cần truyền ip và cổng của máy chủ smtp:
<?php
namespace app\service;
class Mailer
{
private $smtpHost;
private $smtpPort;
public function __construct($smtp_host, $smtp_port)
{
$this->smtpHost = $smtp_host;
$this->smtpPort = $smtp_port;
}
public function mail($email, $content)
{
// Mã gửi email bị lược bỏ
}
}
Trong trường hợp này, không thể sử dụng tiêm tự động qua hàm khởi tạo như đã giới thiệu ở trên, vì php-di
không thể xác định giá trị của $smtp_host
$smtp_port
. Lúc này, bạn có thể thử tiêm qua tùy chỉnh.
Thêm mã sau vào config/dependence.php
(nếu tệp không tồn tại, vui lòng tự tạo):
return [
// ... Bỏ qua các cấu hình khác ở đây
app\service\Mailer::class => new app\service\Mailer('192.168.1.11', 25);
];
Khi tiêm phụ thuộc cần lấy thể hiện app\service\Mailer
, nó sẽ tự động sử dụng thể hiện app\service\Mailer
được tạo trong cấu hình này.
Chúng ta lưu ý rằng, trong config/dependence.php
, sử dụng new
để khởi tạo lớp Mailer
không có vấn đề gì trong ví dụ này, nhưng hãy tưởng tượng nếu lớp Mailer
phụ thuộc vào các lớp khác hoặc lớp Mailer
bên trong sử dụng tiêm qua chú thích, việc khởi tạo bằng new
sẽ không thể tiêm phụ thuộc tự động. Giải pháp là sử dụng tiêm qua giao diện tùy chỉnh, thông qua phương thức Container::get(类名)
hoặc Container::make(类名, [构造函数参数])
để khởi tạo lớp.
Tiêm qua giao diện tùy chỉnh
Trong các dự án thực tế, chúng ta thường mong muốn lập trình hướng đối tượng theo giao diện, chứ không phải theo lớp cụ thể. Ví dụ, trong app\controller\UserController
nên nhập app\service\MailerInterface
thay vì app\service\Mailer
.
Định nghĩa giao diện MailerInterface
.
<?php
namespace app\service;
interface MailerInterface
{
public function mail($email, $content);
}
Định nghĩa việc cài đặt giao diện MailerInterface
.
<?php
namespace app\service;
class Mailer implements MailerInterface
{
private $smtpHost;
private $smtpPort;
public function __construct($smtp_host, $smtp_port)
{
$this->smtpHost = $smtp_host;
$this->smtpPort = $smtp_port;
}
public function mail($email, $content)
{
// Mã gửi email bị lược bỏ
}
}
Nhập giao diện MailerInterface
thay vì việc cài đặt cụ thể.
<?php
namespace app\controller;
use support\Request;
use app\service\MailerInterface;
use DI\Annotation\Inject;
class UserController
{
/**
* @Inject
* @var MailerInterface
*/
private $mailer;
public function register(Request $request)
{
$this->mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
Trong config/dependence.php
, định nghĩa MailerInterface
như sau:
use Psr\Container\ContainerInterface;
return [
app\service\MailerInterface::class => function(ContainerInterface $container) {
return $container->make(app\service\Mailer::class, ['smtp_host' => '192.168.1.11', 'smtp_port' => 25]);
}
];
Như vậy, khi gọi đến giao diện MailerInterface
, sẽ tự động sử dụng cài đặt của Mailer
.
Lợi ích của lập trình theo giao diện là, khi cần thay đổi một thành phần nào đó, bạn không cần phải thay đổi mã nghiệp vụ, chỉ cần thay đổi cài đặt cụ thể trong
config/dependence.php
là đủ. Điều này cũng rất hữu ích trong việc kiểm tra đơn vị.
Các tiêm tùy chỉnh khác
config/dependence.php
không chỉ định nghĩa phụ thuộc của lớp mà còn có thể định nghĩa các giá trị khác như chuỗi, số, mảng,...
Ví dụ, định nghĩa config/dependence.php
như sau:
return [
'smtp_host' => '192.168.1.11',
'smtp_port' => 25
];
Lúc này chúng ta có thể sử dụng @Inject
để tiêm smtp_host
smtp_port
vào thuộc tính của lớp.
<?php
namespace app\service;
use DI\Annotation\Inject;
class Mailer
{
/**
* @Inject("smtp_host")
*/
private $smtpHost;
/**
* @Inject("smtp_port")
*/
private $smtpPort;
public function mail($email, $content)
{
// Mã gửi email bị lược bỏ
echo "{$this->smtpHost}:{$this->smtpPort}\n"; // Sẽ in ra 192.168.1.11:25
}
}
Lưu ý:
@Inject("key")
bên trong có dấu ngoặc kép.
Nội dung khác
Vui lòng tham khảo hướng dẫn php-di