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

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

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

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

Q&A

解決済

1回答

946閲覧

承継した子クラスで親クラスのメソッドを呼び出したい

pegy

総合スコア243

PHP

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

0グッド

2クリップ

投稿2021/06/25 16:21

下記のようにpdo_connectの親クラスに存在するクラスを 承継した子クラスをインスタンス化して呼び出したいのですが、うまく実装することができません。調べてみると「子クラスのメソッドの中でparent変数を使い、親クラスのメソッドが呼べる」ことが
わかったため、pdo_connect_exeのとおり実装しました。

そして、main.phpにおいて$pdo->select($sql);として結果、以下のerrorが出力されました。
Declaration of pdo_connect_exe::select() should be compatible with pdo_connect::select($sql)

宣言された子クラスのメソッドは親クラスと互換性があるべきと解釈できるのですが、具体的にどの点において互換性が維持できていないのかわからず、お尋ねさせていただきたく投稿させていただきました。

よろしくお願い申し上げます。

php

1//class.php 2class pdo_connect 3{ 4 const DB_NAME=''; 5 const HOST=''; 6 const UTF='utf8'; 7 const USER=''; 8 const PASS=''; 9 10 public function pdo(){ 11 $dsn="mysql:dbname=".self::DB_NAME.";host=".self::HOST.";charset=".self::UTF; 12 $user=self::USER; 13 $pass=self::PASS; 14 try{ 15 $pdo=new PDO($dsn,$user,$pass, 16 array( 17 PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, 18 PDO::ATTR_EMULATE_PREPARES => false, 19 ) 20 ); 21 }catch(Exception $e){ 22 echo 'error : ' .$e->getMessage(); 23 die(); 24 } 25 $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); 26 return $pdo; 27 } 28 public function select($sql){ 29 $hoge=$this->pdo(); 30 $stmt=$hoge->query($sql); 31 $items=$stmt->fetchAll(PDO::FETCH_ASSOC); 32 return $items; 33 } 34 35} 36class pdo_connect_exe extends pdo_connect 37{ 38 const DB_NAME='test_db'; 39 const HOST='localhost:XXXX'; 40 const UTF='utf8'; 41 const USER='root'; 42 const PASS='root'; 43 44 public function select(){ 45 parent::select(); 46 } 47 48}

php

1//main.php 2require_once("class.php") 3$pdo = new pdo_connect_exe(); 4$sql = "SELECT * FROM test_table"; 5$pdo->select($sql); 6コード

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

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

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

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

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

guest

回答1

0

ベストアンサー

オーバーライドしているメソッドは引数と戻り値が同じである必要があります。
Declaration of <クラス名>::<メソッド名> should be compatible with <クラス名>::<メソッド名>
サーバーのphpを7.0にした際に出る【Warning: Declaration of My_Walker::start_el〜】の対処方法

親側は引数に$sql(おそらくstring)をとり、戻り値にarrayが指定されるにも関わらず、子側は引数も戻り値もありません。

とりあえず動かすなら子のほうをこう

php

1 public function select($sql){ 2 return parent::select($sql); 3 }

ですが、今回のようなミスを防ぐためにも引数、戻り値に型宣言することを強くすすめます。

php

1//親 2 public function select(string $sql):array{ 3 $hoge=$this->pdo(); 4 $stmt=$hoge->query($sql); 5 $items=$stmt->fetchAll(PDO::FETCH_ASSOC); 6 return $items; 7 } 8 9//子 10 public function select(string $sql):array{ 11 return parent::select($sql); 12 }

簡易実装イメージ

php

1<?php 2<?php 3class p { 4 protected $con; 5 6 protected function execute(){ 7 echo $this->con; 8 } 9} 10 11class c1 extends p{ 12 function __construct(){ 13 $this->con = "aa"; 14 } 15 function ex(){ 16 $this->execute(); 17 } 18} 19class c2 extends p{ 20 function __construct(){ 21 $this->con = "bb"; 22 } 23 function ex(){ 24 $this->execute(); 25 } 26} 27 28$c1 = new c1(); 29$c2 = new c2(); 30$c1->ex(); 31$c2->ex(); 32 33

投稿2021/06/25 21:28

編集2021/06/26 09:07
m.ts10806

総合スコア80854

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

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

pegy

2021/06/25 22:16 編集

コメントをいただきありがとうございます。 他の言語で引数や戻り値の型を宣言していたのに、PHPは宣言しないんだと勉強していたのですが、扱いは同様で省略することができるということなのですが。おっしゃる通り、ミスを減らすために原則宣言する方針で参ろうと思います。 さて、 class pdo_connect { const DB_NAME=''; const HOST=''; const UTF='utf8'; const USER=''; const PASS=''; public function pdo(){ $dsn="mysql:dbname=".self::DB_NAME.";host=".self::HOST.";charset=".self::UTF; $user=self::USER; $pass=self::PASS; try{ $pdo=new PDO($dsn,$user,$pass, array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false, ) ); }catch(Exception $e){ echo 'error : ' .$e->getMessage(); die(); } //エラー表示の設定 $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); return $pdo; } // public function public function select(string $sql){ $hoge=$this->pdo(); $stmt=$hoge->query($sql); $items=$stmt->fetchAll(PDO::FETCH_ASSOC); return $items; } } class pdo_connect_exe extends pdo_connect { // 接続情報 const DB_NAME='test_db'; const HOST='localhost:XXXX'; const UTF='utf8'; const USER='root'; const PASS='root'; public function select(string $sql):array{ return parent::select($sql); } } を実行した結果なのですが、以下の通り、エラーが出力されます。 error : SQLSTATE[HY000] [2002] No such file or directory
pegy

2021/06/25 22:20 編集

ここで、あえて子クラスに継承させずに class pdo_connect { const DB_NAME=''test_db'; const HOST='localhost:8889'; const UTF='utf8'; const USER='root'; const PASS='root'; public function pdo(){ $dsn="mysql:dbname=".self::DB_NAME.";host=".self::HOST.";charset=".self::UTF; $user=self::USER; $pass=self::PASS; try{ $pdo=new PDO($dsn,$user,$pass, array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false, ) ); }catch(Exception $e){ echo 'error : ' .$e->getMessage(); die(); } $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); return $pdo; } public function select(string $sql){ $hoge=$this->pdo(); $stmt=$hoge->query($sql); $items=$stmt->fetchAll(PDO::FETCH_ASSOC); return $items; } // public function }
pegy

2021/06/25 22:26

とすると適切に実行することができます。ここで、失敗するケースで SQLSTATE[HY000] [2002] No such file or directoryが発生するということは 子クラス側でオーバーライドした親クラスのメソッドが新たに参照すべき const DB_NAME='test_db'; const HOST='localhost:XXXX'; const UTF='utf8'; const USER='root'; const PASS='root'; を取得していないように思えます。 オーバーライドしたメソッド public function select()が親クラスのpublic function pdo()で $dsn="mysql:dbname=".self::DB_NAME.";host=".self::HOST.";charset=".self::UTF; $user=self::USER; $pass=self::PASS; としてselfを指定していてもこれはオーバーライドした子クラス側の新たな const DB_NAME='test_db'; const HOST='localhost:XXXX'; const UTF='utf8'; const USER='root'; const PASS='root'; を指しているわけではないのでしょうか?
pegy

2021/06/25 22:27

検証した結果も記載したため長文となり申し訳ございません。よろしくお願いお申し上げます。
m.ts10806

2021/06/25 22:47 編集

親クラスから小クラスを参照はできません。 constのように定数だと個別に設定できないので小クラスのほうを廃し、親クラスのほうをprotected変数にして小クラス(のコンストラクタ)から親クラスの変数に代入してください。
m.ts10806

2021/06/25 22:47

>扱いは同様で省略することができるということなのですが PHP7以降の新機能なのでむしろ「元々できなかったのが型宣言できるようになった」というのが正しいです(PHPマニュアル参照)
pegy

2021/06/26 00:20

。今更ですが、アイコンをよくみるとゾウさんがいるので、あのm.tsさんだったのですね。早朝からコメントありがとうございます。 親クラスの変数を protected $DB_NAME=''; protected $HOST=''; protected $UTF='utf8'; protected $USER=''; protected $PASS=''; とまではしたのですが、 >小クラス(のコンストラクタ)から親クラスの変数に代入してください。 がどうしてもわかりませんでした、、今リファレンス(https://www.php.net/manual/ja/language.oop5.basic.php)も読んでいるのですが、親クラスの変数に代入する方法としてどのあたりを参照すべきでしょうか? また、そもそも論になるのですが、今回初めてPHPでクラスを勉強してみようと思い、その起点がよく利用するPDOのクラス化でした。 今素人なりの発想としては、親クラスでできるだけ必要な処理をまとめて、子クラスで利用場面を分けることを想定しております。例えば、今回で言えば、接続先が変わり処理することが考えられるため、接続先については子クラス側で指定して、select/update/insert into/ delete関連のメソッドは親クラスに設置しようと考えました。 1.実務的にアプローチとしてそもそも「おかしい」や「変」なところはございますでしょうか? 2.また、今回の例はPDOを使っているにもかかわらず、練習段階なので、prepare()を使ってプレースホルダを利用していないですが、これを実装しようと思った場合以下では当然機能しません。 public function select(string $sql){ $hoge=$this->pdo(); $stmt=$hoge->query($sql); $items=$stmt->fetchAll(PDO::FETCH_ASSOC); return $items; } これを例えば、親クラスのメソッドとして // SELECT * FROM test_table WHERE id =:id AND name =:nameを想定 public function select(string $sql, array $col, array $ph){ $hoge=$this->pdo(); foreach ($col as $key){ $condition .= $key."=:".$key." AND" } $condition = substr($condition, 0, -3) //最後のANDを消す $stmt=$hoge->prepare($sql." ".$condition ); foreach($ph as $key =>$val){ $stmt->bindValue(":".$col[$key], $val, PDO::PARAM_INT); } $stmt->(execute) $items=$stmt->fetchAll(PDO::FETCH_ASSOC); return $items; } $test = new pdo_connect_exe(); $test ->select("SELECT * FROM test_table WHERE",["id","name"],[1,"yamada"]) は無理があるでしょうか?WHERE句の数やどのカラムから値を取得したいのかが可変だったり、するので引数に配列を設定してみているのですが・・・・クラスを利用して、状況が変わるprepare()を実装する場合例えばこのような方法は、あまり合理的ではないでしょうか?
m.ts10806

2021/06/26 01:31

一個ずつ整理してください。 子でコンストラクタを設ける 子から親のフィールドを参照する。
m.ts10806

2021/06/26 01:44

>クラスでできるだけ必要な処理をまとめて、子クラスで利用場面を分けることを想定しております それは悪くない発想です。 動的となる部分を子側で定義してあげるわけですよね。 >で言えば、接続先が変わり処理することが考えられるため それはもはや環境自体が違うのでは? 同じアプリ内でDBが違うという事態はあまりないです。環境が違うのであれば設定ファイルに外だしされたほうが良いです。 今回のようなケースで該当するとすれば、テーブル名や主キー、デフォルトのソート条件などになるのでは。 >実務的にアプローチとして 実務のことは実務に入らないとわかりませんし、こういう場に現場のことが出ることはないです(秘密保持とかありますしね) プロジェクトやフレームワーク次第なので「こうじゃなきゃいけない」というのはないです。 ルールは現場次第。 例えば同じ文字がでてきたときの変数を使う基準や、メソッド化の行数など決められているところもあるでしょうし、「既存のコード見てまねしろ」というところもあるでしょうし。 業務で1から入ったばかりの人が作るということはありえないので、ひとまず同様の処理を繰り返すし書かないように配慮できれば良いのではと。 古いシステムでは何回も同じこと書いてたりというのは平気であります。でも要件を満たしていて試験もしっかり行われ、規模が大きいのでほとんどリファクタリングに手は出しません。 規模がそこまででなくても一度リリースしてしまえば内部構造を変更するところまではしませんね。試験も全部やり直しですから。
pegy

2021/06/26 02:12

ご返信ありがとうございます。 コンストラクタの使い方は読み込まないとわからないため、今晩子供が寝静まった時分に頑張って調べてみます。今さわりを調べてましだがら実力では一筋縄で解決できなそうで。 実務的は表現が不適切でした。仮に上記示したものは、引細かいコードは別として、考え方そのもの、つまり複雑なprepareに代入すべきSQL文に対応するために、 sql前半と後半の文字列を分けて配列を利用した引数を3つに分けて、やる方法は違和感はありますか?
m.ts10806

2021/06/26 02:34

コンストラクタすらちゃんと理解せずに継承組み込むのは自殺行為に等しいです。 クラスの基礎 はじめからきちんとおさえてください。 >sql前半と後半の文字列を分けて配列を利用した引数を3つに分けて、やる方法は違和感はありますか? こちらについては「INPUTに対して想定のOUTPUTが得られているならいいんじゃない?」と応えます。 情報が増えたときの対応どうするか、どこまで汎用性があるか 拡張性含めて「やり方」を追うのではなく「必要要件」から「設計」してください。
pegy

2021/06/26 08:43 編集

ありがとうございます。子供が寝ている隙に少し勉強ができました。以下で実装することができました。 class pdo_connect { protected $DB_NAME=''; protected $HOST=''; protected $UTF='utf8'; protected $USER=''; protected $PASS=''; public function pdo(){ $dsn="mysql:dbname=".$this->DB_NAME.";host=".$this->HOST.";charset=".$this->UTF; $user=$this->USER; $pass=$this->PASS; try{ $pdo=new PDO($dsn,$user,$pass, array( // エラーが発生した時に、PDOExceptionを投げる PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // プリペアドステートメントのエミュレーションを無効化する。 PDO::ATTR_EMULATE_PREPARES => false, ) ); }catch(Exception $e){ echo 'error : ' .$e->getMessage(); die(); } //エラー表示の設定 $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); return $pdo; } // public function public function select(string $sql){ $hoge=$this->pdo(); $stmt=$hoge->query($sql); $items=$stmt->fetchAll(PDO::FETCH_ASSOC); return $items; } } class pdo_connect_exe extends pdo_connect { function __construct(){ $this->DB_NAME='test_db'; $this->HOST='localhost:XXXX'; $this->UTF='utf8'; $this->USER='root'; $this->PASS='root'; } } //main.php $pdo = new pdo_connect_exe(); $sql = "SELECT * FROM book_keeping_1"; $result = $pdo->select($sql);
pegy

2021/06/26 08:51 編集

コンストラクタでイニシャライズ(PHPではそのように呼ばないなら申し訳ございません)によってをインスタンス化するときに新たにプロパティを代入してあげるようなイメージなのかと思います。 ------------------------------------------------------------------------------------------------------------------ このコンストラクタがイニシャライズされるときにの$thisは 1.サブクラスを自信を指している 2.スーパークラスを指している 3.インスタンスを指している でその意味合いに迷うのですが、「親クラスの変数に代入」と表現されていたのでは2.なのかな?と思いました。 この周辺に関連して、クラスを承継するときにはサブクラスで利用するスーパークラスのメソッドやプロパティは「スーパークラスをただ参照しているだけなのか」、「サブクラスに実体としてコピーされているのか」、または「そもそもインスタンスで全てを含んだものとして利用しているのか」というのが直感的にまだわかっていないのですが、勉強しています。そうすれば自ずと$thisの指す先が理解できる気がします。 ------------------------------------------------------------------------------------------------------------------ また、承継されたクラスで親クラスのメソッドを使うときには public function select(string $sql):array{   return parent::select($sql); } を設定しなければならないのかと勘違いしていたのですが、そのまま承継しているの上記のように $pdo = new pdo_connect_exe(); $sql = "SELECT * FROM book_keeping_1"; $result = $pdo->select($sql); で実装できるので、不要でした。 メソッドを含めて承継しているのに、なんでparent::のように親クラスのメソッドを明示的に引き継ぐケースなんてあるんだと思ったりしますが、ここはもう少し勉強してみます。
pegy

2021/06/26 08:55

>こちらについては「INPUTに対して想定のOUTPUTが得られているならいいんじゃない?」と応えます。 情報が増えたときの対応どうするか、どこまで汎用性があるか 拡張性含めて「やり方」を追うのではなく「必要要件」から「設計」してください。 この点もアドバイスありがとうございます。これまでも、うまく設計できているつもりがああ、やっぱりやってみると汎用性や拡張性がなく、冗長なコードを書いてしまったという経験があるので、「必要要件」から「設計」を念頭におき失敗をしながら学んでみようと思います。
pegy

2021/06/26 09:01

まだまだご質問できるレベルではないのにご回答いただきありがとうございました。 深く御礼申し上げます。よろしくお願い申し上げます。
m.ts10806

2021/06/26 09:08

いつの間にか解決されてしまってましたが、簡易実装イメージ作りました。 ご参考まで。 引数も返り値もないので型宣言入れてませんが、 「子で定義した値を親のフィールドにセットして親で参照する」サンプルです。
pegy

2021/06/26 10:18

ありがとうございます。んんん〜例えば function __construct(){ $this->con = "aa"; } というのはやはり、親クラスに代入して親クラスでセットする行為なのですね。クラスが動作しているのが親側というのが承継の手続とすれば、直感的に混乱していますが、落ち着いて整理してみます。
m.ts10806

2021/06/26 11:06

>、親クラスに代入して親クラスでセットする行為なのですね。 いいえ。親クラスのフィールドを子クラスでセットしています。
pegy

2021/06/26 16:12

ありがとうございます。そのような意味合いであれば理解することができました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.47%

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

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

質問する

関連した質問