Trong mô hình MVC nên xử lý xác nhận?


25

Tôi đang cố gắng kiến ​​trúc lại một ứng dụng web mà tôi đã phát triển để sử dụng mẫu MVC, nhưng tôi không chắc liệu có nên xử lý xác nhận trong mô hình hay không. Ví dụ: tôi đang thiết lập một trong những mô hình của mình như thế này:

class AM_Products extends AM_Object 
{
    public function save( $new_data = array() ) 
    {
        // Save code
    }
}

Câu hỏi đầu tiên: Vì vậy, tôi tự hỏi liệu phương thức lưu của tôi có nên gọi hàm xác thực trên $ new_data hoặc giả sử rằng dữ liệu đã được xác thực?

Ngoài ra, nếu nó là để cung cấp xác nhận, tôi nghĩ rằng một số mã mô hình để xác định các loại dữ liệu sẽ trông như thế này:

class AM_Products extends AM_Object
{
    protected function init() // Called by __construct in AM_Object
    {
        // This would match up to the database column `age`
        register_property( 'age', 'Age', array( 'type' => 'int', 'min' => 10, 'max' => 30 ) ); 
    }
}

Câu hỏi thứ hai: Mỗi lớp con của AM_Object sẽ chạy register_property cho mỗi cột trong cơ sở dữ liệu của đối tượng cụ thể đó. Tôi không chắc đây có phải là một cách tốt để làm điều đó hay không.

Câu hỏi thứ ba: Nếu mô hình xử lý nên được xử lý, nó sẽ trả về thông báo lỗi hoặc mã lỗi và chế độ xem có sử dụng mã để hiển thị thông báo phù hợp không?

Câu trả lời:


30

Trả lời đầu tiên: Vai trò chính của mô hình là duy trì tính toàn vẹn. Tuy nhiên, xử lý đầu vào của người dùng là trách nhiệm của bộ điều khiển.

Nghĩa là, bộ điều khiển phải dịch dữ liệu người dùng (mà hầu hết thời gian chỉ là chuỗi) thành một cái gì đó có ý nghĩa. Điều này đòi hỏi phân tích cú pháp (và có thể phụ thuộc vào những thứ như miền địa phương, ví dụ như có các toán tử thập phân khác nhau, v.v.).
Vì vậy, việc xác nhận thực tế, như trong "dữ liệu có được hình thành tốt không?", Nên được thực hiện bởi bộ điều khiển. Tuy nhiên, việc xác minh, như trong "dữ liệu có ý nghĩa không?" nên được thực hiện trong mô hình.

Để làm rõ điều này với một ví dụ:
Giả sử ứng dụng của bạn cho phép bạn thêm một số thực thể, với một ngày ( ví dụ: một vấn đề với hạn chót). Bạn có thể có API, trong đó ngày có thể được biểu thị dưới dạng dấu thời gian Unix, trong khi khi đến từ trang HTML, nó sẽ là một tập hợp các giá trị khác nhau hoặc một chuỗi theo định dạng MM / DD / YYYY. Bạn không muốn thông tin này trong mô hình. Bạn muốn mỗi bộ điều khiển cố gắng tìm ra ngày. Tuy nhiên, khi ngày được chuyển đến mô hình, mô hình phải duy trì tính toàn vẹn. Ví dụ: có thể có ý nghĩa là không cho phép ngày trong quá khứ hoặc ngày, vào ngày lễ / chủ nhật, v.v.

Bộ điều khiển của bạn chứa các quy tắc đầu vào (xử lý). Mô hình của bạn chứa các quy tắc kinh doanh. Bạn muốn các quy tắc kinh doanh của bạn luôn được thực thi, bất kể điều gì xảy ra. Giả sử bạn có các quy tắc kinh doanh trong bộ điều khiển, thì bạn sẽ phải sao chép chúng, nếu bạn tạo một bộ điều khiển khác.

Câu trả lời thứ hai: Cách tiếp cận có ý nghĩa, tuy nhiên phương pháp có thể được thực hiện mạnh mẽ hơn. Thay vì tham số cuối cùng là một mảng, nó phải là một thể IContstrainthiện được định nghĩa là:

interface IConstraint {
     function test($value);//returns bool
}

Và đối với những con số bạn có thể có một cái gì đó như

class NumConstraint {
    var $grain;
    var $min;
    var $max;
    function __construct($grain = 1, $min = NULL, $max = NULL) {
         if ($min === NULL) $min = INT_MIN;
         if ($max === NULL) $max = INT_MAX;
         $this->min = $min;
         $this->max = $max;
         $this->grain = $grain;
    }
    function test($value) {
         return ($value % $this->grain == 0 && $value >= $min && $value <= $max);
    }
}

Ngoài ra, tôi không thấy những gì 'Age'có nghĩa là đại diện, thành thật mà nói. Có phải đó là tên tài sản thực tế? Giả sử có một quy ước theo mặc định, tham số có thể đơn giản đi đến cuối hàm và là tùy chọn. Nếu không được đặt, nó sẽ mặc định là to_camel_case của tên cột DB.

Do đó, cuộc gọi ví dụ sẽ như sau:

register_property('age', new NumConstraint(1, 10, 30));

Điểm quan trọng của việc sử dụng các giao diện là bạn có thể thêm nhiều ràng buộc hơn khi bạn đi và chúng có thể phức tạp như bạn muốn. Đối với một chuỗi để khớp với một biểu thức thông thường. Đối với một ngày để được ít nhất 7 ngày trước. Và như vậy.

Trả lời thứ ba: Mỗi thực thể Mô hình nên có một phương thức như thế nào Result checkValue(string property, mixed value). Bộ điều khiển nên gọi nó trước khi cài đặt dữ liệu. Các Resultnên có tất cả các thông tin về việc kiểm tra thất bại, và trong trường hợp nó đã làm, đưa ra lý do, vì vậy bộ điều khiển có thể tuyên truyền những quan điểm cho phù hợp.
Nếu một giá trị sai được truyền cho mô hình, mô hình chỉ cần phản hồi bằng cách đưa ra một ngoại lệ.


Cảm ơn bạn đã viết bài này. Nó làm rõ rất nhiều điều về MVC.
AmadeusDrZaius

5

Tôi không hoàn toàn đồng ý với "back2dos": Khuyến nghị của tôi là luôn luôn sử dụng một lớp xác thực / biểu mẫu riêng biệt, bộ điều khiển có thể sử dụng để xác thực dữ liệu đầu vào trước khi gửi đến mô hình.

Từ quan điểm lý thuyết, xác thực mô hình hoạt động trên dữ liệu tin cậy (trạng thái hệ thống nội bộ) và lý tưởng nhất có thể được lặp lại bất cứ lúc nào trong khi xác thực đầu vào hoạt động rõ ràng một lần trên dữ liệu đến từ các nguồn không tin cậy (tùy thuộc vào trường hợp sử dụng và đặc quyền người dùng).

Sự tách biệt này cho phép xây dựng các mô hình, bộ điều khiển và biểu mẫu có thể tái sử dụng có thể được ghép lỏng lẻo thông qua việc tiêm phụ thuộc. Hãy nghĩ về xác thực đầu vào là xác nhận danh sách trắng (Nhận chấp nhận được tốt) và xác thực mô hình là xác thực danh sách đen (Từ chối đã biết xấu Bad). Xác thực danh sách trắng an toàn hơn trong khi xác thực danh sách đen ngăn lớp mô hình của bạn bị ràng buộc quá mức đối với các trường hợp sử dụng rất cụ thể.

Dữ liệu mô hình không hợp lệ phải luôn khiến ngoại lệ bị ném (nếu không ứng dụng có thể tiếp tục chạy mà không nhận thấy lỗi) trong khi giá trị đầu vào không hợp lệ đến từ các nguồn bên ngoài không phải là bất ngờ, nhưng khá phổ biến (trừ khi bạn có người dùng không bao giờ mắc lỗi).

Xem thêm: https://lastzero.net/2015/11/why-im-USE-a-separate-layer-for-input-data-validation/


Để đơn giản, chúng ta hãy giả sử rằng có một nhóm lớp Trình xác thực và tất cả các xác nhận được thực hiện với một hệ thống phân cấp chiến lược. Trẻ em trình xác nhận cụ thể cũng có thể bao gồm các trình xác nhận đặc biệt: e-mail, số điện thoại, mã thông báo mẫu, captcha, mật khẩu và những người khác. Xác thực đầu vào của bộ điều khiển có hai loại: 1) Xác minh sự tồn tại của bộ điều khiển và phương thức / lệnh và 2) kiểm tra sơ bộ dữ liệu (nghĩa là phương thức yêu cầu HTTP, có bao nhiêu dữ liệu đầu vào (Quá nhiều? Quá ít?).
Anthony Rutledge

Sau khi số lượng đầu vào được xác minh, bạn cần biết rằng các điều khiển HTML chính xác đã được gửi, theo tên, lưu ý rằng số lượng đầu vào cho mỗi yêu cầu có thể thay đổi, vì không phải tất cả các điều khiển của biểu mẫu HTML đều gửi một cái gì đó khi để trống ( đặc biệt là các hộp kiểm). Sau này, kiểm tra sơ bộ cuối cùng là kiểm tra kích thước đầu vào. Theo tôi, điều này nên sớm chứ không phải muộn. Thực hiện kiểm tra số lượng, tên điều khiển và kiểm tra kích thước đầu vào cơ bản trong trình xác nhận bộ điều khiển có nghĩa là có Trình xác thực cho mỗi lệnh / phương thức trong bộ điều khiển. Tôi cảm thấy điều này làm cho ứng dụng của bạn an toàn hơn.
Anthony Rutledge

Có, trình xác nhận bộ điều khiển cho một lệnh sẽ được kết hợp chặt chẽ với các đối số (nếu có) cần thiết cho một phương thức mô hình , nhưng bản thân bộ điều khiển sẽ không, lưu lại để tham chiếu đến trình xác nhận của bộ điều khiển đã nói . Đây là một sự thỏa hiệp xứng đáng, vì người ta không được đi về phía trước với giả định rằng hầu hết các đầu vào sẽ là hợp pháp. Càng sớm bạn có thể ngừng truy cập bất hợp pháp vào ứng dụng của bạn, thì càng tốt. Thực hiện nó trong lớp trình xác nhận của bộ điều khiển (số lượng, tên và kích thước tối đa của đầu vào) giúp bạn không phải khởi tạo toàn bộ mô hình để từ chối các yêu cầu HTTP độc hại rõ ràng.
Anthony Rutledge

Điều đó đang được nói, trước khi giải quyết các vấn đề kích thước đầu vào tối đa, người ta phải đảm bảo mã hóa là tốt. Tất cả mọi thứ được xem xét, điều này là quá nhiều cho mô hình được thực hiện, ngay cả khi công việc được gói gọn. Nó trở nên đắt đỏ không cần thiết để từ chối các yêu cầu độc hại. Tóm lại, bộ điều khiển cần có trách nhiệm nhiều hơn với những gì nó gửi cho mô hình. Lỗi cấp độ bộ điều khiển phải gây tử vong, không có thông tin trả lại cho người yêu cầu ngoài 200 OK. Đăng nhập hoạt động. Ném một ngoại lệ gây tử vong. Chấm dứt mọi hoạt động. Dừng tất cả các quá trình càng sớm càng tốt.
Anthony Rutledge

Điều khiển tối thiểu, điều khiển tối đa, điều khiển chính xác, mã hóa đầu vào và kích thước đầu vào tối đa đều liên quan đến bản chất của yêu cầu (theo cách này hay cách khác). Một số người chưa xác định năm điều cốt lõi này là xác định liệu một yêu cầu có nên được thực hiện hay không. Nếu tất cả những điều này không hài lòng, tại sao bạn lại gửi thông tin này cho người mẫu? Câu hỏi hay.
Anthony Rutledge

3

Có, mô hình nên thực hiện xác nhận. UI cũng nên xác nhận đầu vào.

Rõ ràng trách nhiệm của mô hình là xác định các giá trị và trạng thái hợp lệ. Đôi khi các quy tắc như vậy thay đổi thường xuyên. Trong trường hợp đó, tôi sẽ cung cấp mô hình từ siêu dữ liệu và / hoặc trang trí nó.


Còn những trường hợp mà ý định của người dùng rõ ràng là độc hại, hoặc có lỗi thì sao? Ví dụ: một yêu cầu HTTP cụ thể được cho là có không quá bảy (7) giá trị đầu vào, nhưng bộ điều khiển của bạn nhận được bảy mươi (70). Bạn có thực sự sẽ cho phép mười lần (10 lần) số lượng giá trị được phép đánh vào mô hình khi yêu cầu rõ ràng bị hỏng không? Trong trường hợp này, đó là trạng thái của toàn bộ yêu cầu đang được đề cập, không phải là trạng thái của bất kỳ một giá trị cụ thể nào. Chiến lược bảo vệ theo chiều sâu sẽ đề xuất rằng bản chất của yêu cầu HTTP phải được kiểm tra trước khi gửi dữ liệu tới mô hình.
Anthony Rutledge

(tiếp theo) Theo cách này, bạn không kiểm tra các giá trị và trạng thái do người dùng cụ thể cung cấp có hợp lệ không, nhưng tổng số yêu cầu là hợp lệ. Không cần phải đi sâu vào vấn đề đó. Dầu đã ở trên bề mặt.
Anthony Rutledge

(tiếp theo) Không có cách nào để buộc xác nhận giao diện người dùng. Người ta phải xem xét rằng các công cụ tự động có thể được sử dụng giao diện với ứng dụng web của bạn.
Anthony Rutledge

(Sau khi suy nghĩ) Các giá trị và trạng thái hợp lệ của dữ liệu trong mô hình rất quan trọng, nhưng những gì tôi đã mô tả các lần truy cập theo ý định của yêu cầu đến thông qua bộ điều khiển. Bỏ qua việc xác minh ý định sẽ khiến ứng dụng của bạn dễ bị tổn thương hơn. Ý định chỉ có thể là tốt (chơi theo quy tắc của bạn) hoặc xấu (đi ra ngoài quy tắc của bạn). Ý định có thể được xác minh bằng các kiểm tra cơ bản về đầu vào: điều khiển tối thiểu, điều khiển tối đa, điều khiển chính xác, mã hóa đầu vào và kích thước đầu vào tối đa. Đó là một đề xuất tất cả hoặc không có gì. Mọi thứ trôi qua, hoặc yêu cầu không hợp lệ. Không cần phải gửi bất cứ điều gì cho mô hình.
Anthony Rutledge

2

Câu hỏi tuyệt vời!

Về mặt phát triển web trên toàn thế giới, điều gì sẽ xảy ra nếu bạn hỏi những điều sau đây.

"Nếu đầu vào của người dùng xấu được cung cấp cho bộ điều khiển từ giao diện người dùng, bộ điều khiển có nên cập nhật Chế độ xem theo kiểu vòng lặp, buộc các lệnh và dữ liệu đầu vào phải chính xác trước khi xử lý chúng không? Làm thế nào? Điều kiện? Một khung nhìn được kết hợp chặt chẽ với một mô hình? Là logic kinh doanh cốt lõi xác thực đầu vào của người dùng của mô hình, hay nó là sơ bộ cho nó và do đó sẽ xảy ra bên trong bộ điều khiển (vì dữ liệu đầu vào của người dùng là một phần của yêu cầu)?

(Trong thực tế, có thể, và nên, một sự chậm trễ khởi tạo một mô hình cho đến khi có được đầu vào tốt?)

Ý kiến ​​của tôi là các mô hình nên quản lý một tình huống thuần túy và nguyên sơ (càng nhiều càng tốt), không bị cản trở bởi xác thực nhập yêu cầu HTTP cơ bản sẽ xảy ra trước khi khởi tạo mô hình (và chắc chắn trước khi mô hình nhận được dữ liệu đầu vào). Vì việc quản lý dữ liệu trạng thái (liên tục hoặc nói cách khác) và các mối quan hệ API là thế giới của mô hình, hãy để xác thực nhập yêu cầu HTTP cơ bản xảy ra trong bộ điều khiển.

Tóm tắt.

1) Xác thực tuyến đường của bạn (được phân tích cú pháp từ URL), vì bộ điều khiển và phương thức phải tồn tại trước khi mọi thứ khác có thể đi tiếp. Điều này chắc chắn sẽ xảy ra trong vương quốc của bộ điều khiển phía trước (lớp Bộ định tuyến), trước khi đến bộ điều khiển thực sự. Duh. :-)

2) Một mô hình có thể có nhiều nguồn dữ liệu đầu vào: yêu cầu HTTP, cơ sở dữ liệu, tệp, API và có, mạng. Nếu bạn định đặt tất cả xác thực đầu vào của mình vào mô hình, thì bạn xem xét phần xác thực đầu vào yêu cầu HTTP của các yêu cầu nghiệp vụ cho chương trình. Trường hợp đóng cửa.

3) Tuy nhiên, việc cận thị nhiều đối tượng nếu đầu vào yêu cầu HTTP là không tốt! Bạn có thể biết liệu ** đầu vào yêu cầu HTTP ** có tốt không ( có kèm theo yêu cầu ) bằng cách xác thực nó trước khi khởi tạo mô hình và tất cả các độ phức tạp của nó (có, thậm chí nhiều trình xác thực hơn cho dữ liệu đầu vào / đầu ra API và DB).

Kiểm tra như sau:

a) Phương thức yêu cầu HTTP (GET, POST, PUT, PATCH, DELETE ...)

b) Các điều khiển HTML tối thiểu (bạn có đủ không?).

c) Các điều khiển HTML tối đa (bạn có quá nhiều không?).

d) Điều khiển HTML đúng (bạn có đúng không?).

e) Mã hóa đầu vào (thông thường, là mã hóa UTF-8?).

f) Kích thước đầu vào tối đa (có bất kỳ đầu vào nào nằm ngoài giới hạn không?).

Hãy nhớ rằng, bạn có thể nhận được chuỗi và tệp, do đó, việc chờ mô hình khởi tạo có thể rất tốn kém vì các yêu cầu tấn công máy chủ của bạn.

Những gì tôi đã mô tả ở đây nhấn vào mục đích của yêu cầu đến thông qua bộ điều khiển. Bỏ qua việc xác minh ý định sẽ khiến ứng dụng của bạn dễ bị tổn thương hơn. Ý định chỉ có thể là tốt (chơi theo quy tắc cơ bản của bạn) hoặc xấu (đi ra ngoài quy tắc cơ bản của bạn).

Ý định cho một yêu cầu HTTP là một đề xuất tất cả hoặc không có gì. Mọi thứ trôi qua, hoặc yêu cầu không hợp lệ . Không cần phải gửi bất cứ điều gì cho mô hình.

Mức cơ bản của mục đích yêu cầu HTTP này không liên quan gì đến các lỗi nhập và xác thực người dùng thông thường. Trong các ứng dụng của tôi, một yêu cầu HTTP phải hợp lệ theo năm cách trên để tôi tôn trọng nó. Theo cách nói chuyên sâu , bạn sẽ không bao giờ có được xác thực đầu vào của người dùng ở phía máy chủ nếu năm điều này không thành công.

Có, điều này có nghĩa là ngay cả đầu vào tệp phải tuân theo các nỗ lực của bạn để xác minh và cho người dùng biết kích thước tệp tối đa được chấp nhận. Chỉ HTML? Không có JavaScript? Tốt, nhưng người dùng phải được thông báo về hậu quả của việc tải lên các tệp quá lớn (chủ yếu là họ sẽ mất tất cả dữ liệu biểu mẫu và bị loại khỏi hệ thống).

4) Điều này có nghĩa là dữ liệu đầu vào yêu cầu HTTP không phải là một phần của logic nghiệp vụ của ứng dụng? Không, nó chỉ có nghĩa là máy tính là thiết bị hữu hạn và tài nguyên phải được sử dụng một cách khôn ngoan. Nó có ý nghĩa để ngăn chặn hoạt động độc hại sớm hơn, không muộn hơn. Bạn trả nhiều tiền hơn trong các tài nguyên tính toán để chờ dừng lại sau này.

5) Nếu đầu vào yêu cầu HTTP là xấu, toàn bộ yêu cầu là xấu . Đó là cách tôi nhìn vào nó. Định nghĩa về đầu vào yêu cầu HTTP tốt được lấy từ các yêu cầu nghiệp vụ của mô hình, nhưng phải có một số điểm phân định tài nguyên. Bạn sẽ để một yêu cầu xấu tồn tại bao lâu trước khi giết nó và nói, "Ồ, này, đừng bận tâm. Yêu cầu tồi."

Phán quyết không chỉ đơn giản là người dùng đã phạm sai lầm đầu vào hợp lý, mà yêu cầu HTTP vượt quá giới hạn đến mức phải tuyên bố độc hại và dừng ngay lập tức.

6) Vì vậy, đối với tiền của tôi, yêu cầu HTTP (PHƯƠNG PHÁP, URL / tuyến đường và dữ liệu) là TẤT CẢ, hoặc KHÔNG CÓ gì khác có thể tiến hành. Một mô hình mạnh mẽ đã có các nhiệm vụ xác nhận để quan tâm đến chính nó, nhưng một người chăn tài nguyên tốt nói "Cách của tôi, hoặc đường cao. Hãy sửa lại, hoặc không đến chút nào."

Đây là chương trình của bạn, mặc dù. "Có nhiều hơn một cách để làm điều đó." Một số cách tốn nhiều thời gian và tiền bạc hơn những cách khác. Xác thực dữ liệu yêu cầu HTTP sau này (trong mô hình) sẽ có chi phí cao hơn trong suốt vòng đời của một ứng dụng (đặc biệt là nếu tăng hoặc giảm).

Nếu trình xác nhận của bạn là mô-đun, xác thực cơ bản * đầu vào yêu cầu HTTP ** trong bộ điều khiển sẽ không thành vấn đề. Chỉ cần sử dụng lớp Trình xác thực chiến lược, trong đó trình xác thực đôi khi cũng bao gồm các trình xác nhận chuyên biệt (e-mail, điện thoại, mã thông báo mẫu, captcha, ...).

Một số người coi điều này là hoàn toàn sai lầm, nhưng HTTP đang ở giai đoạn đầu khi Gang of Four viết các mẫu thiết kế: Các yếu tố của phần mềm hướng đối tượng có thể sử dụng lại .

================================================== ========================

Bây giờ, khi nó liên quan đến xác thực đầu vào của người dùng thông thường (sau khi yêu cầu HTTP được coi là hợp lệ), nó đang cập nhật chế độ xem khi người dùng rối tung lên mà bạn cần phải suy nghĩ! Loại xác nhận đầu vào của người dùng nên xảy ra trong mô hình.

Bạn không có gì đảm bảo về JavaScript ở mặt trước. Điều này có nghĩa là bạn không có cách nào để đảm bảo cập nhật không đồng bộ giao diện người dùng của ứng dụng với các trạng thái lỗi. Tăng cường tiến bộ thực sự cũng sẽ bao gồm cả trường hợp sử dụng đồng bộ.

Kế toán cho trường hợp sử dụng đồng bộ là một nghệ thuật bị mất ngày càng nhiều vì một số người không muốn trải qua thời gian và rắc rối, theo dõi trạng thái của tất cả các thủ thuật UI của họ (hiển thị / ẩn điều khiển, tắt / bật điều khiển , chỉ báo lỗi, thông báo lỗi) ở mặt sau (thường bằng cách theo dõi trạng thái trong mảng).

Cập nhật : Trong sơ đồ, tôi nói rằng Viewnên tham khảo Model. Không. Bạn nên truyền dữ liệu Viewtừ Modelđể giữ khớp nối lỏng lẻo. nhập mô tả hình ảnh ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.