LoginSignup
18
31

More than 3 years have passed since last update.

CodeIgniterメモ

Last updated at Posted at 2017-10-16

コントローラーやモデルを作る上での注意

クラス名は被らないようにすること(後々困ったりする ネームスペースを使用できるようにすれば多少はましになるかも)
フォルダで分けた場合はプレフィックスをつける等

loadまでは問題なかったがメソッドを呼び出したところでメモリ不足になった。

同じ名前のクラスは作らない

プロファイラ出力

$this->output->enable_profiler(TRUE);

JSON出力

output後も処理は続くので注意

  • コントローラから使うとき
$this->output
    ->set_content_type('application/json')
    ->set_status_header(200)
    ->set_output(json_encode([]));
return;
  • コントローラから使うときまたは、コントローラから呼び出されるメソッドで使うとき
$this->output
    ->set_content_type('application/json')
    ->set_status_header(200)
    ->set_output(json_encode([]))
    ->_display();
exit(0);

APIの時はJSONでCSRFのエラーを出すようにする

APIの時はJSONでCSRFのエラーを出すようにExceptionsを拡張しようとしたところ$CInullになってしまう。

get_instance return null

ということで以下は動きません。

application\core\MY_Exceptions.php
defined('BASEPATH') OR exit('No direct script access allowed');
require_once BASEPATH.'core/Controller.php';

class MY_Exceptions extends CI_Exceptions {

    protected $CI;

    public function __construct()
    {
        $this->CI =& get_instance();
        parent::__construct();
    }

    public function show_error($heading, $message, $template = 'error_general', $status_code = 500)
    {
        if ($this->CI->uri->segment(1) === 'api')
        {
            // APIの場合JSONで返す
            $this->CI->output
                ->set_content_type('application/json')
                ->set_status_header($status_code)
                ->set_output(json_encode([
                    'message' => $message,
                    'csrf_token_name' => $this->CI->security->get_csrf_token_name(),
                    'csrf_hash' => $this->CI->security->get_csrf_hash()
                ]))->_display();
            exit(0);
        }

        // 通常
        return parent::show_error($heading, $message, $template, $status_code);
    }

    public function show_exception($exception)
    {
        if ($this->CI->uri->segment(1) === 'api')
        {
            // APIの場合JSONで返す
            $this->CI->output
                ->set_content_type('application/json')
                ->set_status_header(500)
                ->set_output(json_encode([
                    'message' => $this->CI->exception->getMessage(),
                    'csrf_token_name' => $this->CI->security->get_csrf_token_name(),
                    'csrf_hash' => $this->CI->security->get_csrf_hash()
                ]))->_display();
            exit(0);
        }

        // 通常
        return parent::show_exception($exception);
    }

}

とりあえず個別でnewしてもここについては問題なさそうなのでしてみる。
ここでの注意はCI_Securitynewするだけではhashが更新されるだけでクッキーには登録されないのでcsrf_set_cookieも行う必要がある。それを行わないと判定はクッキーのものとinputを比較しているので永遠に一致しない。

application\core\MY_Exceptions.php
defined('BASEPATH') OR exit('No direct script access allowed');

class MY_Exceptions extends CI_Exceptions {

    public function show_error($heading, $message, $template = 'error_general', $status_code = 500)
    {
        $uri = new CI_URI();
        if ($uri->segment(1) === 'api')
        {
            // APIの場合JSONで返す
            $output = new CI_Output();
            $security = new CI_Security();
            $security->csrf_set_cookie();
            $output
                ->set_content_type('application/json')
                ->set_status_header($status_code)
                ->set_output(json_encode([
                    'message' => $message,
                    'csrf_token_name' => $security->get_csrf_token_name(),
                    'csrf_hash' => $security->get_csrf_hash()
                ]))->_display();
            exit(0);
        }

        // 通常
        return parent::show_error($heading, $message, $template, $status_code);
    }

    public function show_exception($exception)
    {
        $uri = new CI_URI();
        if ($uri->segment(1) === 'api')
        {
            // APIの場合JSONで返す
            $output = new CI_Output();
            $security = new CI_Security();
            $security->csrf_set_cookie();
            $output
                ->set_content_type('application/json')
                ->set_status_header(500)
                ->set_output(json_encode([
                    'message' => $exception->getMessage(),
                    'csrf_token_name' => $security->get_csrf_token_name(),
                    'csrf_hash' => $security->get_csrf_hash()
                ]))->_display();
            exit(0);
        }

        // 通常
        return parent::show_exception($exception);
    }

}

API判定はリクエストヘッダに独自ヘッダ(X-から始まる)を付けたりして判定するのでも良いかも
X-Custom-Header: apiとか
$_SERVER['HTTP_X_CUSTOM_HEADER']で判定(CI_Inputをnewすると応答が返ってこなくなったのでinputは使えない)(CIが使える箇所では`$this->input->get_request_header('X-Custom-Header')で)

config値の取得

$this->config->item('base_url');

リクエストメソッド

リクエストパラメータ

http://codeigniter.jp/user_guide/3/libraries/input.html#CI_Input::post
http://codeigniter.jp/user_guide/3/libraries/input.html#CI_Input::get

アイテムが存在しない場合NULLを返す

XSS

入力を画面に出力する際は
xss_cleanではscriptタグは[removed]に置き換えたり有害と思われるものを削除したりHTMLエンティティ化を行ったりします。これで有害と思われなかったものは削除されたりHTMLエンティティ化をされないので HTMLインジェクション の対策は行われないのでhtmlspecialcharsを通すこと。

また、XSSのあるSVGの画像には有効ではないので管理された画像を使用する必要がある。

echo htmlspecialchars($this->input->get('html', true), ENT_QUOTES, $this->config->item('charset'));

getpostget_cookieの第二引数がxss_cleanをするかどうかになっている

xss_cleanのデフォルト値は$config['global_xss_filtering']に依存している

リクエストメソッド判定

method

  • POSTとPOST以外のページを同じコントローラのメソッドで実装する例
$data = [];
$data['csrf_token_name'] = $this->security->get_csrf_token_name();
$data['csrf_hash'] = $this->security->get_csrf_hash();

if ($this->input->method() !== 'post')
{
    $this->smarty->view('index.html', $data);
    return;
}

// POST時の処理

セグメント

パス

  • アプリケーション
APPPATH . '../public/uploads/';

バリデーション

$this->load->library('form_validation');
$this->form_validation->set_error_delimiters('', '');// デフォルトはpで囲われるけど無くしたい
$this->form_validation->set_rules(
    'password',
    'パスワード',
    'required|min_length[8]|regex_match[/^(?=.*?[A-Za-z])(?=.*?[0-9])(?=.*?[ -\/:-@\[-`\{-\~])/]',
    ['regex_match' => '半角英数記号を含めてください'],
);

getの場合

post以外のバリデーションは対象の配列をセットしたら良い
set_rulesの前に設定する必要があります

$this->form_validation->set_data($this->input->get());

配列バリデーション

$this->form_validation->set_rules('input_array[]', '配列', 'required');

画面にエラー表示と入力復帰を行う

const ERROR_KEY = 'error';

入力画面側コントローラ

$cache_prefix = 'login_';
$field_list = [
    'id',
    'password',
];
$field_list[] = self::ERROR_KEY;
foreach ($field_list as $field) {
    $cache_key = $cache_prefix . $field . '_' . 'ユーザーのID等';
    $cache = $this->cache->memcached->get($cache_key);
    if ($cache !== false) {
        $this->cache->memcached->delete($cache_key);
        $data[$field] = $cache;
    }
}

POST受け取り側コントローラ

$cache_prefix = 'login_';
$field_list = [
    'id',
    'password',
];

foreach ($this->input->post($field_list, true) as $field => $value) {
    if (isset($value)) {
        $this->cache->memcached->save($cache_prefix . $field . '_' . 'ユーザーのID等', $value);
    }
}

$error_key = $cache_prefix . self::ERROR_KEY . '_' . 'ユーザーのID等';
if ($this->form_validation->run() !== true) {
    // バリデーションエラー
    $this->cache->memcached->save($error_key, validation_errors());
    redirect('入力画面');
}

// 成功時削除
foreach ($this->input->post($field_list, true) as $field => $value) {
    if (isset($value)) {
        $this->cache->memcached->delete($cache_prefix . $field . 'ユーザーのID等');
    }
}

エラーを個別に判定する

$error_list = $this->form_validation->error_array();
// $error_list[フィールド名']でエラーメッセージが入っている配列が取得可能

CSRF対策

特定のページでcsrf protectionを無効にする方法

ファイルダウンロードをPOSTで行った際にそのページのまま再度ダウンロードを行うとcsrf protectionのエラーになってしまうので何とかする。

以下をconfig.phpに追加する

$config['csrf_exclude_uris'] = array(
    'download/execute',
);

キャッシュドライバ

$this->cache->memcached->get();

アイテムが存在しない場合、このメソッドは FALSEを返す。
NULLと間違えやすい。

ログ

404

エラーページに前のページに戻るリンクを追加する

views/errors/html/error_404.php
views/errors/html/error_general.php

内に以下を追加
<?php
if (isset($_SERVER['HTTP_REFERER']))
{
    echo '<p><a href="' . $_SERVER['HTTP_REFERER'] . '">back to previous page</a></p>';
}
?>

status code

http://codeigniter.jp/user_guide/3/general/common_functions.html?highlight=set_status_header#set_status_header

ダウンロード

http://codeigniter.jp/user_guide/3/helpers/download_helper.html

ファイルアップロード

http://codeigniter.jp/user_guide/3/libraries/file_uploading.html

formタグにenctype="multipart/form-data"は忘れずにつけてください。

input type fileは $_POST ではなく $ _FILES に格納されるため、以下では取得できません。

$this->input->post('file_field_name')

複数ファイルには対応していない様子でした。(アップロードするファイルが選択されていません。となる)

$config['upload_path'] = APPPATH . 'uploads/';
$config['file_name'] = 'image';// 拡張子を省略すると元々の拡張子で保存してくれます。
$config['allowed_types'] = 'jpg|png';
$config['overwrite'] = true;

$this->load->library('upload', $config);
// アップロード
if ( ! $this->upload->do_upload('file_field_name'))
{
    throw new RuntimeException($this->upload->display_errors());
}

ファイル名をそのまま使う場合は$config['file_name']を省略します。
省略して後々取得したい場合は$this->upload->data('file_name')等を使って取得できます。

http://codeigniter.jp/user_guide/3/libraries/file_uploading.html#CI_Upload::data

ディレクトリ・ファイルの削除

http://codeigniter.jp/user_guide/3/helpers/file_helper.html#delete_files

メール

http://codeigniter.jp/user_guide/3/libraries/email.html

Cookie

config/config.php
$config['cookie_secure']    = FALSE;
$config['cookie_httponly']  = true;

httponlyはJavaScriptからセッションIDを取得できないようにするのでtrueにしておくset_cookieの引数でも設定できるけど。

http://codeigniter.jp/user_guide/3/helpers/cookie_helper.html

$this->load->helper('cookie');
set_cookie('name', '名前', $expire_second);
get_cookie('name', true);

クッキーの値又は見つからない場合は NULL

1回のアクセス内でセットと同時に取得はできませんでした。
cookieヘルパーはCI_Input::set_cookie()のエイリアスということなので
$this->inputに反映されないのかもしれません。

DB

クエリビルダクラスリファレンス

結果の取得の方法

http://codeigniter.jp/user_guide/3/database/results.html#CI_DB_result::result

// array [object]
$result = $this->db
    ->where('name', $name)
    ->get($table_name)
    ->result();
// object|null
$result = $this->db
    ->where('name', $name)
    ->get($table_name)
    ->row();

http://codeigniter.jp/user_guide/3/database/query_builder.html#CI_DB_query_builder::insert

$this->db->insert($table_name, $set_array);
$result = $this->db->affected_rows();
if ($result === 1)
{
    $insert_id = $this->db->insert_id();
}

http://codeigniter.jp/user_guide/3/database/db_driver_reference.html?highlight=affected_rows#CI_DB_driver::affected_rows
http://codeigniter.jp/user_guide/3/database/helpers.html#id2

カウント

https://codeigniter.jp/user_guide/3/database/query_builder.html#id6

$this->db->where('name', $name)->count_all_results($table_name);

SELECT count を使用しているわけではない
以下の方が良さそう。

$result = $this->db
    ->select('count(*) as count')
    ->where('name', $name)
    ->get($table_name)
    ->row();
$count = $result->count;
トランザクション
if ($this->db->trans_begin())
{
    try
    {
        $this->db->trans_commit();
    }
    catch (\Throwable $error)
    {
        $this->db->trans_rollback();
    }
}
SQLのエスケープ
  • クエリービルダ

setやinsertの第三引数をデフォルトにしておけばエスケープされるので気にしなくてよい。

  • プリペアドステートメント(CIではサポートされていない)のようなことをする

クエリバインディングを使うとエスケープは自動でやってくれる

$sql = "SELECT * FROM some_table WHERE id IN ? AND status = ? AND author = ?";
$this->db->query($sql, [[3, 6], 'live', 'Rick']);
プリペアドステートメントのようなsqlを発行しないのでログがきれいになる
SELECT * FROM some_table WHERE id IN (3,6) AND status = 'live' AND author = 'Rick'
  • SQLを直接書く

変数を用いる場合はエスケープすること

$this->db->escape_str();
$this->db->escape();

escape()はシングルクォートで囲まれてしまうので使い勝手が悪いです。

$sql = 'SELECT * FROM `table_name` WHERE `name` = ' . $this->db->escape_str($name) . ';';
$query = $this->db->query($sql);
// object[]
$result = $query->result();

http://codeigniter.jp/user_guide/3/database/queries.html#id7

クエリを確認したい場合
$this->db->last_query()

最後に実行されたクエリの文字列を返します

$search_bigram = "あい いう";
$search_bigram = $this->db->escape_str($search_bigram);
// object[]
$result = $this->db
    ->where('match(`bigram_contents`) against(' . "'" . '+"' . $search_bigram . '" in boolean mode' . "'" . ')');
    ->get($table_name)
    ->result();

var_dump($this->db->last_query());
"SELECT * FROM `table_name` WHERE match(`bigram_contents`) against('+"あい いう" in boolean mode');"
クエリービルダーでの注意

where_inを使う際には第二引数の配列には必ず空配列以外を渡すこと。
where_inの前に配列のチェックする必要があります。
http://codeigniter.jp/user_guide/3/database/query_builder.html#CI_DB_query_builder::where_in

バッチ実行

  • Batchコントローラを作成し、testメソッドを作成

  • testメソッドの最初には以下を追加してCLIからのみ実行を許可(CLI で実行する)

// ブラウザからのアクセスは禁止
if ( ! is_cli())
{
    show_404();
}
  • 次のコマンドでバッチの実行が可能
CI_ENV=production php <application root path>/index.php batch test

ENVIRONMENT 定数

$_SERVER['CI_ENV']で環境を判定しているのでCI_ENVの設定が必要

18
31
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
31