可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Here's my database structure:
In my app I have a company management of customers, employees and branches.
Customers and Employees are associated with one Person, one Individual and one User.
Branches are associated with one Person and one Company.
So, to insert a new costumer or employee, I have to first insert its common data into Person
table, its person data into Person
table, then its user data into User
table and finally create a new record into Customer
or Employee
table.
To insert a new branch, I've to first insert its common data into Person
table, then its company data into Company
table and finally create a new record into Branch
table.
I'm new to MVC concept and I'm a bit lost in how I should design my model classes to CRUD them. I'm using CodeIgniter framework, but I think it's irrelevant here. Should I create a separate class model for each database table? If so, how I should code that? (theory only)
For example, to insert a new costumer...
- Start a new transaction
- Instantiate a new Person
- Fill Person's data
- Save Person
- Instantiate a new Individual
- Fill Individual's data and relate to its Person
- Save Individual
- Instantiate a new User
- Fill Users' data and relate to its Person
- Save User
- Instantiate a new Costumer
- Fill Costumer's data and relate to its Person
- Save Costumer
- End transaction
Is that correct? Where this code should be, in the costumer's controller? Any better convention to use that database structure (I need to use the third normal form design)?
回答1:
Ok, interesting, why do you want to use 3NF?
In MVC web frameworks (like Codeigniter, Zend Framework or Ruby on Rails) you don't require 3NF or even care about the different tables. What you're trying, I'd say, is multiple table inheritance, which is not very common.
So actually you have a, say, Custumer
class, which has all the attributes you have your Customer, User and Person table. To stick to MVC and the thin controller approach, you'd have a save
action in your CustomerController
class which accepts these attributes and instantiates an object from this Costumer
class:
class CostumerController {
function create() {
Costumer c = new Customer();
c.save($_POST['costumer']);
}
}
(Make sure, you check the parameters for SQL injections but your framework should handle that somehow.)
Your customor class should have a constructor which should accept all its attributes and save them to the appropriate tables. I.e.:
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 . '";
...
}
}
As you may have noticed, I've shortened the Sql statements and some of the code since I don't know CodeIgniter. But the idea is to give all the data you need to the save method which saves it to the appropriate tables in you database.
Remember: Only the model class communicate with the database in MVC.
The problem is the ID you use as a foreign key in other tables. So after inserting your data to person you need to query the table for the ID it just inserted and use it in another table.
That is why some frameworks (so to say: all I know), use single table inheritance by default. Picture it like this:
The way you described it, your Costumer
inherits from User
which inherits from Person
. All these tables are merged into one table, which also holds a type
attribute, which says which type that row's object is, in our case: Customer
, User
or Person
. If you only add a user, all unused attributes will be set to NULL
. But this would break your 3NF, of course.
What I don't understand, though, is why you always require 1:1 relationshipts. For example between Person
and Company
.
Oh, now I get it, since there is no companyId (FK)
in Person
, many Person
s can be in one Company
. But why does Branch
have a personId (FK)
. Usually there are more person working in a branch, no?
Back to your question
You can either use thin-controller-fat-model or fat-controller-thin-model, though I prefer the first one. In this, you'd only have a few lines in your controller and more logic in your model (as in my example above). The communication with the database happens only in your model classes.
You usually don't have a one-table-equals-one-model-type approach, but you may also have more than one model type accessing one table (see the single table inheritance thing).
Another approach
Your could have a Costumer
model that accepts your data and "group" it the way your tables are "scattered":
class Customer {
function save(params) {
Person $person = new Person();
$person.save(params['person']);
User $user = new User();
$user.save(params['user'], $person->id);
....
}
}
But... well, as I said, I'd suggest you leave the 3NF approach and use STI (single table inheritance).
Don't forget to make sure to quote form data to avoid SQL injections or see, which functions your framework offers to do that for you.
Sorry for any mistakes in the code, I was only "coding" from memory and didn't even syntax parse it.
HTH
回答2:
TL;DR. No. You should not create separate model for each table.
First of all , your DB diagram is wrong. The fragmentation of User
, Individual
and Person
. They all have 1:1 relations and the User
table seems extremely redundant.Oh .. and what's with the singular naming convention for tables ?!
Anyway ..
Actually, what you refer to as "models" are actually domain objects. They are just a part of model layer. And yes, model in MVC is a layer. Not a class nor an object.
When implemented properly, your domain objects would be separate from classes, that implement storage logic ( usually: data mappers ). Besides adhering to SRP such implementation would also grant you ability for domain object to be independent from storage form. A data mapper can map multiple tables to single domain object, unlike the active record (anti)pattern, which lumps the logic with storage mechanisms.
As for the controllers ... well. You should have a single controller for each view. Controllers are supposed only to change the state of model layer and current view, by passing along data from incoming request.
Your question seems to indicate that you have domain business logic leaking from model layer into presentation layer. Instead you should create services ( you can think of them as "higher order domain objects" ), which facilitate interaction between multiple domain objects and your chosen storage abstractions (data mappers, DAOs, repositories, units of work .. etc). The controller should only the user management service: "here is data, create me a new user account."
P.S. : you might find this post relevant.
回答3:
You will probably get many different opinions.
Without knowing too many details about your application, one of the possible approaches would be to put this into application services.
Then your web app will use proper app service to execute this logic. This will decouple your web interface from the application logic, thus enabling some other application (e.g. some internal auditing service) to use the same logic without duplicating the implementation logic.
I don't know PHP, so I will use some .NET-ish pseudo code, without any specific technology in mind (that's why method calls will deliberately not be made for any popular web app framework).
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);
}
Then in your web app, you will have a method which handles the web request
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");
}
}
One thing which is not really obvious is where to put the transaction handling. It can be inside the application service, or inside the web request handler method.
Although the first seems more intuitive, the problem is that often app services don't know enough context to decide on transaction handling. Imagine this popular money transfer example:
m_svc.TakeMoney(account1, amount);
m_svc.AddMoney(account2, amount);
The service can't handle transactions inside the service method calls, because if the first method succeeds and second fails, we end up with inconsistent state where money was withdrawn from one account, but never reached the other one.
Therefore, transaction must be managed externally:
using(var tx = new Transaction())
{
// now both execute within same transaction
m_svc.TakeMoney(account1, amount);
m_svc.AddMoney(account2, amount);
}
It is up to you to decide what is the best fit for your app.