Symfoware

Symfowareについての考察blog

PHP クライアントに応答した後、http connectionを閉じて重い処理を継続する

webサーバーへのリクエストが行われた時、メール送信や画像変換など
時間のかかる処理を行う場合、一旦レスポンスを返してからじっくり処理したい。

exec("nohup php background.php > /dev/null &");

を実行して、別プロセスに投げるのもよいですが、これだとwebリクエストとバッチ処理で
2つのphpプロセスが起動してしまうのがもったいない気がします。


Connection: close



レスポンスを書き込んだ後、とっとと接続を閉じればいいんじゃないかと思い調べていると、
こちらのページに行き着きました。

close a connection early
How to kill an HTTP connection and continue processing (PHP and Apache2)

「Connection: close」をヘッダーに出力すれば良いようです。

サンプルはこんな感じになりました。


  1. <?php
  2. ob_start();
  3. echo 'ok';
  4. header("Connection: close");
  5. header("Content-length: " . (string)ob_get_length());
  6. ob_end_flush();
  7. ob_flush();
  8. flush();
  9. sleep(10);
  10. file_put_contents('debug.txt', 'fin');



ブラウザでアクセスすると、すぐに「ok」と表示されます。
10秒後、サーバーにdebug.txtファイルが出力されました。

プログラムからのアクセスも、すぐに応答が得られます。
こんなPythonプログラムで試してみました。


  1. # -*- coding:utf-8 -*-
  2. import urllib2
  3. url = 'http://192.168.1.102:8000/'
  4. request = urllib2.Request(url)
  5. # 送信実行
  6. response = urllib2.urlopen(request)
  7. ret = response.read()
  8. print('Response:', ret)



実行すると一瞬で応答が得られます。


$ python sample.py
('Response:', 'ok')



これは使えそう。



【参考URL】

close a connection early
How to kill an HTTP connection and continue processing (PHP and Apache2)

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2018/06/28(木) 21:52:49|
  2. PHP
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

FuelPHP コントローラーのafterで共通処理を記載する

例えばこんなコントローラーとビューがあるとします。

・sample.php


  1. <?php
  2. class Controller_Sample extends Controller {
  3.     public function action_hoge() {
  4.         $data = [
  5.             'info_list' => [
  6.                 'hoge-1',
  7.                 'hoge-2'
  8.             ]
  9.         ];
  10.         return View::forge('sample/hoge', $data);
  11.     }
  12.     public function action_piyo() {
  13.         $data = [
  14.             'info_list' => [
  15.                 'piyo-1',
  16.                 'piyo-2'
  17.             ]
  18.         ];
  19.         return View::forge('sample/piyo', $data);
  20.     }
  21. }




・view/sample/hoge.php


  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <meta charset="utf-8">
  5.     <title>hoge!</title>
  6. </head>
  7. <body>
  8.     <h3>hoge</h3>
  9.     <ul>
  10.     <?php foreach($info_list as $info) { ?>
  11.         <li><?php echo($info); ?></li>
  12.     <?php } ?>
  13.     </ul>
  14. </body>
  15. </html>




・view/sample/piyo.php


  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <meta charset="utf-8">
  5.     <title>piyo!</title>
  6. </head>
  7. <body>
  8.     <h3>piyo</h3>
  9.     <ul>
  10.     <?php foreach($info_list as $info) { ?>
  11.         <li><?php echo($info); ?></li>
  12.     <?php } ?>
  13.     </ul>
  14. </body>
  15. </html>




http://[server]/sample/hoge

857_01.png


http://[server]/sample/piyo

857_02.png



両方のビューで共通の「info_list」という配列を使用しリストを表示しています。
双方、リストの末尾に同じ文字を追加したい。



after



afterを実装して実現してみます。
https://fuelphp.com/docs/general/controllers/base.html


afterの引数は、Viewオブジェクトになっています。
Viewに表示したいデータを追加。
戻り値はResponseオブジェクトでなくてはいけないので、parent::afterで変換しています。


  1. <?php
  2. class Controller_Sample extends Controller {
  3.     public function action_hoge() {
  4.         $data = [
  5.             'info_list' => [
  6.                 'hoge-1',
  7.                 'hoge-2'
  8.             ]
  9.         ];
  10.         return View::forge('sample/hoge', $data);
  11.     }
  12.     public function action_piyo() {
  13.         $data = [
  14.             'info_list' => [
  15.                 'piyo-1',
  16.                 'piyo-2'
  17.             ]
  18.         ];
  19.         return View::forge('sample/piyo', $data);
  20.     }
  21.     public function after($response) {
  22.         // この時点ではViewオブジェクト
  23.         Log::error(get_class($response));
  24.         if ($response instanceof View) {
  25.             Log::error('Viewです');
  26.         }
  27.         if (isset($response->info_list)) {
  28.             $response->info_list[] = 'show-all';
  29.         } else {
  30.             $response->info_list = ['show-all'];
  31.         }
  32.         // これでResponseオブジェクトとなる
  33.         $response = parent::after($response);
  34.         Log::error(get_class($response));
  35.         return $response;
  36.     }
  37. }




これで狙い通りの動作になりました。

857_03.png

857_04.png

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2018/04/23(月) 22:08:12|
  2. PHP
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

FuelPHP チュートリアル4 データベースアクセス

FuelPHPを触ってみています。
FuelPHP チュートリアル3 モデルの使い方

今回はデータベースアクセスについて調べていきます。


データベース接続の準備



データベースはPostgresqlを使用することにしました。
こちらでインストールした10.3を使用します。
Postgresql 10.3をUbuntu Server 16.04にインストールし、外部アクセスを許可する

また、検索テスト用に「users」テーブルを作成。
適当にデータを投入しておきました。


CREATE TABLE users (
id int,
name text,
email text
);

INSERT INTO users VALUES
(1, 'shiro', 'abc@example.com'),
(2, 'kuro', 'efg@example.com'),
(3, 'sora', 'hij@example.com'),
(4, 'umi', 'klm@example.com'),
(5, 'yama', 'nop@example.com');







接続ライブラリ



phpからpostgresqlに接続するためのライブラリをインストールしておきます。


$ sudo apt install php-pgsql




こんな接続サンプルを作成し、事前に接続できることを確認しておきました。


  1. <?php
  2. try {
  3.     $pdo = new PDO('pgsql:host=localhost;dbname=sample','pgadmin','P@ssw0rd');
  4. } catch (PDOException $e) {
  5.     exit($e->getMessage().PHP_EOL);
  6. }
  7. echo('ok'.PHP_EOL);






FuelPHPのデータベース設定ファイル



データベースの接続設定は
「fuel/app/config/development/db.php」
に記載します。

※「development」は、FUEL_ENVに指定した値により変化。
FuelPHPをnginx + php-fpm + Ubuntu Server 16.04の動作環境を構築する


・db.php


  1. <?php
  2. /**
  3. * The development database settings. These get merged with the global settings.
  4. */
  5. return array(
  6.     'default' => array(
  7.         'connection' => array(
  8.             'dsn'        => 'pgsql:host=localhost;dbname=sample',
  9.             'username' => 'pgadmin',
  10.             'password' => 'P@ssw0rd',
  11.         ),
  12.         'identifier' => '' /* for PostgreSQL */,
  13.     ),
  14. );




1つのデータベースにのみ接続する場合は、defaultの設定のみでOKです。
identifierの指定が必要な理由はこちら。
FuelPHP + Postgresqlで「SQLSTATE[42601]: Syntax error: 7 ERROR: syntax error at or near "`"」




Running queries



単純にクエリーを実行するには、DB::query('SQL文')を実行します。
execute()で結果を取得。
実行結果はforeachでそのままイテレートできます。


  1. <?php
  2. class Controller_Sample extends Controller {
  3.     public function action_index() {
  4.         // queryオブジェクトを生成
  5.         $query = DB::query('SELECT * FROM users');
  6.         // SQL文を実行し、結果を取得
  7.         $result = $query->execute();
  8.         foreach($result as $row) {
  9.             echo($row['id'] . ':' . $row['name'] . '<br/>');
  10.         }
  11.     }
  12. }




実行結果

856_01.png


「as_object」を指定すると、行データがオブジェクトで取得できます。


  1. <?php
  2. class Controller_Sample extends Controller {
  3.     public function action_index() {
  4.         // queryオブジェクトを生成
  5.         $query = DB::query('SELECT * FROM users');
  6.         // SQL文を実行し、結果を取得
  7.         // as_object()で行データを連想配列ではなくオブジェクトに
  8.         $result = $query->as_object()->execute();
  9.         foreach($result as $row) {
  10.             echo($row->id . ':' . $row->name . '<br/>');
  11.         }
  12.     }
  13. }




便利な機能として、as_array('キー名', '値')を指定し、値を取り出すことが出来ます。
キーのみ指定するとitemは行の連想配列。


  1. <?php
  2. class Controller_Sample extends Controller {
  3.     public function action_index() {
  4.         // queryオブジェクトを生成
  5.         $query = DB::query('SELECT * FROM users');
  6.         // SQL文を実行し、結果を取得
  7.         // as_arrayでキーをidに
  8.         $result = $query->execute()->as_array('id');
  9.         foreach($result as $id => $row) {
  10.             echo($id . ':' . $row['name'] . '<br/>');
  11.         }
  12.     }
  13. }




キーと値を指定するパターン。


  1. <?php
  2. class Controller_Sample extends Controller {
  3.     public function action_index() {
  4.         // queryオブジェクトを生成
  5.         $query = DB::query('SELECT * FROM users');
  6.         // SQL文を実行し、結果を取得
  7.         // as_arrayでキーをidに
  8.         $result = $query->execute()->as_array('id', 'name');
  9.         foreach($result as $id => $name) {
  10.             echo($id . ':' . $name . '<br/>');
  11.         }
  12.     }
  13. }





SQL文を直接記載するのではなく、
select('フィールド名')
from('テーブル名')
where('フィールド名', '値')
のようにチェーンメソッドで記載してクエリーを組み立てることが出来ます。

例として、id=3のデータのnameとemailフィールドを検索してみます。


  1. <?php
  2. class Controller_Sample extends Controller {
  3.     public function action_index() {
  4.         // queryオブジェクトを生成
  5.         $query = DB::select('name', 'email')->from('users')->where('id', 3);
  6.         // SQL文を実行し、結果を取得
  7.         $result = $query->execute();
  8.         foreach($result as $row) {
  9.             var_export($row);
  10.         }
  11.     }
  12. }



856_02.png


where('フィールド名', '演算子', '値')
という指定も行えます。
idが2と3のデータを検索してみます。


  1. <?php
  2. class Controller_Sample extends Controller {
  3.     public function action_index() {
  4.         // queryオブジェクトを生成
  5.         $query = DB::select('name', 'email')->from('users')->where('id', 'in', [2, 3]);
  6.         // SQL文を実行し、結果を取得
  7.         $result = $query->execute();
  8.         foreach($result as $row) {
  9.             var_export($row);
  10.             echo('<br/>');
  11.         }
  12.     }
  13. }



856_03.png


もちろん、複数whereを指定することが可能です。


  1. <?php
  2. class Controller_Sample extends Controller {
  3.     public function action_index() {
  4.         // queryオブジェクトを生成
  5.         $query = DB::select('name', 'email')->from('users')
  6.             ->where('id', 'in', [2, 3])
  7.             ->where('name', 'kuro');
  8.         // SQL文を実行し、結果を取得
  9.         $result = $query->execute();
  10.         foreach($result as $row) {
  11.             var_export($row);
  12.             echo('<br/>');
  13.         }
  14.     }
  15. }





orderby,limit,offset



oderbyやlimit,offsetを指定してみます。


  1. <?php
  2. class Controller_Sample extends Controller {
  3.     public function action_index() {
  4.         // queryオブジェクトを生成
  5.         $query = DB::select('id', 'name')->from('users')
  6.             ->order_by('id', 'desc')
  7.             ->limit(2)
  8.             ->offset(1);
  9.             
  10.         // SQL文を実行し、結果を取得
  11.         $result = $query->execute();
  12.         foreach($result as $row) {
  13.             var_export($row);
  14.             echo('<br/>');
  15.         }
  16.     }
  17. }




実行結果

856_04.png



Inserting



id=6のデータを登録してみます。
Fuelの機能を使用して追加しようとすると、


  1. <?php
  2. class Controller_Sample extends Controller {
  3.     public function action_index() {
  4.         list($insert_id, $rows_affected) = DB::insert('users')->set(array(
  5.             'id' => 6,
  6.             'name' => 'test',
  7.             'email' => 'test@example.com'
  8.         ))->execute();
  9.     }
  10. }



こんなエラーになります。


PDOException [ 55000 ]:
SQLSTATE[55000]: Object not in prerequisite state: 7 ERROR: lastval is not yet defined in this session




常にlastInsertIdを取得しようとしてエラー。
ざっと見た感じ、これを回避するオプションはなさそうなので、別のやり方を考えてみます。

浮かんだ案は、insert文を直接記載。
bindでデータを設定する方法。


  1. $query = DB::query("INSERT INTO users (id, name, email) VALUES (:id, :name, :email)");
  2. $query->bind('id', 6);
  3. $query->bind('name', 'test');
  4. $query->bind('email', 'test@example.com');



これだと、こんなエラーになります。


Error [ Error ]:
Cannot pass parameter 2 by reference



bindParam() に直接値を入れたらダメ

なるほど、一回変数に入れればよいのか。


  1. <?php
  2. class Controller_Sample extends Controller {
  3.     public function action_index() {
  4.         $id = 6;
  5.         $name = 'test';
  6.         $email = 'test@example.com';
  7.         $query = DB::query("INSERT INTO users (id, name, email) VALUES (:id, :name, :email)", 0);
  8.         $query->bind('id', $id);
  9.         $query->bind('name', $name);
  10.         $query->bind('email', $email);
  11.         $query->execute();
  12.         // 登録したデータを検索
  13.         $query = DB::select('id', 'name')->from('users')->where('id', 6);
  14.             
  15.         // SQL文を実行し、結果を取得
  16.         $result = $query->execute();
  17.         foreach($result as $row) {
  18.             var_export($row);
  19.             echo('<br/>');
  20.         }
  21.     }
  22. }



DB::queryの第二引数に「0」を指定しているのは、SQL文中に「INSERT」という文字があったら、
lastInsertIdを取得しようとしてしまうので、明示的に「クエリタイプ不明」を指定しています。

これでどうにかデータの登録が行えました。

856_05.png





更新と削除



DB::updateでデータを更新してみます。


  1. <?php
  2. class Controller_Sample extends Controller {
  3.     public function action_index() {
  4.         DB::update('users')
  5.             ->set([
  6.                 'name' => 'update_test',
  7.                 'email' => 'update_test@example.com'
  8.             ])
  9.             ->where('id', '=', 6)
  10.             ->execute();
  11.         // 登録したデータを検索
  12.         $query = DB::select('*')->from('users')->where('id', 6);
  13.             
  14.         // SQL文を実行し、結果を取得
  15.         $result = $query->execute();
  16.         foreach($result as $row) {
  17.             var_export($row);
  18.             echo('<br/>');
  19.         }
  20.     }
  21. }



856_06.png


DB::deleteでデータの削除が行えます。


  1. <?php
  2. class Controller_Sample extends Controller {
  3.     public function action_index() {
  4.         DB::delete('users')
  5.             ->where('id', '=', 6)
  6.             ->execute();
  7.         // 登録したデータを検索
  8.         $query = DB::select('*')->from('users')->where('id', 6);
  9.             
  10.         // SQL文を実行し、結果を取得
  11.         $result = $query->execute();
  12.         foreach($result as $row) {
  13.             var_export($row);
  14.             echo('<br/>');
  15.         }
  16.     }
  17. }


テーマ:プログラミング - ジャンル:コンピュータ

  1. 2018/04/22(日) 22:11:34|
  2. PHP
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

FuelPHP + Postgresqlで「SQLSTATE[42601]: Syntax error: 7 ERROR: syntax error at or near "`"」

FuelPHP 1.8.1 + PostgreSQL 10.3でクエリーを実行するとこんなエラーが発生しました。


Fuel\Core\Database_Exception [ 42601 (7) ]:
SQLSTATE[42601]: Syntax error: 7
ERROR: syntax error at or near "`" LINE 1:
SELECT `name`, `email` FROM `users` WHERE `id` = 3 ^ with query: "SELECT `name`, `email` FROM `users` WHERE `id` = 3"

COREPATH/classes/database/pdo/connection.php @ line 223




コンソールで生成されたクエリーを実行してもエラーになります。


sample=# SELECT `name`, `email` FROM `users` WHERE `id` = 3;
ERROR: syntax error at or near "`"
行 1: SELECT `name`, `email` FROM `users` WHERE `id` = 3;




MySQLだと、テーブル名やフィールド名を「`」でくくってエスケープするのですが、
PostgreSQLだと構文エラー。知らんかった。


FuelPHPでデータベース接続テスト - PostgreSQL編
こちらを参考に、データベースの接続定義に「identifier」の設定を追加。


  1. <?php
  2. /**
  3. * The development database settings. These get merged with the global settings.
  4. */
  5. return array(
  6.     'default' => array(
  7.         'connection' => array(
  8.             'dsn'        => 'pgsql:host=localhost;dbname=sample',
  9.             'username' => 'pgadmin',
  10.             'password' => 'P@ssw0rd',
  11.         ),
  12.         'identifier' => '' /* for PostgreSQL */,
  13.     ),
  14. );




この指定で「`」でエスケープされなくなります。


テーマ:プログラミング - ジャンル:コンピュータ

  1. 2018/04/22(日) 21:26:29|
  2. PHP
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

FuelPHP チュートリアル3 モデルの使い方

FuelPHPの使い方を調べています。
FuelPHP チュートリアル2 ビューの使い方

引き続き、モデルの使い方を調べてみます。
https://fuelphp.com/docs/general/models.html


Model



データの取得や加工などは、コントローラーではなくモデルに記載したほうが
他のコントローラーでも処理を使いまわせて便利です。

モデルは「fuel/app/classes/model」に配置します。

855_01.png


Modelを継承してSampleモデルを作成しました。


  1. <?php
  2. namespace Model;
  3. class Sample extends \Model {
  4.     public static function get_results() {
  5.         // 本来はデータベースにアクセスし、取得したデータを返す。
  6.         return [
  7.             ['id' => 1, 'name' => 'test1'],
  8.             ['id' => 2, 'name' => 'test2'],
  9.             ['id' => 3, 'name' => 'test3']
  10.         ];
  11.     }
  12. }




メソッドはstatic functionで定義するのがルールのようです。

コントローラーで使用するには、useで作成したモデルを指定。
作成したstaticメソッドを直接呼び出してやります。


  1. <?php
  2. use \Model\Sample;
  3. class Controller_Sample extends Controller
  4. {
  5.     public function action_test() {
  6.         // モデルから取得したデータを表示
  7.         $rows = Sample::get_results();
  8.         var_export($rows);
  9.     }
  10. }



855_02.png


次は実際にデータベースにアクセスし、データを取得してみます。

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2018/04/22(日) 17:30:07|
  2. PHP
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
次のページ