为什么使用服务容器而不是新的 class
Why use service container instead of new class
什么场景下我们应该使用服务容器而不是直接调用new class?
我从Laravel官方文档上看到它主要用于管理class依赖和执行依赖注入,但是我不确定我是否理解正确。
例如薪资申请,我们将有薪资 class、员工 Class、休假 Class、时间表 Class。
因此,要处理每月工资单,工资单 class 将取决于
- 员工Class (getBasicSalary)
- 请假Class (getUnpaidLeaveCount)
- 时间表Class (getLateHoursCount)
下面是我平时写的(没有任何设计模式)
Class PayrollProcessController
{
public function index(){
$payrollObj = new Payroll();
$payroll->setPayrollMonth('202106');
$payroll->process();
}
}
Class Payroll
{
private $payrollMonth;
public function process()
{
// loop employee from database
foreach ($employees as $employee_id){
$salary = $this->calculateNetSalary($employee_id);
// save into db
}
}
public function calculateNetSalary($employee_id)
{
$employee = Employee::find($employee_id);
$basicSalary = $employee->getBasicSalary();
$basicSalaryPerDay = $basicSalary / date('t');
$basicSalaryPerHour = $basicSalaryPerDay / 8;
$leaveTakenDay = Leave::getUnpaidLeaveCount( $employee->getId(), $this->payrollMonth);
$leaveDeductionAmount = $basicSalaryPerDay * $leaveTakenDay;
$timesheetLateHours = Timesheet::getLateHoursCount($employee->getId(), $this->payrollMonth);
$lateDeductionAmount = $basicSalaryPerHour * $timesheetLateHours;
$netSalary = $basicSalary - $leaveDeductionAmount - $lateDeductionAmount;
return $netSalary;
}
}
如果应用服务容器概念,
Class PayrollProcessController
{
public function index(Payroll $payroll){
$payroll->setPayrollMonth('202106');
$payroll->process();
}
}
Class Payroll
{
public function process()
{
// loop employee from database
foreach ($employees as $employee_id){
$employee = app(Employee::class);
$employee = $employee->find($employee_id);
$leave = app(Leave::class);
$timesheet = app(Timesheet::class);
$salary = $this->calculateNetSalary($employee, $leave, $timesheet);
// save into db
}
}
public function calculateNetSalary(Employee $employee, Leave $leave, Timesheet $timesheet)
{
$basicSalary = $employee->getBasicSalary();
$basicSalaryPerDay = $basicSalary / date('t');
$basicSalaryPerHour = $basicSalaryPerDay / 8;
$leaveTakenDay = $leave->getUnpaidLeaveCount( $employee->getId(), $this->payrollMonth);
$leaveDeductionAmount = $basicSalaryPerDay * $leaveTakenDay;
$timesheetLateHours = $timesheet->getLateHoursCount($employee->getId(), $this->payrollMonth);
$lateDeductionAmount = $basicSalaryPerHour * $timesheetLateHours;
$netSalary = $basicSalary - $leaveDeductionAmount - $lateDeductionAmount;
return $netSalary;
}
}
问题:
以上概念是否正确?我可以看到服务容器允许我将工资单、员工、时间表、休假 class 移动到 laravel 包中,这样我就可以安装在另一个 laravel 应用程序中(主要好处是我可以重新使用代码,如果需要,还可以通过在服务提供商中指定 class 路径来覆盖 class)
我再次查看了服务容器文档
https://laravel.com/docs/8.x/container#when-to-use-the-container,写成:
First, if you write a class that implements an interface and you wish to type-hint that interface on a route or class constructor, you must tell the container how to resolve that interface. Secondly, if you are writing a Laravel package that you plan to share with other Laravel developers, you may need to bind your package's services into the container.
所以我可以说除非我们将工资单模块发布为 laravel 包供其他人使用或编写单元测试,否则使用服务容器没有意义吗?
如果我们看opencart的registry class,是不是和Laravel的服务容器很像?
https://github.com/opencart/opencart/blob/e22ddfb060752cd8134abbfb104202172ab45c86/upload/system/framework.php#L9
以opencart为例,Languageclass是单例设计模式,registry是容器设计模式?
$language = new \Opencart\System\Library\Language($config->get('language_code'));
$language->addPath(DIR_LANGUAGE);
$language->load($config->get('language_code'));
$registry->set('language', $language);
通过 Laravels IoC 容器 (service container
) 实现的依赖注入的目的是分离对象的创建和使用。所以我认为 app(Employee::class);
并不比 new Employee
或 Employee::create()
好。 IoC 不仅仅是将对象注册到容器中,并在需要时将其拉出。
一个实际的例子可能是你有一个 class 传输 SMS
消息。
class Sms
{
private $smsProvider;
public function send()
{
$this->smsProvider->send();
}
}
因此您的 SmsSender
class 需要 smsProvider
才能运行。与其在 Sms
class 中创建 SMS
提供程序的 new
实例,不如将提供程序注入 class (通常是此类示例中的构造函数)并以这种方式使用它。如果你想让它更加灵活,你可以为 SMS
提供者定义一个接口,然后允许服务容器从你的 IoC 映射中注入正确的具体 class。
class Sms
{
private $smsProvider;
public __construct(ISmsProvider $smsProvider)
{
$this->smsProvider = $smsProvider;
}
public function send()
{
$this->smsProvider->send();
}
}
class SmsTwillio implements ISmsProvider
{
}
class SmsNexmo implements ISmsProvider
{
}
然后您将在服务容器中定义映射以将对 ISmsProvider
的引用绑定到具体实现,例如 SmsTwillio
或 SmsNexmo
.
使用您的示例,您可以定义 PayrollService
:
class PayrollService
{
public function process($employees, $start, $end = null)
{
foreach ($employees as $employee) {
$salary = $this->calculateNetSalary($employee);
}
}
private function calculateNetSalary(Employee $employee)
{
}
}
class ProcessPayrollController extends ProcessPayrollController
{
private $payrollService;
public __constructor(PayrollService $PayrollService)
{
$this->payrollService = $payrollService;
}
public function __invoke()
{
$result = $this->payrollService->process(Employee::all(), '2021-06-01');
}
}
我认为您的 Payroll
class 不需要 Leave
或 Timesheet
。我会在你的 Employee
class 上定义 functions
或 scopes
,其中 return 是数据:
$employee->daysUnpaidLeave('2021-06-01');
$employee->hoursLate('2021-06-01');
日期是从 start
日期计算到今天的日期,或者提供一个可选的 end
日期作为第二个参数。
您可以更深入地研究兔子洞并查看其他 creational design patterns 在运行时创建对象,但是,设计模式可能会很快被滥用并增加而不是减少复杂性,因此请保持简单。
OpenCart中的Registry
也是类似的原理。在该示例中,$registry->get('language');
将始终 return Language
的相同实例,所以是的,它将是一个单例。
您的 Payroll
可能是单例,因为您不需要它的多个实例,因为它只是处理您提供给它的数据。
什么场景下我们应该使用服务容器而不是直接调用new class? 我从Laravel官方文档上看到它主要用于管理class依赖和执行依赖注入,但是我不确定我是否理解正确。
例如薪资申请,我们将有薪资 class、员工 Class、休假 Class、时间表 Class。 因此,要处理每月工资单,工资单 class 将取决于
- 员工Class (getBasicSalary)
- 请假Class (getUnpaidLeaveCount)
- 时间表Class (getLateHoursCount)
下面是我平时写的(没有任何设计模式)
Class PayrollProcessController
{
public function index(){
$payrollObj = new Payroll();
$payroll->setPayrollMonth('202106');
$payroll->process();
}
}
Class Payroll
{
private $payrollMonth;
public function process()
{
// loop employee from database
foreach ($employees as $employee_id){
$salary = $this->calculateNetSalary($employee_id);
// save into db
}
}
public function calculateNetSalary($employee_id)
{
$employee = Employee::find($employee_id);
$basicSalary = $employee->getBasicSalary();
$basicSalaryPerDay = $basicSalary / date('t');
$basicSalaryPerHour = $basicSalaryPerDay / 8;
$leaveTakenDay = Leave::getUnpaidLeaveCount( $employee->getId(), $this->payrollMonth);
$leaveDeductionAmount = $basicSalaryPerDay * $leaveTakenDay;
$timesheetLateHours = Timesheet::getLateHoursCount($employee->getId(), $this->payrollMonth);
$lateDeductionAmount = $basicSalaryPerHour * $timesheetLateHours;
$netSalary = $basicSalary - $leaveDeductionAmount - $lateDeductionAmount;
return $netSalary;
}
}
如果应用服务容器概念,
Class PayrollProcessController
{
public function index(Payroll $payroll){
$payroll->setPayrollMonth('202106');
$payroll->process();
}
}
Class Payroll
{
public function process()
{
// loop employee from database
foreach ($employees as $employee_id){
$employee = app(Employee::class);
$employee = $employee->find($employee_id);
$leave = app(Leave::class);
$timesheet = app(Timesheet::class);
$salary = $this->calculateNetSalary($employee, $leave, $timesheet);
// save into db
}
}
public function calculateNetSalary(Employee $employee, Leave $leave, Timesheet $timesheet)
{
$basicSalary = $employee->getBasicSalary();
$basicSalaryPerDay = $basicSalary / date('t');
$basicSalaryPerHour = $basicSalaryPerDay / 8;
$leaveTakenDay = $leave->getUnpaidLeaveCount( $employee->getId(), $this->payrollMonth);
$leaveDeductionAmount = $basicSalaryPerDay * $leaveTakenDay;
$timesheetLateHours = $timesheet->getLateHoursCount($employee->getId(), $this->payrollMonth);
$lateDeductionAmount = $basicSalaryPerHour * $timesheetLateHours;
$netSalary = $basicSalary - $leaveDeductionAmount - $lateDeductionAmount;
return $netSalary;
}
}
问题:
以上概念是否正确?我可以看到服务容器允许我将工资单、员工、时间表、休假 class 移动到 laravel 包中,这样我就可以安装在另一个 laravel 应用程序中(主要好处是我可以重新使用代码,如果需要,还可以通过在服务提供商中指定 class 路径来覆盖 class)
我再次查看了服务容器文档 https://laravel.com/docs/8.x/container#when-to-use-the-container,写成:
First, if you write a class that implements an interface and you wish to type-hint that interface on a route or class constructor, you must tell the container how to resolve that interface. Secondly, if you are writing a Laravel package that you plan to share with other Laravel developers, you may need to bind your package's services into the container.
所以我可以说除非我们将工资单模块发布为 laravel 包供其他人使用或编写单元测试,否则使用服务容器没有意义吗?
如果我们看opencart的registry class,是不是和Laravel的服务容器很像? https://github.com/opencart/opencart/blob/e22ddfb060752cd8134abbfb104202172ab45c86/upload/system/framework.php#L9
以opencart为例,Languageclass是单例设计模式,registry是容器设计模式?
$language = new \Opencart\System\Library\Language($config->get('language_code')); $language->addPath(DIR_LANGUAGE); $language->load($config->get('language_code')); $registry->set('language', $language);
通过 Laravels IoC 容器 (service container
) 实现的依赖注入的目的是分离对象的创建和使用。所以我认为 app(Employee::class);
并不比 new Employee
或 Employee::create()
好。 IoC 不仅仅是将对象注册到容器中,并在需要时将其拉出。
一个实际的例子可能是你有一个 class 传输 SMS
消息。
class Sms
{
private $smsProvider;
public function send()
{
$this->smsProvider->send();
}
}
因此您的 SmsSender
class 需要 smsProvider
才能运行。与其在 Sms
class 中创建 SMS
提供程序的 new
实例,不如将提供程序注入 class (通常是此类示例中的构造函数)并以这种方式使用它。如果你想让它更加灵活,你可以为 SMS
提供者定义一个接口,然后允许服务容器从你的 IoC 映射中注入正确的具体 class。
class Sms
{
private $smsProvider;
public __construct(ISmsProvider $smsProvider)
{
$this->smsProvider = $smsProvider;
}
public function send()
{
$this->smsProvider->send();
}
}
class SmsTwillio implements ISmsProvider
{
}
class SmsNexmo implements ISmsProvider
{
}
然后您将在服务容器中定义映射以将对 ISmsProvider
的引用绑定到具体实现,例如 SmsTwillio
或 SmsNexmo
.
使用您的示例,您可以定义 PayrollService
:
class PayrollService
{
public function process($employees, $start, $end = null)
{
foreach ($employees as $employee) {
$salary = $this->calculateNetSalary($employee);
}
}
private function calculateNetSalary(Employee $employee)
{
}
}
class ProcessPayrollController extends ProcessPayrollController
{
private $payrollService;
public __constructor(PayrollService $PayrollService)
{
$this->payrollService = $payrollService;
}
public function __invoke()
{
$result = $this->payrollService->process(Employee::all(), '2021-06-01');
}
}
我认为您的 Payroll
class 不需要 Leave
或 Timesheet
。我会在你的 Employee
class 上定义 functions
或 scopes
,其中 return 是数据:
$employee->daysUnpaidLeave('2021-06-01');
$employee->hoursLate('2021-06-01');
日期是从 start
日期计算到今天的日期,或者提供一个可选的 end
日期作为第二个参数。
您可以更深入地研究兔子洞并查看其他 creational design patterns 在运行时创建对象,但是,设计模式可能会很快被滥用并增加而不是减少复杂性,因此请保持简单。
OpenCart中的Registry
也是类似的原理。在该示例中,$registry->get('language');
将始终 return Language
的相同实例,所以是的,它将是一个单例。
您的 Payroll
可能是单例,因为您不需要它的多个实例,因为它只是处理您提供给它的数据。