2

这是我的数据库结构:

数据库模式

在我的应用程序中,我对客户、员工和分支机构进行了公司管理。

客户和员工与一个人、一个个人和一个用户相关联。分支机构与一个人和一个公司相关联。

因此,要插入一个新的客户或员工,我必须先将其公共数据插入Person表中,将其人员数据插入Person表中,然后将其用户数据插入表中,User最后在表中创建新记录。CustomerEmployee

要插入一个新的分支,我必须先将它的公共数据插入到Person表中,然后将其公司数据插入到Company表中,最后在表中创建一条新记录Branch

我是 MVC 概念的新手,我对如何设计我的模型类来对它们进行 CRUD 有点迷茫。我正在使用 CodeIgniter 框架,但我认为这无关紧要。我应该为每个数据库表创建一个单独的类模型吗?如果是这样,我应该如何编码?(仅理论)

例如,要插入一个新客户...

  1. 开始新的交易
  2. 实例化一个新的人
  3. 填写个人资料
  4. 救人
  5. 实例化一个新个体
  6. 填写个人数据并与其个人相关
  7. 保存个人
  8. 实例化一个新用户
  9. 填写用户的数据并与其人员相关
  10. 保存用户
  11. 实例化一个新的 Costumer
  12. 填写客户的数据并与其人员相关
  13. 节省客户
  14. 结束交易

那是对的吗?在客户的控制器中,这段代码应该在哪里?使用该数据库结构有什么更好的约定(我需要使用第三范式设计)?

4

3 回答 3

2

TL;博士。不,您不应该为每个表创建单独的模型。


首先,您的数据库图是错误的。和的碎片User化。它们都有 1:1 的关系,而且表格看起来非常多余。哦..表格的单数命名约定是什么?!IndividualPersonUser

反正 ..


实际上,您所说的“模型”实际上是域对象。它们只是模型层的一部分。是的,MVC 中的模型是一层。不是类也不是对象。

如果实施得当,您的域对象将与实现存储逻辑的类分开(通常:数据映射器)。除了遵守SRP之外,这样的实现还可以让您能够使域对象独立于存储形式。数据映射器可以将多个表映射到单个域对象,这与将逻辑与存储机制混为一谈的活动记录(反)模式不同。

至于控制器......好吧。每个视图都应该有一个控制器。控制器应该仅通过传递来自传入请求的数据来更改模型层和当前视图的状态。

您的问题似乎表明您的域业务逻辑从模型层泄漏到表示层。相反,您应该创建服务(您可以将它们视为“高阶域对象”),这有助于多个域对象与您选择的存储抽象(数据映射器、DAO、存储库、工作单元等)之间的交互。控制器应该只有用户管理服务:“这里是数据,给我创建一个新的用户帐户。”


PS你可能会发现这篇文章很相关。

于 2012-09-30T17:33:12.703 回答
1

好吧,有趣的是,为什么要使用 3NF?

在 MVC Web 框架(如 Codeigniter、Zend Framework 或 Ruby on Rails)中,您不需要 3NF,甚至不需要关心不同的表。我想说的是,您正在尝试的是多表继承,这不是很常见。

所以实际上你有一个Custumer类,它具有你的 Customer、User 和 Person 表的所有属性。要坚持 MVC 和瘦控制器方法,您的类中有一个save动作,CustomerController它接受这些属性并从此类实例化一个对象Costumer

class CostumerController {
    function create() {
        Costumer c = new Customer();
        c.save($_POST['costumer']);
    }
}

(确保您检查 SQL 注入的参数,但您的框架应该以某种方式处理它。)

您的自定义类应该有一个构造函数,它应该接受其所有属性并将它们保存到适当的表中。IE:

class Costumer {
    function save(params) {
        $sql = 'INSERT INTO person SET `phoneNumber` = "' . params['phonenumber'] . '"';
        $dbh->query($sql);
        $lastid = PDO::lastInsertId;
        $sql = 'INSERT INTO user SET `password` = "'. md5(params['password']) . '", `personId` = "' . $lastid . '";
        ...
    }
}

您可能已经注意到,由于我不了解 CodeIgniter,因此我缩短了 Sql 语句和一些代码。但是这个想法是将您需要的所有数据提供给 save 方法,该方法将其保存到数据库中的适当表中。 记住:只有模型类与 MVC 中的数据库通信。

问题是您在其他表中用作外键的 ID。因此,在将数据插入到人员之后,您需要在表中查询它刚刚插入的 ID,并在另一个表中使用它。

这就是为什么一些框架(可以这么说:我所知道的)默认使用单表继承。把它想象成这样:

您描述它的方式,您的Costumer继承自Userwhich 继承自Person. 所有这些表都合并到一个表中,该表还包含一个type属性,该属性表示该行的对象是哪种类型,在我们的例子中是CustomerUserPerson。如果只添加一个用户,所有未使用的属性都将设置为NULL。但这当然会破坏你的 3NF。

不过,我不明白为什么你总是需要 1:1 的关系。例如Person和之间Company。哦,现在我明白了,既然没有companyId (FK)in Person,很多Persons 可以在一个中Company。但是为什么Branch会有一个personId (FK). 通常有更多的人在一个分支机构工作,不是吗?

回到你的问题

您可以使用Thin-controller-fat-modelfat-controller-thin-model,但我更喜欢第一个。在这种情况下,您的控制器中只有几行代码,模型中有更多逻辑(如我上面的示例所示)。与数据库的通信仅发生在您的模型类中。您通常没有一个表等于一个模型类型的方法,但您也可能有多个模型类型访问一个表(请参阅单表继承)。

另一种方法

您可以有一个Costumer模型来接受您的数据并以表格“分散”的方式对其进行“分组”:

class Customer {
    function save(params) {
        Person $person = new Person();
        $person.save(params['person']);
        User $user = new User();
        $user.save(params['user'], $person->id);
        ....
    }
}

但是...好吧,正如我所说,我建议您离开 3NF 方法并使用 STI(单表继承)。

不要忘记确保引用表单数据以避免 SQL 注入或查看您的框架提供哪些功能来为您执行此操作。

对不起,代码中的任何错误,我只是从内存中“编码”,甚至没有语法解析它。

高温高压

于 2012-09-30T16:18:05.843 回答
0

你可能会得到很多不同的意见。在不了解有关您的应用程序的太多细节的情况下,一种可能的方法是将其放入应用程序服务中。

然后您的 Web 应用程序将使用适当的应用程序服务来执行此逻辑。这将使您的 Web 界面与应用程序逻辑分离,从而使某些其他应用程序(例如某些内部审计服务)能够使用相同的逻辑,而无需复制实现逻辑。

我不懂 PHP,所以我将使用一些 .NET 式的伪代码,而不考虑任何特定技术(这就是为什么故意不对任何流行的 Web 应用程序框架进行方法调用的原因)。

class CustomerService // typically it will be an interface ICustomerSvc, but nevermind...
{ 
   // this will implement your logic to add customer - points 1-14
   // it might return the ID of the customer or not (CustomerID is typically
   // an integer, string, GUID, etc
   CustomerID AddCustomer(CustomerInfo info); 
}

然后在您的网络应用程序中,您将有一个处理网络请求的方法

void AddCustomer(CustomerData data) 
{
   // note: data is not necessarily the same CustomerInfo type.
   // this is your web app model, and can be same but doesn't have to
   try {
      // m_customerSvc - can be instantiated withing class, provided in constructor, etc
      var id = m_customerSvc.AddCustomer(data);      // add a customer
      RedirectTo("confirmation_page_for_user_", id); // show confirmation page
   }
   catch(...) {
      RedirectTo("error_page");
   }
}

不太明显的一件事是在哪里放置事务处理。它可以在应用程序服务内部,也可以在 Web 请求处理程序方法内部。

虽然第一个看起来更直观,但问题是应用服务通常不知道足够的上下文来决定事务处理。想象一下这个流行的汇款示例:

 m_svc.TakeMoney(account1, amount);
 m_svc.AddMoney(account2, amount);

该服务无法处理服务方法调用内部的事务,因为如果第一个方法成功而第二个方法失败,我们最终会出现不一致的状态,即资金从一个帐户中提取,但从未到达另一个帐户。

因此,事务必须在外部进行管理:

using(var tx = new Transaction())
{
   // now both execute within same transaction
   m_svc.TakeMoney(account1, amount);
   m_svc.AddMoney(account2, amount);
}

由您决定最适合您的应用程序的内容。

于 2012-09-30T14:14:09.600 回答