将数据库访问权移至单独的类将为您带来一些优势。首先,如果您将数据库访问与其他逻辑分开,您可以更轻松地替换数据库访问的实现。如果出于某种原因您想放弃 Doctrine DBAL,您会很高兴所有代码只是引用存储库的某个接口,而不是直接查询数据库。
第二个很大的优势是您可以在分离数据库访问逻辑的情况下测试应用程序逻辑。如果您在 UserService 中为用户注入一个存储库,您可以在测试中模拟它,并确保它们仅在实际应用程序逻辑出现问题时才会失败。
你可以做什么的一个小例子
该界面便于在整个代码库中参考。没有代码引用实现,只有接口。这样,您可以轻松地替换接口的实现,而无需触及它使用的所有地方:
interface IUserRepository
{
/**
* @return User
*/
public function getUserById($userId);
}
当然,您确实需要实现所述接口。这就是您注入到 UserService 中的内容。这就是你有一天可能会用另一种接口实现来代替的东西:
class DoctrineDBALUserRepository implements IUserRepository
{
/**
* @return User
*/
public function getUserById($userId)
{
//implementation specific for Doctrine DBAL
}
}
UserService 只知道接口,可以自由使用。为了避免在代码中的很多地方注入 UserRepository,您可以创建一个方便的构建方法。注意引用接口的构造函数和注入该接口实现的构建方法:
class UserService
{
private $UserRepository;
public static build()
{
return new UserService(new DoctrineDBALUserRepository());
}
public function __construct(IUserRepository $UserRepository)
{
$this->UserRepository = $UserRepository;
}
public function getUserById($userId)
{
if ($User = $this->UserRepository->getUserById($userId) {
return $User;
}
throw new RuntimeException('O noes, we messed up');
}
有了这个,您可以为业务逻辑编写测试(例如,如果保存失败则抛出异常):
public function UserServiceTest extends PHPUnit_Framework_TestCase
{
public function testGetUserById_whenRetrievingFails_shouldThrowAnException()
{
$RepositoryStub = $this->getMock('IUserRepository');
$RepositoryStub->expects($this->any())->method('getUserById')->will($this->returnValue(false);
$UserService = new UserService($RepositoryStub);
$this->setExpectedException('RuntimeException');
$UserService->getUserById(1);
}
}
如果您还没有进行单元测试,我可以想象您不熟悉最后一段代码。我希望你是,如果不是的话,我也敦促你阅读:DI 认为无论如何都包括它对答案的完整性是有好处的。