質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

Q&A

解決済

2回答

4094閲覧

PDOでのFETCH_CLASSの際のテーブル結合について

Smar

総合スコア40

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

0グッド

1クリップ

投稿2016/08/01 09:40

編集2016/08/02 01:55

DTOとDAOの形からmapperやactiverecordなどの方に移行してみたい…と、
勉強も兼ねて以下のサイト様を参考にMapper形式でPDO(表現おかしかったらすいません…)にて各種SELECT、INSERTなどを試してみました。
http://blog.tojiru.net/article/277021312.html

そこで疑問になったのですが、
クラスでテーブルのカラムをデータモデルという形で持ち、PDOにてそのクラスをFETCH_CLASSとすることで制作する、というところは理解できたのですが、
テーブルを結合する際はどのような設計になるのが一般的なのでしょうか?

最初は何も考えずテストとして、IDが紐付いているような簡単なもので試してみました際にエラーとなり、よくわからずだったのですが、
「PDO::FETCH_CLASS」を「PDO::FETCH_ASSOC」とすれば良いのだなと解決すると同時に通常は全JOINパターン分のモデルクラスを用意するのだろうか…と疑問になりました。

データベースの内容によってはものすごいことになるのでは…と思ったりしたのですが、通常はそれでも準備するものなのでしょうか?
同じテーブルでも取得したい場合によって、この場合はこっちとJOIN、この場合は…みたいになったりすると思っています。

すいません、記述不足でした為簡単にソースを記載させて頂きます。
(ほぼ、上記サイトでのサンプルそのままですが…)

PHP

1// mapperのクラス. 2 3abstract class DataMapper { 4 5 protected $_pdo; 6 7 function __construct( PDO $pdo ) { 8 $this->_pdo = $pdo; 9 } 10 11 protected function _decorate( PDOStatement $stmt ) { 12 $stmt->setFetchMode( PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, static::MODEL_CLASS ); 13 return $stmt; 14 } 15 16} 17 18class MemberMapper extends DataMapper { 19 20 const MODEL_CLASS = 'Member'; 21 22 function findAll() { 23 $stmt = $this->_pdo->query('SELECT * FROM `member`'); 24 return $this->_decorate($stmt); 25 } 26 27 function findAllJoin() { 28 $stmt = $this->_pdo->query('SELECT member.name as a, member_2.name as b FROM `member` LEFT JOIN `member_2` ON member.id = member_2.member_id'); 29 return $this->_decorate($stmt); 30 } 31 32} 33 34// PDO接続用関数. 35function fncGetPDO( $env = null ) { 36 37 static $pdo = array(); 38 39 if( !isset( $pdo[$env] ) ) { 40 41 $conf = array('dsn'=>'mysql:dbname=xxxxx;host=localhost;charset=utf8','user'=>'xxxxx','pass'=>'xxxxx'); 42 43 $pdo[$env] = new PDO( 44 $conf['dsn'], 45 $conf['user'], 46 $conf['pass'], 47 array( 48 PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, 49 PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_CLASS, 50 ) 51 ); 52 53 } 54 55 return $pdo[$env]; 56 57} 58 59// …と別途テーブルのカラムに合わせたモデルクラスがあります。 60 61// 実行時. 62 63static $pdo; 64$pdo = fncGetPDO('local');// ここでのlocalは実際は条件分岐していますが、上記関数内に直接記載しました 65 66// こちらは問題無し. 67$member_mapper = new MemberMapper($pdo); 68$entries = $member_mapper->findAll()->fetchAll(); 69var_dump($entries); 70 71// こちらはエラー. 72$member_mapper = new MemberMapper($pdo); 73$entries = $member_mapper->findAllJoin()->fetchAll(); 74var_dump($entries);

ここでの問題は「FETCH_CLASS」としていることでモデルに含まれないものをJOINで取得しようとしているから、だと思った次第です。
(FETCH_ASSOCに書き換えることで実際に動作しました)

そこで、例えば結合するパターンのものを盛り込んでいく場合に一般的にはどのように構築していくものなのでしょうか…?
・PDO接続用関数をFETCH_CLASS用とFETCH_ASSOC用と2つ持つ?
・そもそもFETCH_CLASSを使わない?
…と、浅い考えに至ってしまった為、質問させて頂いております。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

ここでの問題は「FETCH_CLASS」としていることでモデルに含まれないものをJOINで取得しようとしているから、だと思った次第です

FETCH_CLASS ではモデルに含まれないものをJOINで取得できない、ということはありません。

ご質問のコードは

ほぼ、上記サイトでのサンプルそのまま

とのことですので、このコードでエラーが発生した原因は、おそらく Member クラスが継承しているDataModel クラスの実装にあります。

FETCH_CLASS を使用すると、指定したクラスに定義されていないプロパティに値をセットしようとした場合に __set メソッドが呼び出されます。
http://php.net/manual/ja/pdo.constants.php#pdo.constants.fetch-class

注意: 要求されたクラスにプロパティが存在しない場合は、マジックメソッド __set() がコールされます。

で、 DataModel クラスの __set メソッドを見てみると、31〜33行目でセットしようとしているプロパティの名前が $_schema という連想配列のキーに存在しない場合、 InvalidArgumentException を投げています。
https://github.com/hirak/pdo-datamapper-example/blob/master/lib/DataModel.php#L30

連想配列 $_schema'には、 Member クラスの方で member テーブルのカラム名に合わせて

php

1protected static $_schema = array( 2 'id' => parent::INTEGER 3 , 'name' => parent::STRING 4 , (その他) 5);

のように要素を格納しているはずです。

一方、ご質問のコードの findAll 関数と findAllJoin 関数それぞれの内部で実行しているSQL文を見比べると、取得するカラムの名称が相異なっています。
( findAll は id と name とその他。 findAllJoin の方は a と b )

そのため、 findAllJoin 関数を呼び出すと、連想配列 $_schema に a または b というキーが存在しないためにエラーが発生する(32行目で InvalidArgumentException が投げられる)、というわけです。

例えば結合するパターンのものを盛り込んでいく場合に一般的にはどのように構築していくものなのでしょうか…?

何が一般的かは私には分かりませんが、
マジックメソッドをうまく利用して、JOIN して増えたカラムに対応するプロパティやメソッドが setFetchMode で指定したクラスに
「あたかも最初から定義されていた」
かのように見せかけることは可能です。

実際、サンプルの DataModel クラスは、31〜33行を削除すればどのような SELECT文の結果も受け入れることが可能です。

しかし、yambejp 様のコメントにあるように、特にそうしたい事情がなければ、無理に FETCH_CLASS を使用する必要はないと思います。


FETCH_CLASS がどのように振る舞うか知りたい場合、もっと単純なプログラムで確認されることをお勧めします。
参考にされているコードは余計な処理が多すぎて、ある現象が FETCH_CLASS によるものか否かが判断しにくいからです。

例えば、以下のようなコードで SQL文や Member クラスの中身などをいろいろ変えながら、実行してみてください。

php

1<?php 2error_reporting(E_ALL); 3 4$pdo = new PDO('mysql:dbname=XXX;host=XXX', 'XXX', 'XXX'); 5$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 6 7// $stmt = $pdo->prepare('SELECT id, name FROM member'); 8$stmt = $pdo->prepare('SELECT member.name as a, member_2.name as b FROM member LEFT JOIN member_2 ON member.id = member_2.member_id'); 9 10$stmt->execute(); 11 12$stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Member'); 13 14var_dump($stmt->fetchAll()); 15 16// Memberクラスには何も定義していない 17class Member {}

投稿2016/08/03 10:58

KiyoshiMotoki

総合スコア4791

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Smar

2016/08/04 07:18

ご丁寧なコメントありがとうございます。 色々と頂いたサンプルにて試させて頂きました。 InvalidArgumentExceptionの件も気づきませんで、なるほど…でした。 大変勉強になりました。 ありがとうございます。
Smar

2016/08/04 07:53

追記で恐縮なのですが…お聞きしたいです。 ローカルのXAMPP環境にてテストを行っているのですが、 サンプル頂きました形でシンプルに書き、試しに throw new InvalidArgumentException( 'TEST' ); と単純に書いてみたところでは Fatal error: Uncaught exception 'InvalidArgumentException' with message 'TEST' in~ と表示されるのですが、 サンプルサイトでの__set内での場合、 なぜか ページは機能していません ~では現在このリクエストを処理できません。 HTTP ERROR 500 と表示されてしまいまして原因が不明です…。 throwの直前でexitした際に出ませんので間違いなく中には入ってきていてここでそのように動いていると思われます。 throwを使用した経験がなく、普通なことでしたらすいません…。 よろしければお願い致します。
KiyoshiMotoki

2016/08/05 07:34

それは私にも分かりません。 Smar様のプログラムは、スローされた例外をどこか別の箇所でキャッチしてエラーメッセージを書き換えているのかもしれません。 あるいは、XAMPP環境をお使いとのことなので、それぞれ異なる方法で実行している、と言うことはありませんか? 例えば、  ・私のプログラム -> phpコマンド  ・Smar様のプログラム -> WEBアプリケーションとして など。
Smar

2016/08/08 01:53

ご返信遅くなりまして申し訳ございません。 コメントありがとうございます。 無茶な質問すいませんでした。 ウェブサーバーにて試してみる、などやってみたいと思います。 ありがとうございましたm(_ _)m
guest

0

見た感じ

pdo->query('SELECT tbl_a.name as a_name, tbl_b.name as b_name FROM tbl_a LEFT JOIN tbl_b ON tbl_a.id = tbl_b.tbl_a_id');

「$pdo」とかいうオチでは?

たとえば以下のようにしてエラーを拾います

PHP

1try{ 2 $pdo = new PDO($dsn, $user,$password); 3 $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 4 $sql='SELECT tbl_a.name as a_name, tbl_b.name as b_name FROM tbl_a LEFT JOIN tbl_b ON tbl_a.id = tbl_b.tbl_a_id'; 5 $stmt = $pdo->query($sql); 6}catch(PDOException $e){ 7 die($e->getMessage()); 8}

投稿2016/08/01 11:20

yambejp

総合スコア114572

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Smar

2016/08/02 01:12

コメントありがとうございます。 すいません、エラーの解決法ではなかったのですが、中途半端にPHP記述したことで誤解を生んでしまいました…。 後ほど質問内容訂正したいと思います。 申し訳ございませんでした。
yambejp

2016/08/02 03:50

失礼しました。FETCH_CLASSの質問でしたね http://blog.tojiru.net/article/277021312.html にあるように、クラスを使うメリットは少なく無いと思いますが FETCH_ASSOCに慣れているとそこまでする必要はあるのか?と思う時もあります。 基本的にはグループで運用したり、特別な定義がしたいときは FETCH_CLASSを検討し、逆に特別な下準備などしたくない場合は FETCH_ASSOCで十分な気がします。
Smar

2016/08/03 05:20

コメントありがとうございます。 まさに欲しかったお言葉を頂いた気持ちです(苦笑)。 「そこまでする必要はあるのか?」とまさに思っていたところでしたので…。 もちろんカッチリした方が速度的には良いのかもですが…という気持ちでした。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問