Dalam MVC haruskah model menangani validasi?

25

Saya mencoba merancang ulang aplikasi web yang saya kembangkan untuk menggunakan pola MVC, tetapi saya tidak yakin apakah validasi harus ditangani dalam model atau tidak. Misalnya, saya menyiapkan salah satu model saya seperti ini:

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

Pertanyaan Pertama: Jadi saya bertanya-tanya apakah metode penyimpanan saya harus memanggil fungsi validasi pada $ new_data atau menganggap bahwa data telah divalidasi?

Juga, jika ingin menawarkan validasi, saya pikir beberapa kode model untuk mendefinisikan tipe data akan terlihat seperti ini:

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

Pertanyaan Kedua: Setiap kelas anak dari AM_Object akan menjalankan register_property untuk setiap kolom dalam database objek tertentu. Saya tidak yakin apakah ini cara yang baik untuk melakukannya atau tidak.

Pertanyaan Ketiga: Jika validasi harus ditangani oleh model, haruskah itu mengembalikan pesan kesalahan atau kode kesalahan dan meminta pandangan menggunakan kode untuk menampilkan pesan yang sesuai?

Brandon Wamboldt
sumber

Jawaban:

30

Jawaban Pertama: Peran utama model adalah untuk menjaga integritas. Namun memproses input pengguna adalah tanggung jawab pengontrol.

Artinya, pengontrol harus menerjemahkan data pengguna (yang sebagian besar waktu hanya berupa string) menjadi sesuatu yang bermakna. Ini membutuhkan penguraian (dan mungkin bergantung pada hal-hal seperti lokal, mengingat bahwa misalnya, ada operator desimal yang berbeda, dll.).
Jadi validasi aktual, seperti pada "apakah data terbentuk dengan baik?", Harus dilakukan oleh pengontrol. Namun verifikasi, seperti pada "apakah data masuk akal?" harus dilakukan dalam model.

Untuk mengklarifikasi ini dengan contoh:
Asumsikan aplikasi Anda memungkinkan Anda untuk menambahkan beberapa entitas, dengan tanggal (masalah dengan tenggat waktu misalnya). Anda mungkin memiliki API, di mana tanggal mungkin direpresentasikan sebagai perangko waktu Unix belaka, sementara ketika berasal dari halaman HTML, itu akan menjadi satu set nilai yang berbeda atau string dalam format MM / DD / YYYY. Anda tidak ingin informasi ini ada dalam model. Anda ingin setiap pengontrol mencoba mencari tahu tanggalnya secara individual. Namun, ketika tanggal tersebut kemudian diteruskan ke model, model tersebut harus menjaga integritas. Sebagai contoh, mungkin masuk akal untuk tidak mengizinkan tanggal di masa lalu, atau tanggal, yang pada hari libur / minggu, dll.

Pengontrol Anda berisi aturan input (pemrosesan). Model Anda mengandung aturan bisnis. Anda ingin aturan bisnis Anda selalu ditegakkan, apa pun yang terjadi. Dengan asumsi Anda memiliki aturan bisnis di controller, maka Anda harus menduplikatnya, jika Anda pernah membuat controller yang berbeda.

Jawaban Kedua: Pendekatannya memang masuk akal, namun metode ini bisa dibuat lebih kuat. Alih-alih parameter terakhir menjadi array, itu harus berupa instance IContstraintyang didefinisikan sebagai:

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

Dan untuk angka Anda dapat memiliki sesuatu sebagai

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

Juga saya tidak melihat apa 'Age'yang dimaksudkan untuk mewakili, jujur. Apakah itu nama properti yang sebenarnya? Dengan asumsi ada konvensi secara default, parameter dapat dengan mudah pergi ke akhir fungsi dan menjadi opsional. Jika tidak disetel, itu akan default ke to_camel_case dari nama kolom DB.

Jadi contoh panggilan akan terlihat seperti:

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

Inti dari menggunakan antarmuka adalah bahwa Anda dapat menambah lebih banyak kendala saat Anda pergi dan mereka dapat serumit yang Anda inginkan. Agar string cocok dengan ekspresi reguler. Untuk kencan setidaknya 7 hari ke depan. Dan seterusnya.

Jawaban Ketiga: Setiap entitas Model harus memiliki metode seperti Result checkValue(string property, mixed value). Pengontrol harus memanggilnya sebelum mengatur data. The Resultharus memiliki semua informasi tentang apakah cek gagal, dan dalam hal itu, memberikan alasan, sehingga controller dapat menyebarkan mereka ke tampilan yang sesuai.
Jika nilai yang salah diteruskan ke model, model harus merespons dengan meningkatkan pengecualian.

back2dos
sumber
Terima kasih atas artikel ini. Itu menjelaskan banyak hal tentang MVC.
AmadeusDrZaius
5

Saya tidak sepenuhnya setuju dengan "back2dos": Rekomendasi saya adalah untuk selalu menggunakan lapisan form / validasi terpisah, yang dapat digunakan oleh pengontrol untuk memvalidasi data input sebelum dikirim ke model.

Dari sudut pandang teoretis, validasi model beroperasi pada data tepercaya (kondisi sistem internal) dan idealnya harus dapat diulangi pada titik waktu mana pun sementara validasi input secara eksplisit beroperasi sekali pada data yang berasal dari sumber yang tidak terpercaya (tergantung pada kasus penggunaan dan hak pengguna).

Pemisahan ini memungkinkan untuk membuat model, pengontrol, dan bentuk yang dapat digunakan kembali yang dapat digabungkan secara longgar melalui injeksi ketergantungan. Anggap validasi input sebagai validasi daftar putih (“accept known good”) dan validasi model sebagai validasi daftar hitam (“reject bad known bad”). Validasi daftar putih lebih aman sementara validasi daftar hitam mencegah lapisan model Anda dari terlalu dibatasi untuk kasus penggunaan yang sangat spesifik.

Data model yang tidak valid harus selalu menyebabkan pengecualian untuk dibuang (jika tidak aplikasi dapat terus berjalan tanpa memperhatikan kesalahan) sementara nilai input yang tidak valid yang berasal dari sumber eksternal tidak terduga, tetapi lebih umum (kecuali Anda memiliki pengguna yang tidak pernah melakukan kesalahan).

Lihat juga: https://lastzero.net/2015/11/why-im-using-a-separate-layer-for-input-data-validation/

lastzero
sumber
Untuk kesederhanaan, mari kita asumsikan bahwa ada keluarga kelas Validator, dan bahwa semua validasi dilakukan dengan hierarki yang strategis. Anak validator beton juga dapat terdiri dari validator khusus: email, nomor telepon, token formulir, captcha, kata sandi, dan lainnya. Validasi input pengontrol terdiri dari dua jenis: 1) Memverifikasi keberadaan pengontrol dan metode / perintah, dan 2) pemeriksaan awal data (yaitu metode permintaan HTTP, berapa banyak input data (Terlalu banyak? Terlalu sedikit?).
Anthony Rutledge
Setelah jumlah input diverifikasi, Anda perlu tahu bahwa kontrol HTML yang benar dikirimkan, berdasarkan nama, mengingat bahwa jumlah input per permintaan dapat bervariasi, karena tidak semua kontrol dari formulir HTML mengirimkan sesuatu ketika dibiarkan kosong ( terutama kotak centang). Setelah ini, pemeriksaan pendahuluan terakhir adalah tes ukuran input. Menurut saya, ini harusnya awal , tidak terlambat. Melakukan kuantitas, nama kontrol, dan pengecekan ukuran input dasar dalam validator kontroler akan berarti memiliki Validator untuk setiap perintah / metode dalam controller. Saya merasa ini membuat aplikasi Anda lebih aman.
Anthony Rutledge
Ya, validator pengontrol untuk suatu perintah akan digabungkan dengan argumen (jika ada) yang diperlukan untuk metode model , tetapi pengontrol itu sendiri tidak akan, kecuali untuk referensi ke validator pengontrol tersebut . Ini adalah kompromi yang layak, karena seseorang tidak boleh berjalan dengan asumsi bahwa sebagian besar input akan sah. Semakin cepat Anda dapat menghentikan akses tidak sah ke aplikasi Anda, semakin baik. Melakukannya di kelas validator pengontrol (jumlah, nama, dan ukuran maksimal input) membuat Anda tidak perlu membuat seluruh model untuk menolak permintaan HTTP yang jelas-jelas berbahaya.
Anthony Rutledge
Yang sedang berkata, sebelum menangani masalah ukuran input maks, orang harus memastikan pengkodean baik. Semua hal dipertimbangkan, ini terlalu banyak untuk dilakukan oleh model, bahkan jika pekerjaan dirangkum. Menjadi tidak perlu mahal untuk menolak permintaan jahat. Singkatnya, pengontrol perlu lebih bertanggung jawab atas apa yang dikirimkannya ke model. Kegagalan tingkat pengontrol harus berakibat fatal, tanpa informasi pengembalian ke pemohon selain 200 OK. Catat aktivitas. Melempar pengecualian yang fatal. Hentikan semua aktivitas. Hentikan semua proses sesegera mungkin.
Anthony Rutledge
Kontrol minimum, kontrol maksimum, kontrol yang benar, penyandian input, dan ukuran input maksimal semuanya berkaitan dengan sifat permintaan (dengan satu atau lain cara). Beberapa orang belum mengidentifikasi lima hal inti ini sebagai penentu apakah suatu permintaan harus dihormati. Jika semua hal ini tidak terpenuhi, mengapa Anda mengirimkan informasi ini ke model? Pertanyaan bagus.
Anthony Rutledge
3

Ya, model harus melakukan validasi. UI juga harus memvalidasi input.

Jelas merupakan tanggung jawab model untuk menentukan nilai dan status yang valid. Terkadang aturan seperti itu sering berubah. Dalam hal ini saya akan memberi makan model dari metadata dan / atau menghiasnya.

Elang
sumber
Bagaimana dengan kasus-kasus di mana niat pengguna jelas-jelas berbahaya, atau salah? Misalnya, permintaan HTTP tertentu seharusnya tidak lebih dari tujuh (7) nilai input, tetapi pengontrol Anda mendapat tujuh puluh (70). Apakah Anda benar-benar akan mengizinkan sepuluh kali (10x) jumlah nilai yang diizinkan untuk mencapai model ketika permintaan jelas rusak? Dalam hal ini, itu adalah keadaan dari seluruh permintaan yang dipertanyakan, bukan keadaan dari satu nilai tertentu. Strategi pertahanan mendalam akan menyarankan bahwa sifat permintaan HTTP harus diperiksa sebelum mengirim data ke model.
Anthony Rutledge
(lanjutan) Dengan cara ini, Anda tidak memeriksa bahwa nilai dan status yang diberikan pengguna tertentu valid, tetapi totalitas permintaan itu valid. Tidak perlu menelusuri sejauh itu, belum. Minyak sudah ada di permukaan.
Anthony Rutledge
(lanjutan) Tidak ada cara untuk memaksa validasi front-end. Orang harus mempertimbangkan bahwa alat otomatis dapat digunakan antarmuka dengan aplikasi web Anda.
Anthony Rutledge
(Setelah dipikirkan) Nilai yang valid dan status data dalam model itu penting, tetapi apa yang saya jelaskan mengenai tujuan permintaan yang masuk melalui controller. Mengabaikan verifikasi niat membuat aplikasi Anda lebih rentan. Niat hanya bisa baik (bermain menurut aturan Anda) atau buruk (keluar dari aturan Anda). Intent dapat diverifikasi dengan pemeriksaan dasar pada input: kontrol minimum, kontrol maksimum, kontrol yang benar, pengkodean input, dan ukuran input maks. Ini semua atau tidak sama sekali proposisi. Semuanya lewat, atau permintaan tidak valid. Tidak perlu mengirim apa pun ke model.
Anthony Rutledge
2

Pertanyaan bagus!

Dalam hal pengembangan web di seluruh dunia, bagaimana jika Anda menanyakan yang berikut, juga.

"Jika input pengguna yang buruk diberikan ke pengontrol dari antarmuka pengguna, haruskah pengontrol memperbarui Lihat dalam semacam siklus berulang, memaksa perintah dan memasukkan data menjadi akurat sebelum memprosesnya ? Bagaimana? Bagaimana tampilan diperbarui di bawah normal kondisi? Apakah pandangan erat dengan model? Apakah input pengguna logika bisnis inti dari model, atau apakah itu pendahuluan untuk itu dan dengan demikian harus terjadi di dalam controller (karena data input pengguna adalah bagian dari permintaan)?

(Dampaknya, dapat, dan haruskah, satu penundaan instantiating model sampai input yang baik diperoleh?)

Pendapat saya adalah bahwa model harus mengelola keadaan murni dan murni (sebanyak mungkin), tidak terbebani oleh validasi input permintaan HTTP dasar yang harus terjadi sebelum model instantiation (dan pasti sebelum model mendapatkan data input). Karena mengelola data status (persisten, atau lainnya) dan hubungan API adalah dunia model, biarkan validasi input permintaan HTTP dasar terjadi di controller.

Meringkas.

1) Validasikan rute Anda (diurai dari URL), karena pengontrol dan metode harus ada sebelum hal lain dapat dilanjutkan. Ini pasti harus terjadi di ranah front-controller (kelas Router), sebelum sampai ke controller yang sebenarnya. Duh. :-)

2) Suatu model mungkin memiliki banyak sumber data input: permintaan HTTP, database, file, API, dan ya, jaringan. Jika Anda akan menempatkan semua validasi input Anda ke dalam model, maka Anda mempertimbangkan bagian validasi input permintaan HTTP dari persyaratan bisnis untuk program ini. Kasus ditutup.

3) Namun, itu adalah rabun untuk melalui pengeluaran instantiating banyak objek jika input permintaan HTTP tidak baik! Anda dapat mengetahui apakah ** HTTP request input ** baik ( yang disertakan dengan permintaan ) dengan memvalidasinya sebelum membuat model dan semua kerumitannya (ya, bahkan mungkin lebih validator untuk data input / output API dan DB).

Tes berikut ini:

a) Metode permintaan HTTP (GET, POST, PUT, PATCH, DELETE ...)

b) Kontrol HTML minimum (apakah Anda punya cukup?).

c) Kontrol HTML maksimum (apakah Anda memiliki terlalu banyak?).

d) Kontrol HTML yang benar (apakah Anda memiliki yang benar?).

e) Pengodean input (biasanya, apakah pengodean UTF-8?).

f) Ukuran input maksimum (adakah salah satu input di luar batas?).

Ingat, Anda mungkin mendapatkan string dan file, jadi menunggu model untuk instantiate bisa menjadi sangat mahal karena permintaan menekan server Anda.

Apa yang saya jelaskan di sini mengenai maksud dari permintaan yang masuk melalui controller. Mengabaikan verifikasi niat membuat aplikasi Anda lebih rentan. Niat hanya bisa baik (bermain dengan aturan fundamental Anda) atau buruk (melampaui aturan fundamental Anda).

Maksud untuk permintaan HTTP adalah proposisi semua atau tidak sama sekali. Semuanya lewat, atau permintaan tidak valid . Tidak perlu mengirim apa pun ke model.

Tingkat dasar ini HTTP meminta niat memiliki apa-apa hubungannya dengan kesalahan input user biasa dan validasi. Dalam aplikasi saya, permintaan HTTP harus valid dalam lima cara di atas agar saya menghormatinya. Dalam pertahanan-mendalam cara berbicara, Anda tidak pernah bisa validasi input pengguna pada server-side jika setiap lima hal ini gagal.

Ya, ini berarti bahkan input file harus sesuai dengan upaya front-end Anda untuk memverifikasi dan memberi tahu pengguna ukuran file maksimal yang diterima. Hanya HTML? Tidak Ada JavaScript? Baik, tetapi pengguna harus diberitahu tentang konsekuensi dari mengunggah file yang terlalu besar (terutama, bahwa mereka akan kehilangan semua data formulir dan dikeluarkan dari sistem).

4) Apakah ini berarti bahwa data input permintaan HTTP bukan bagian dari logika bisnis aplikasi? Tidak, itu hanya berarti komputer adalah perangkat yang terbatas dan sumber daya harus digunakan dengan bijak. Masuk akal untuk menghentikan aktivitas jahat lebih cepat, bukan nanti. Anda membayar lebih banyak dalam sumber daya komputasi untuk menunggu untuk menghentikannya nanti.

5) Jika input permintaan HTTP buruk, seluruh permintaan buruk . Begitulah cara saya melihatnya. Definisi input permintaan HTTP yang baik berasal dari persyaratan bisnis model, tetapi harus ada beberapa titik demarkasi sumber daya. Berapa lama Anda akan membiarkan permintaan buruk hidup sebelum membunuhnya dan berkata, "Oh, hei, sudahlah. Permintaan buruk."

Penghakiman tidak hanya karena pengguna telah membuat kesalahan input yang masuk akal, tetapi bahwa permintaan HTTP sangat di luar batas sehingga harus dinyatakan berbahaya dan segera dihentikan.

6) Jadi, untuk uang saya, permintaan HTTP (METHOD, URL / rute, dan data) adalah SEMUA baik, atau TIDAK ADA yang bisa dilanjutkan. Model yang kuat sudah memiliki tugas validasi untuk dikhawatirkan, tetapi seorang penggembala sumber daya yang baik mengatakan, "Cara saya, atau cara tinggi. Ayo benar, atau jangan datang sama sekali."

Ini adalah program Anda. "Ada lebih dari satu cara untuk melakukannya." Beberapa cara lebih mahal dalam waktu dan uang daripada yang lain. Memvalidasi data permintaan HTTP nanti (dalam model) harus lebih mahal sepanjang masa aplikasi (terutama jika meningkatkan atau keluar).

Jika validator Anda modular, memvalidasi input HTTP * dasar ** di controller seharusnya tidak menjadi masalah. Cukup gunakan kelas Validator yang strategis, di mana validator terkadang juga terdiri dari validator khusus (email, telepon, token formulir, captcha, ...).

Beberapa orang melihat ini sebagai langkah yang salah, tetapi HTTP masih dalam masa pertumbuhan ketika Gang of Four menulis Pola Desain: Elemen Perangkat Lunak Berorientasi Objek yang Dapat Digunakan Kembali .

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

Sekarang, karena ini berkaitan dengan validasi input pengguna normal (setelah permintaan HTTP dianggap valid), itu memperbarui tampilan ketika pengguna mengacaukan yang perlu Anda pikirkan! Jenis validasi input pengguna ini harus terjadi dalam model.

Anda tidak memiliki jaminan JavaScript di front-end. Ini berarti Anda tidak memiliki cara untuk menjamin pembaruan asinkron UI Anda dengan status kesalahan. Peningkatan progresif sejati juga akan mencakup kasus penggunaan sinkron.

Akuntansi untuk use case sinkron adalah seni yang semakin hilang karena beberapa orang tidak ingin melalui waktu, dan kerumitan, melacak keadaan semua trik UI mereka (tampilkan / sembunyikan kontrol, nonaktifkan / aktifkan kontrol , indikasi kesalahan, pesan kesalahan) di back-end (biasanya dengan melacak status dalam array).

Pembaruan : Dalam diagram, saya mengatakan bahwa Viewseharusnya referensi Model. Tidak. Anda harus meneruskan data ke Viewdari Modeluntuk mempertahankan sambungan yang longgar. masukkan deskripsi gambar di sini

Anthony Rutledge
sumber