🔥

CodeIgniter4のCodeIgniter\Model (3)モデル内でのバリデーション

2021/12/14に公開

CodeIgniter Advent Calendar 2021

CodeIgniter4の CodeIgniter\Model でのバリデーションを使ってみましょう。

CodeIgniter4のチュートリアルのコードをモデル内でのバリデーションを使うように変更します。

目次

NewsModelの変更

CodeIgniter\Model$validationRules プロパティにバリデーションルールを設定します。

--- a/app/Models/NewsModel.php
+++ b/app/Models/NewsModel.php
@@ -10,6 +10,11 @@ class NewsModel extends Model
 
     protected $allowedFields = ['title', 'slug', 'body'];
 
+    protected $validationRules = [
+        'title' => 'required|min_length[3]|max_length[255]',
+        'body'  => 'required',
+    ];
+
     public function getNews($slug = false)
     {
         if ($slug === false) {

これで、保存時(insert(), update(), save())にバリデーションが自動的に実行されます。

Newsコントローラの変更

動作を確認するために、コントローラでのバリデーションは削除してみましょう。

$this->validate() を削除します。

--- a/app/Controllers/News.php
+++ b/app/Controllers/News.php
@@ -41,17 +41,19 @@ class News extends BaseController
     {
         $model = model(NewsModel::class);
 
-        if ($this->request->getMethod() === 'post' && $this->validate([
-            'title' => 'required|min_length[3]|max_length[255]',
-            'body'  => 'required',
-        ])) {
-            $model->save([
+        if ($this->request->getMethod() === 'post') {
+            $data = [
                 'title' => $this->request->getPost('title'),
                 'slug'  => url_title($this->request->getPost('title'), '-', true),
                 'body'  => $this->request->getPost('body'),
-            ]);
-
-            echo view('news/success');
+            ];
+            if ($model->save($data) === false) {
+                echo view('templates/header', ['title' => 'Create a news item']);
+                echo view('news/create', ['errors' => $model->errors()]);
+                echo view('templates/footer');
+            } else {
+                echo view('news/success');
+            }
         } else {
             echo view('templates/header', ['title' => 'Create a news item']);
             echo view('news/create');

create() メソッドは以下のようになります。

    public function create()
    {
        $model = model(NewsModel::class);

        if ($this->request->getMethod() === 'post') {
            $data = [
                'title' => $this->request->getPost('title'),
                'slug'  => url_title($this->request->getPost('title'), '-', true),
                'body'  => $this->request->getPost('body'),
            ];
            if ($model->save($data) === false) {
                echo view('templates/header', ['title' => 'Create a news item']);
                echo view('news/create', ['errors' => $model->errors()]);
                echo view('templates/footer');
            } else {
                echo view('news/success');
            }
        } else {
            echo view('templates/header', ['title' => 'Create a news item']);
            echo view('news/create');
            echo view('templates/footer');
        }
    }

検証に失敗すると、$model->save($data) が false を返します。

$model->errors() でエラーメッセージを取得できます。

ビューの変更

コントローラから渡された $errors を表示するように変更します。

--- a/app/Views/news/create.php
+++ b/app/Views/news/create.php
@@ -1,7 +1,13 @@
 <h2><?= esc($title) ?></h2>
 
 <?= session()->getFlashdata('error') ?>
-<?= service('validation')->listErrors() ?>
+<?php if (! empty($errors)): ?>
+    <div class="alert alert-danger">
+        <?php foreach ($errors as $field => $error): ?>
+            <p><?= $error ?></p>
+        <?php endforeach ?>
+    </div>
+<?php endif ?>
 
 <form action="/news/create" method="post">
     <?= csrf_field() ?>

これで、バリデーションエラーが表示されます。

$error をエスケープしていないことに注目してください。

これは、HTMLタグが使えるということと、エラーメッセージを動的に組み立てるとXSSが可能になるかも知れないということです。

ユーザーが変更可能な文字列を使い、エラーメッセージを動的に組み立てることはしないようにしてください。

バリデーションエラーの表示確認

記事作成フォームに何も入力せずに「Create news item」ボタンを押します。

バリデーションエラーが表示されました。

エラーメッセージの変更

エラーメッセージを変更してみましょう。

CodeIgniter\Model$validationMessages プロパティにエラーメッセージを設定します。

--- a/app/Models/NewsModel.php
+++ b/app/Models/NewsModel.php
@@ -15,6 +15,12 @@ class NewsModel extends Model
         'body'  => 'required',
     ];
 
+    protected $validationMessages = [
+        'title'        => [
+            'required' => 'タイトルは<strong style="color:#ff0000;">必須</strong>です。',
+        ],
+    ];
+
     public function getNews($slug = false)
     {
         if ($slug === false) {

エラーメッセージが変更されました。

どちらでバリデーションすべきか?

コントローラでのバリデーションとモデルでのバリデーション、どちらを使うべきでしょうか?

これは、どちらかを選ぶというものではありません。これらは用途が違います。

基本的にコントローラでは入力データについて必ずバリデーションをしてください。
外部からのデータをバリデーションなしで使うことは危険です。

モデルでのバリデーションはデータベースへの保存時におかしな値が保存されないようにするためのものです。必要があれば、設定してください。

参考

Discussion