使用数据库驱动的应用程序示例将OOP理论与实践联系起来

时间:2012-08-27 14:40:41

标签: php oop doctrine-orm silex

我是OOP的新手,并且认为我会尝试在我尝试的小应用程序上试用Silex。我正在寻找一些关于我的设计是否符合良好的面向对象原则的建议。

我有一个User对象,它基本上只是一堆属性,getter和setter。然后我有一个UserService对象,它将包含用于验证用户,从数据库获取用户,设置或更新用户信息等的逻辑。我还有一个UserServiceProvder类,用于提供应用程序的UserService类的实例(这似乎是在Silex中创建可重用的代码块的最佳方法)。

我现在的问题是:我正在使用Silex附带的Doctrine DBAL,当我实例化UserService类时,我很想传入对Doctrine对象的引用,然后是硬编码将该对象调用为UserService类的方法。

例如,要通过id从数据库返回User,我可能会创建一个名为getUserById($id)的方法,然后将Doctrine准备好的语句硬编码到该方法中,从数据库中选择该用户,然后返回User个对象。

我是否更好地创建一个完整的其他服务,它只是Doctrine DBAL的进一步抽象,并在实例化时将其传递给UserService?这样,我可以将Doctrine准备好的语句硬编码到该类中,使我的UserService类更加封装和重用,以防我将来决定退出Doctrine。

我想我现在遇到的困难是意识到OOP是否存在过度杀戮这样的事情。在我看来,第二种方法更可重复使用,但它是必要的还是明智的?

1 个答案:

答案 0 :(得分:5)

将数据库访问权移动到单独的类将带来一些优势。首先,如果您将数据库访问与其余逻辑分开,则可以更轻松地替换数据库访问的实现。如果出于某种原因想要删除Doctrine DBAL,您会很高兴所有代码只是引用存储库的某个接口而不是直接查询数据库。

第二大优势是您可以在分离数据库访问逻辑时测试应用程序逻辑。如果为UserService中的用户注入一个Repository,你可以在测试中模拟它,并确保它们只有在实际应用程序逻辑出现问题时才会失败。

您可以做的一个小例子

该界面便于您在整个代码库中进行参考。没有代码引用实现,只引用接口。这样,您可以轻松地替换界面的实现,而无需触及它所使用的所有位置:

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);
  }
}

如果你还没有进行单元测试,我可以想象你不熟悉最后一段代码。我希望你是,如果不是,也请你仔细阅读:D我认为无论如何将答案的完整性包括在内是好的。