CodeIgniter\Modelを使わない場合

CodeIgniter\Model

CodeIgniter4のチュートリアルのモデルは、以下のように CodeIgniter\Model を使っています。

<?php

namespace App\Models;

use CodeIgniter\Model;

class NewsModel extends Model
{
    protected $table = 'news';

    protected $allowedFields = ['title', 'slug', 'body'];

    public function getNews($slug = false)
    {
        if ($slug === false) {
            return $this->findAll();
        }

        return $this->where(['slug' => $slug])->first();
    }
}

CodeIgniter\Model は便利ですが、いろいろな機能が混ざっており、ちょっとわかりづらいです。

もともと、CodeIgniter3でのチュートリアルのモデルは、以下のようでした。

<?php

class News_model extends CI_Model
{
    public function __construct()
    {
        parent::__construct();

        $this->load->database();
    }

    public function get_news($slug = false)
    {
        if ($slug === false) {
            $query = $this->db->get('news');
            return $query->result_array();
        }

        $query = $this->db->get_where('news', ['slug' => $slug]);
        return $query->row_array();
    }

    public function set_news()
    {
        $this->load->helper('url');

        $slug = url_title($this->input->post('title'), 'dash', true);

        $data = [
            'title' => $this->input->post('title'),
            'slug'  => $slug,
            'text'  => $this->input->post('text')
        ];

        return $this->db->insert('news', $data);
    }
}

CodeIgniter3のように、クエリビルダーが使えればいいだけという場合には、CodeIgniter\Model を使わないという選択肢もあります。

CodeIgniter\Modelを使わない場合

いきなりですが、CodeIgniter\Model を使わないで NewsModel を書くと以下のようになります。

app/Models/NewsModel.php

<?php

namespace App\Models;

use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\ConnectionInterface;

class NewsModel
{
    /**
     * @var string
     */
    private $table = 'news';

    /**
     * @var ConnectionInterface
     */
    private $db;

    /**
     * @var BaseBuilder
     */
    private $builder;

    public function __construct(?ConnectionInterface $db = null)
    {
        if ($db === null) {
            $this->db = db_connect();
        } else {
            $this->db = $db;
        }

        $this->builder = $this->db->table($this->table);
    }

    public function getNews($slug = false)
    {
        if ($slug === false) {
            $query = $this->builder->get();
            return $query->getResultArray();
        }

        $query = $this->builder->getWhere(['slug' => $slug]);
        return $query->getRowArray();
    }

    public function save($data)
    {
        return $this->builder->insert($data);
    }
}

データベース接続

db_connect() 関数は「データベース接続」オブジェクトを返します。

            $this->db = db_connect();

引数なしで呼ぶと、データベース接続にはデフォルトの設定が使われます。

クエリビルダー

「データベース接続」の table() メソッドは、新しいクエリビルダーを返します。

        $this->builder = $this->db->table($this->table);

これで、クエリビルダーを使って、クエリを構築できます。

クエリービルダーは、最初にテーブル名を指定することと、メソッド名がcamelCaseに変わっていること以外は、CodeIgniter3のクエリビルダーとほとんど同じです。

            $query = $this->builder->get();
            return $query->getResultArray();
        $query = $this->builder->getWhere(['slug' => $slug]);
        return $query->getRowArray();
        return $this->builder->insert($data);

App\Models\Model

コンストラクタをモデルごとに毎回書くのは面倒くさいという場合は、抽象クラスにまとめましょう。

app/Models/Model.php

<?php

namespace App\Models;

use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\ConnectionInterface;

abstract class Model
{
    /**
     * @var string
     */
    protected $table;

    /**
     * @var ConnectionInterface
     */
    protected $db;

    /**
     * @var BaseBuilder
     */
    protected $builder;

    public function __construct(?ConnectionInterface $db = null)
    {
        if ($db === null) {
            $this->db = db_connect();
        } else {
            $this->db = $db;
        }

        $this->builder = $this->db->table($this->table);
    }
}

このクラスを継承してモデルを作成すれば、クエリビルダーの生成までは行ってくれます。

app/Models/NewsModel.php

<?php

namespace App\Models;

class NewsModel extends Model
{
    /**
     * @var string
     */
    protected $table = 'news';

    public function getNews($slug = false)
    {
        if ($slug === false) {
            $query = $this->builder->get();
            return $query->getResultArray();
        }

        $query = $this->builder->getWhere(['slug' => $slug]);
        return $query->getRowArray();
    }

    public function save($data)
    {
        return $this->builder->insert($data);
    }
}

参考

Tags: codeigniter, codeigniter4, database

CodeIgniter4入門 公式チュートリアル (3)DBへのデータの追加

CodeIgniter4入門 公式チュートリアル (2)DBデータの表示 の続きです。

データベースへの書き込みを実装します。

CSRFフィルターの設定

CSRF対策のための設定を少し変更します。

app/Config/Filters.php を以下のように変更してください。

--- a/app/Config/Filters.php
+++ b/app/Config/Filters.php
@@ -30,7 +30,7 @@ class Filters extends BaseConfig
     public $globals = [
         'before' => [
             // 'honeypot',
-            'csrf',
+            // 'csrf',
         ],
         'after' => [
             'toolbar',
@@ -47,7 +47,9 @@ class Filters extends BaseConfig
      *
      * @var array
      */
-    public $methods = [];
+    public $methods = [
+        'post' => ['csrf'],
+    ];

     /**
      * List of filter aliases that should run on any

全てのリクエストで csrf フィルターを有効にしていたものを、POSTメソッドのリクエストに対してのみ有効にするように変更しました。

フォームの作成

記事を書き込むためのビューファイル app/Views/news/create.php を作成します。

<h2><?= esc($title) ?></h2>

<?= session()->getFlashdata('error') ?>
<?= service('validation')->listErrors() ?>

<form action="/news/create" method="post">
    <?= csrf_field() ?>

    <label for="title">Title</label>
    <input type="input" name="title" /><br />

    <label for="body">Text</label>
    <textarea name="body" cols="45" rows="4"></textarea><br />

    <input type="submit" name="submit" value="Create news item" />
</form>

session() 関数は、セッションオブジェクトを返します。 session()->getFlashdata('error') は、セッションに保存されているフラッシュデータ error を返します。 これは、csrfフィルターがトークンの検証に失敗した場合に、エラーメッセージを表示します。

service() 関数は、CodeIgniter4のサービスロケータ(\Config\Services)へのラッパーです。 CodeIgniter4では、フレームワークのクラスはサービスとして、サービスロケータから取得できます。

service('validation') は、CodeIgniter4の Validation オブジェクトを返します。 Validation クラスの listErrors() メソッドは、検証エラーをHTMLのリストで返します。

csrf_field() 関数は、CSRF対策のためのトークンをフォームに追加します。

フォームを受け取るためのコントローラメソッドの追加

News コントローラにフォームを受け取るための create() メソッドを追加します。

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

コントローラの $this->validate() メソッドは、リクエストのデータを検証するための便利なメソッドです。

CodeIgniter\Model クラスの save() メソッドは、データを挿入または更新するメソッドです。今回は、プライマリキーの指定がないため全て挿入処理になります。

書き込み成功時のビューの追加

書き込みが成功した旨のメッセージを表示するための app/Views/news/success.php を作成します。

News item created successfully.

NewsModelの変更

NewsModel がデータをDBに保存できるように、$allowedFields プロパティに変更可能なカラム名を指定します。

--- a/app/Models/NewsModel.php
+++ b/app/Models/NewsModel.php
@@ -8,6 +8,8 @@ class NewsModel extends Model
 {
     protected $table = 'news';

+    protected $allowedFields = ['title', 'slug', 'body'];
+
     public function getNews($slug = false)
     {
         if ($slug === false) {

ルーティングの追加

News コントローラの create() メソッドへのルートを追加します。

--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -33,6 +33,7 @@ $routes->setAutoRoute(true);
 // route since we don't have to scan directories.
 $routes->get('/', 'Home::index');

+$routes->match(['get', 'post'], 'news/create', 'News::create');
 $routes->get('news/(:segment)', 'News::view/$1');
 $routes->get('news', 'News::index');
 $routes->get('(:any)', 'Pages::view/$1');

news/create にGETまたはPOSTメソッドでアクセスされた場合に、News::create() メソッドを呼び出します。

記事の追加

では、http://localhost/news/create にアクセスしてみましょう。

記事を入力します。

「Create news item」ボタンを押します。

書き込みに成功しました。

右下のプロファイラから、データベースクエリを確認してみます。

ニュースの一覧を表示してみます。

一番下に追加した記事が表示されています。

以上で公式チュートリアルの内容は完了しました。データベースからの読み込みと書き込み処理までが含まれています。

NewsコントローラとNewsModel

コントローラとモデルのコードは以下のようになっています。

app/Controllers/News.php

<?php

namespace App\Controllers;

use App\Models\NewsModel;

class News extends BaseController
{
    public function index()
    {
        $model = model(NewsModel::class);

        $data = [
            'news'  => $model->getNews(),
            'title' => 'News archive',
        ];

        echo view('templates/header', $data);
        echo view('news/overview', $data);
        echo view('templates/footer', $data);
    }

    public function view($slug = null)
    {
        $model = model(NewsModel::class);

        $data['news'] = $model->getNews($slug);

        if (empty($data['news'])) {
            throw new \CodeIgniter\Exceptions\PageNotFoundException('Cannot find the news item: ' . $slug);
        }

        $data['title'] = $data['news']['title'];

        echo view('templates/header', $data);
        echo view('news/view', $data);
        echo view('templates/footer', $data);
    }

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

        if ($this->request->getMethod() === 'post' && $this->validate([
            'title' => 'required|min_length[3]|max_length[255]',
            'body'  => 'required',
        ])) {
            $model->save([
                'title' => $this->request->getPost('title'),
                'slug'  => url_title($this->request->getPost('title'), '-', true),
                'body'  => $this->request->getPost('body'),
            ]);

            echo view('news/success');
        } else {
            echo view('templates/header', ['title' => 'Create a news item']);
            echo view('news/create');
            echo view('templates/footer');
        }
    }
}

app/Models/NewsModel.php

<?php

namespace App\Models;

use CodeIgniter\Model;

class NewsModel extends Model
{
    protected $table = 'news';

    protected $allowedFields = ['title', 'slug', 'body'];

    public function getNews($slug = false)
    {
        if ($slug === false) {
            return $this->findAll();
        }

        return $this->where(['slug' => $slug])->first();
    }
}

参考

Tags: codeigniter, codeigniter4, database

CodeIgniter4入門 公式チュートリアル (2)DBデータの表示

CodeIgniter4入門 公式チュートリアル (1)静的ページの表示 の続きです。

今回から、データベースを使います。

MySQLでのデータベースの準備

チュートリアル用のデータベースを作成します。

http://localhost:81/ のphpMyAdminにアクセスします。

データベースの作成

データベース ci4tutorial を作成します。

コマンドで作成する場合は、以下を実行します。

CREATE DATABASE `ci4tutorial` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_cs;

ユーザ dbuserci4tutorial への権限を付与します。以下のSQL文を実行します。

GRANT ALL PRIVILEGES ON `ci4tutorial`.* TO `dbuser`@`%`;

テーブルの作成

news テーブルを作成します。以下のSQL文を実行します。

CREATE TABLE news (
    id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    title VARCHAR(128) NOT NULL,
    slug VARCHAR(128) NOT NULL,
    body TEXT NOT NULL,
    PRIMARY KEY (id),
    KEY slug (slug)
);

データの挿入

初期データを挿入します。以下のSQL文を実行します。

INSERT INTO news VALUES
(1,'Elvis sighted','elvis-sighted','Elvis was sighted at the Podunk internet cafe. It looked like he was writing a CodeIgniter app.'),
(2,'Say it isn\'t so!','say-it-isnt-so','Scientists conclude that some programmers have a sense of humor.'),
(3,'Caffeination, Yes!','caffeination-yes','World\'s largest coffee shop open onsite nested coffee shop for staff only.');

挿入したデータを確認します。

CodeIgniter4のデータベース設定

.env ファイルのデータベース接続情報を以下のように設定します。 DB_DATABASEci4tutorial に変更します。

DB_DATABASE = ci4tutorial
DB_USERNAME = dbuser
DB_PASSWORD = dbpassword

database.default.hostname = database
database.default.database = "${DB_DATABASE}"
database.default.username = "${DB_USERNAME}"
database.default.password = "${DB_PASSWORD}"
database.default.DBDriver = MySQLi
database.default.DBPrefix =

NewsModelの作成

news テーブルにアクセスする NewsModel を作成します。

app/Models/NewsModel.php

<?php
namespace App\Models;

use CodeIgniter\Model;

class NewsModel extends Model
{
    protected $table = 'news';

    public function getNews($slug = false)
    {
        if ($slug === false) {
            return $this->findAll();
        }

        return $this->where(['slug' => $slug])->first();
    }
}

CodeIgniter\ModelfindAll() メソッドと first() メソッドを使い、news テーブルからデータを取得します。

Newsコントローラの作成

News コントローラを作成します。

app/Controllers/News.php

<?php
namespace App\Controllers;

use App\Models\NewsModel;
use CodeIgniter\Exceptions\PageNotFoundException;

class News extends BaseController
{
    public function index()
    {
        $model = model(NewsModel::class);

        $data = [
            'news'  => $model->getNews(),
            'title' => 'News archive',
        ];
        dd($data);

        echo view('templates/header', $data);
        echo view('news/overview', $data);
        echo view('templates/footer', $data);
    }

    public function view($slug = null)
    {
        $model = model(NewsModel::class);

        $data['news'] = $model->getNews($slug);

        if (empty($data['news'])) {
            throw new PageNotFoundException('Cannot find the news item: ' . $slug);
        }

        $data['title'] = $data['news']['title'];
        dd($data);

        echo view('templates/header', $data);
        echo view('news/view', $data);
        echo view('templates/footer', $data);
    }
}

CodeIgniter4の model() 関数は、モデルクラスをインスタンス化します。

CodeIgniter4の dd() 関数で $data の値を表示して確認できるようにしました。

ルーティング設定

前回、全てのルートを Pages コントローラで処理するように、ルーティング設定をしたため、現状だと News コントローラにアクセスできません。

そこで、News コントローラにアクセスするためのルートを追加します。

app/Config/Routes.php

--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -33,6 +33,8 @@ $routes->setAutoRoute(true);
 // route since we don't have to scan directories.
 $routes->get('/', 'Home::index');

+$routes->get('news/(:segment)', 'News::view/$1');
+$routes->get('news', 'News::index');
 $routes->get('(:any)', 'Pages::view/$1');

 /*

http://localhost/news にアクセスすると、CodeIgniter4の dd() メソッドで $data の値が表示されます。

データベースに登録したレコードの値が取得できていることがわかります。

http://localhost/news/elvis-sighted にもアクセスしてみましょう。

データベースとのやりとりを確認できたら、デバッグ用の dd() 関数を削除しておきます。

--- a/app/Controllers/News.php
+++ b/app/Controllers/News.php
@@ -15,7 +15,6 @@ class News extends Controller
             'news'  => $model->getNews(),
             'title' => 'News archive',
         ];
-        dd($data);

         echo view('templates/header', $data);
         echo view('news/overview', $data);
@@ -33,7 +32,6 @@ class News extends Controller
         }

         $data['title'] = $data['news']['title'];
-        dd($data);

         echo view('templates/header', $data);
         echo view('news/view', $data);

Viewファイルの作成

一覧ページ用の app/Views/news/overview.php を追加します。

<h2><?= esc($title) ?></h2>

<?php if (! empty($news) && is_array($news)): ?>

    <?php foreach ($news as $news_item): ?>

        <h3><?= esc($news_item['title']) ?></h3>

        <div class="main">
            <?= esc($news_item['body']) ?>
        </div>
        <p><a href="/news/<?= esc($news_item['slug'], 'url') ?>">View article</a></p>

    <?php endforeach ?>

<?php else: ?>

    <h3>No News</h3>

    <p>Unable to find any news for you.</p>

<?php endif ?>

CodeIgniter4の esc() 関数は、HTMLでのエスケープ処理をする関数です。 XSS脆弱性を作り込まないように、原則としてビューでは全ての変数をこの関数で処理します。

また、<a> タグの href 属性の値を出力する際には、第2引数にコンテキスト url を指定し、 esc($news_item['slug'], 'url') のように使います。

続いて、個別記事ページ用の app/Views/news/view.php を追加します。

<h2><?= esc($news['title']); ?></h2>
<p><?= esc($news['body']); ?></p>

これで、ビューファイルが全て用意できました。

表示の確認

http://localhost/news にアクセスしてみます。

記事の一覧が表示されました。

「View article」をクリックして、http://localhost/news/elvis-sighted にアクセスします。

個別の記事も表示されています。

CodeIgniter4入門 公式チュートリアル (3)DBへのデータの追加 へ続く。

参考

Tags: codeigniter, codeigniter4, database