【改訂版】PHP7で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計 / PHP Conference 2016 Revised

9f3a83db74bee75a64b5e6ed106a775c?s=47 Takuto Wada
December 15, 2016

【改訂版】PHP7で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計 / PHP Conference 2016 Revised

2016/12/15 @ PHPカンファレンス2016再演
https://saien.connpass.com/event/45318/

9f3a83db74bee75a64b5e6ed106a775c?s=128

Takuto Wada

December 15, 2016
Tweet

Transcript

  1. 4.

    class BugRepository { public static function findAll($params) { global $CONF;

    $pdo = new PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false ]); $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } } print_r(BugRepository::findAll([ 'assignedTo' => '12', 'status' => 'OPEN' ])); ஫ʰ42-ΞϯνύλʔϯʱͷͻͲ͍ ίʔυྫΛΞϨϯδͯ͠ॻ͍͍ͯ·͢ ɹͱ͋Δͱ͜Ζʹɺ͜Μͳίʔυ͕͋Γ·ͨ͠ # " %
  2. 5.

    $ php example.php Array ( [0] => Bug Object (

    [bug_id] => 842 [summary] => 保存処理でクラッシュする [date_reported] => 2016-10-26 ) [1] => Bug Object ( [bug_id] => 5150 [summary] => XMLのサポート [date_reported] => 2016-10-26 ) [2] => Bug Object ( [bug_id] => 6060 [summary] => パフォーマンスの向上 [date_reported] => 2016-10-26 ) ) ɹॲཧ͕੒ޭ͢Δͱɺ͜Μͳײ͡ɻ
  3. 7.

    public static function findAll($params) { global $CONF; $pdo = new

    PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false ]); $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } ⏰੍ݶ࣌ؒඵ ɹॲཧࣦഊͷݪҼʹͳΔՄೳੑ͕͋Δͷ͸Ͳͷߦ # " %
  4. 8.

    public static function findAll($params) { global $CONF; $pdo = new

    PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false ]); $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } # " % σʔλϕʔε઀ଓཱࣦ֬ഊ AVTSA AQBTTXEA౳Ωʔ໊͕มߋ͞Εͨ
  5. 9.

    public static function findAll($params) { global $CONF; $pdo = new

    PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false ]); $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } # " % ςʔϒϧ໊΍ΧϥϜ໊͕୭͔ʹมߋ͞Εͨ  ͜͜Ͱ σʔλϕʔε઀ଓΤϥʔ Fatal error: Call to a member function execute() on a non-object
  6. 10.

    public static function findAll($params) { global $CONF; $pdo = new

    PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false ]); $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } # " % QBSBNT͕OVMM QBSBNTͷΩʔ໊΍਺ͷෆҰக QBSBNTͷ஋͕จࣈྻʹม׵ෆೳ  ͜͜Ͱ σʔλϕʔε઀ଓΤϥʔ
  7. 11.

    public static function findAll($params) { global $CONF; $pdo = new

    PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false ]); $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } # " % #VHΫϥε͕ະఆٛ  ͜͜Ͱ σʔλϕʔε઀ଓΤϥʔ ˞ઃఆʹΑΔ
  8. 12.

    public static function findAll($params) { global $CONF; $pdo = new

    PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false ]); $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } ɹॲཧࣦഊͷݪҼʹͳΔՄೳੑ͕͋Δͷ͸Ͳͷߦ # " % ᶃ ᶄ ᶅ ᶆ ͑ͬɺ͜Μͳʹ͋Δͷ 
  9. 14.

    ɹࢲݟͰ͸QBSBNTͷΩʔ໊΍਺ͷෆҰக public static function findAll($params) { global $CONF; $pdo =

    new PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false ]); $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } QBSBNTͷΩʔ໊΍਺ͷෆҰக # " %
  10. 15.

    $ php example.php PHP Warning: PDOStatement::execute(): SQLSTATE[HY093]: Invalid parameter number:

    parameter was not defined in example.php Array ( ) ɹΩʔ໊΍਺Λؒҧ࣮͑ͯߦ͢ΔͱͲ͏ͳΔ ཪͰͬͦ͜Γܯࠂ͕ग़Δ͚ͩͳͷ  ۭ഑ྻ͕ฦͬͯ͘Δͷ  ਖ਼ৗܥͱݟ෼͚͕͔ͭͳ͘ͳ͍  
  11. 17.

    public static function findAll($params) { global $CONF; $pdo = new

    PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false ]); $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } ɹॲཧࣦഊͷݪҼʹͳΔՄೳੑ͕͋Δͷ͸Ͳͷߦ # " % ᶃ ᶄ ᶅ ᶆ ݱ࣮ੈք΁Α͏ͦ͜
  12. 27.

    public static function findAll($params) { if (is_null($params)) { throw new

    InvalidArgumentException('params should not be null'); } if (!is_array($params)) { throw new InvalidArgumentException('params should be an array'); } if (count($params) !== 2) { throw new InvalidArgumentException('params should be have exact two items'); } if (!array_key_exists('assignedTo', $params) || !array_key_exists('status', $params)) { throw new InvalidArgumentException('params should have key `assignedTo` and `status` only'); } if (!is_int($params['assignedTo'])) { throw new InvalidArgumentException('params[`assignedTo`] should be an integer'); } if (!is_string($params['status'])) { throw new InvalidArgumentException('params[`status`] should be a string'); } if (!in_array($params['status'], ['OPEN', 'NEW', 'FIXED'], true)) { throw new InvalidArgumentException('params[`status`] should be in `OPEN`,`NEW`,`FIXED`'); } global $CONF; $pdo = new PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false ]); ɹͨͩͻͨ͢ΒೖྗΛνΣοΫ͠Α͏ͱͨ͠Γ # " %
  13. 28.

    global $CONF; $dsn = $CONF['dsn'] ?? $CONF['ds'] ?? $CONF['dataSource']; $user

    = $CONF['usr'] ?? $CONF['user']; $password = $CONF['passwd'] ?? $CONF['password']; $pdo = new PDO($dsn, $user, $password, [ PDO::ATTR_EMULATE_PREPARES => false ]); $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $pdo->prepare($sql); $safeParams = [ 'assignedTo' => $params['assignedTo'] ?? $params['assigned_to'], 'status' => $params['status'] ?? 'OPEN', ]; $stmt->execute($safeParams); $className = class_exists('Bug') ? 'Bug' : 'BugModel'; return $stmt->fetchAll(PDO::FETCH_CLASS, $className); ɹෆਖ਼ͳೖྗ͕͋ͬͯ΋ࣗ෼ͰͳΜͱ͔͠Α͏ͱͨ͠Γ # " %
  14. 29.

    /** * 担当者, ステータスに合致する Bug を検索し、ヒットした全件を Bug オブジェクト の配列として返す。 *

    * @param array $params 格納した検索条件の連想配列。キー `assignedTo` にユーザID をintで, キー `status` にステータス文字列をstringで指定すること。キー、値それぞ れNULLは不可とする。 * @return Bug[] 検索結果を Bug オブジェクトにマッピングして返す。検索結果が0件 のときは空配列を返す。 */ public static function findAll($params) { global $CONF; $pdo = new PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false ]); ɹυΩϡϝϯτͰؒҧ͍΍͢͞Λิ͓͏ͱͨ͠Γ # " %
  15. 31.

    w ʮ๷ޚతϓϩάϥϛϯάʯͱ͸ɺ໰୊ൃੜΛ ࣄલʹ๷͝͏ͱ͍͏ίʔσΟϯάελΠϧ w Մಡੑͷߴ͍ίʔυͱద੾ͳ໋໊نଇ w શͯͷؔ਺ͷ໭Γ஋ΛνΣοΫ w σβΠϯύλʔϯͷ࠾༻ w

    ཁ͢Δʹɺྑࣝ͋Δ࣮ફͷੵΈॏͶͰ͋Δ w ๷ޚతϓϩάϥϛϯά͸ɺਖ਼͍͠ίʔυ࡞੒ ͷͨΊͷن཯ΛϓϩάϥϚ͕Ұ؏ͯ͠ద༻͢ ΔͨΊͷҰछͷίʔσΟϯάඪ४ ๷ޚతϓϩάϥϛϯά
  16. 35.

    public static function findAll(int $assignedTo, string $status) { if (is_null($params))

    { throw new InvalidArgumentException('params should not be null'); } if (!is_array($params)) { throw new InvalidArgumentException('params should be an array'); } if (count($params) !== 2) { throw new InvalidArgumentException('params should be have exact two items'); } if (!array_key_exists('assignedTo', $params) || !array_key_exists('status', $params)) { throw new InvalidArgumentException('params should have key `assignedTo` and `status` only'); } if (!is_int($params['assignedTo'])) { throw new InvalidArgumentException('params[`assignedTo`] should be an integer'); } if (!is_string($params['status'])) { throw new InvalidArgumentException('params[`status`] should be a string'); } if (!in_array($params['status'], ['OPEN', 'NEW', 'FIXED'], true)) { throw new InvalidArgumentException('params[`status`] should be in `OPEN`,`NEW`,`FIXED`'); } ɹܕએݴʹΑͬͯʮग़དྷ͍͍ͯ͜ͱ͚ͩΛग़དྷΔʯΑ͏ʹ 1 ) 1 
  17. 36.

    public static function findAll(int $assignedTo, string $status) { if (!in_array($status,

    ['OPEN', 'NEW', 'FIXED'], true)) { throw new InvalidArgumentException('params[`status`] should be in `OPEN`,`NEW`,`FIXED`'); } global $CONF; $pdo = new PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false ]); $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $pdo->prepare($sql); $stmt->bindValue(':assignedTo', $assignedTo, PDO::PARAM_INT); $stmt->bindValue(':status', $status, PDO::PARAM_STR); $stmt->execute(); return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } ɹ૝ఆ͠ͳ͚Ε͹ͳΒͳ͍ঢ়گ͕ݮͬͨ 1 ) 1  注: さらに堅くしたい場合は 呼び出し側ファイルで厳密な型チェックを有効にする declare(strict_types=1);
  18. 40.

    abstract class Enum { private $scalar; public function __construct($value) {

    $ref = new ReflectionObject($this); $constants = $ref->getConstants(); if (!in_array($value, $constants, true)) { throw new InvalidArgumentException("value [{$value}] is not defined"); } $this->scalar = $value; } final public function value() { return $this->scalar; } final public function __toString() { return (string)$this->scalar; } } ɹ)JSBLV͞Μͷ&OVN࣮૷Λ গ͠ΞϨϯδͯ͠ ࢖ͬͯΈΔ
  19. 41.

    final class Status extends Enum { const OPEN = 'OPEN';

    const NEW = 'NEW'; const FIXED = 'FIXED'; } $status = new Status(Status::OPEN); $status = new Status('OPEN'); // "InvalidArgumentException: value [HOGE] is not defined" $status = new Status('HOGE'); ɹ͋Β͔͡Ίఆٛ͞Εͨ஋͚ͩΛΠϯελϯεԽͰ͖Δ
  20. 42.

    public static function findAll(int $assignedTo, Status $status) { if (!in_array($status,

    ['OPEN', 'NEW', 'FIXED'], true)) { throw new InvalidArgumentException('params[`status`] should be in `OPEN`,`NEW`,`FIXED`'); } global $CONF; $pdo = new PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false ]); $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $pdo->prepare($sql); $stmt->bindValue(':assignedTo', $assignedTo, PDO::PARAM_INT); $stmt->bindValue(':status', $status->value(), PDO::PARAM_STR); $stmt->execute(); return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } ɹ૝ఆ͠ͳ͚Ε͹ͳΒͳ͍ঢ়گ͕͞Βʹݮͬͨ
  21. 46.

    class BugRepository { private $pdo; public function __construct(PDO $pdo) {

    $this->pdo = $pdo; } // 以下省略 } // 設定者 (DI コンテナ等) $pdo = new PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false, ]); $repo = new BugRepository($pdo); // 利用者 print_r($repo->findAll(12, new Status(Status::OPEN))); ɹ1%0ੜ੒ͱઃఆͷ੹຿Λ֎෦ʹग़͠ɺίϯετϥΫλͰड͚औΔ
  22. 48.

    public static function findAll($params) { if (is_null($params)) { throw new

    InvalidArgumentException('params should not be null'); } if (!is_array($params)) { throw new InvalidArgumentException('params should be an array'); } if (count($params) !== 2) { throw new InvalidArgumentException('params should be have exact two items'); } if (!array_key_exists('assignedTo', $params) || !array_key_exists('status', $params)) { throw new InvalidArgumentException('params should have key `assignedTo` and `status` only'); } if (!is_int($params['assignedTo'])) { throw new InvalidArgumentException('params[`assignedTo`] should be an integer'); } if (!is_string($params['status'])) { throw new InvalidArgumentException('params[`status`] should be a string'); } if (!in_array($params['status'], ['OPEN', 'NEW', 'FIXED'], true)) { throw new InvalidArgumentException('params[`status`] should be in `OPEN`,`NEW`,`FIXED`'); } global $CONF; if (!isset($CONF['dsn'])) { throw new LogicException('config key `dsn` not found'); } if (!isset($CONF['usr'])) { throw new LogicException('config key `usr` not found'); } if (!isset($CONF['passwd'])) { throw new LogicException('config key `passwd` not found'); } $pdo = new PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false ]); $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $pdo->prepare($sql); $stmt->execute($params); if (!class_exists('Bug')) { throw new LogicException('class `Bug` does not exist'); } return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } public function __construct(PDO $pdo) { $this->pdo = $pdo; } public function findAll(int $assignedTo, Status $status) { $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $this->pdo->prepare($sql); $stmt->bindValue(':assignedTo', $assignedTo, PDO::PARAM_INT); $stmt->bindValue(':status', $status->value(), PDO::PARAM_STR); $stmt->execute(); return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } ୈ෦·ͱΊ༧๷ʹউΔ๷ޚͳ͠
  23. 55.

    w ࠷దͳΤϥʔॲཧ͸Τϥʔ͕ൃੜͨ͠ιϑτ΢ΣΞ ͷछྨʹΑΓҟͳΔ w ਖ਼౰ੑͱ͸ɺෆਖ਼֬ͳ݁ՌΛܾͯ͠ฦ͞ͳ͍͜ͱΛ ҙຯ͢Δɻෆਖ਼֬ͳ݁ՌΛฦ͘͢Β͍ͳΒɺԿ΋ฦ ͞ͳ͍ํ͕·͠Ͱ͋Δ w ݎ࿚ੑͱ͸ɺιϑτ΢ΣΞͷ࣮ߦΛܧଓͰ͖ΔΑ͏ ʹखΛਚ͘͢͜ͱͰ͋ΔɻͦΕʹΑͬͯෆਖ਼͕֬݁

    Ռ͕΋ͨΒ͞ΕΔ͜ͱ͕͋ͬͯ΋͔·Θͳ͍ w ҆શੑ ΍ਖ਼֬ੑ Λॏࢹ͢ΔΞϓϦέʔγϣϯͰ ͸ɺݎ࿚ੑΑΓ΋ਖ਼౰ੑ͕༏ઌ͞ΕΔ܏޲ʹ͋Δ w ίϯγϡʔϚΞϓϦέʔγϣϯͰ͸ɺਖ਼౰ੑΑΓ΋ ݎ࿚ੑ͕༏ઌ͞ΕΔ܏޲ʹ͋Δ ݎ࿚ੑͱਖ਼౰ੑ
  24. 59.

    public function findAll(int $assignedTo, Status $status) { $sql = 'SELECT

    bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; if (($stmt = $this->pdo->prepare($sql)) !== false) { if ($stmt->bindValue(':assignedTo', $assignedTo, PDO::PARAM_INT) !== false) { if ($stmt->bindValue(':status', $status->value(), PDO::PARAM_STR) !== false) { if ($stmt->execute() !== false) { if (($bugs = $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class)) !== false) { return $bugs; } } } } } return false; } ɹ1%0Τϥʔ࣌ͷ໭Γ஋ GBMTF ΛνΣοΫ͢Δ # " % ೾ಈݓ NJYFEฦ͠
  25. 60.

    public function findAll(int $assignedTo, Status $status) { $sql = 'SELECT

    bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; if (($stmt = $this->pdo->prepare($sql)) === false) { $error = $this->pdo->errorInfo(); report_error('prepare: ' . $error[2]); return false; } if ($stmt->bindValue(':assignedTo', $assignedTo, PDO::PARAM_INT) === false) { $error = $stmt->errorInfo(); report_error('bindValue: ' . $error[2]); return false; } if ($stmt->bindValue(':status', $status->value(), PDO::PARAM_STR) === false) { $error = $stmt->errorInfo(); report_error('bindValue: ' . $error[2]); return false; } if ($stmt->execute() === false) { $error = $stmt->errorInfo(); report_error('execute: ' . $error[2]); return false; } if (($bugs = $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class)) === false) { $error = $stmt->errorInfo(); report_error('fetchAll: ' . $error[2]); return false; } return $bugs; } ɹ໭Γ஋ GBMTF ΛνΣοΫͯ͠ૣظϦλʔϯ # " % ஫SFQPSU@FSSPS͸ϩάʹग़ࣗ͢࡞ؔ਺ NJYFEฦ͠
  26. 64.

    public function findAll(int $assignedTo, Status $status) { $sql = 'SELECT

    bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $this->pdo->prepare($sql); $stmt->bindValue(':assignedTo', $assignedTo, PDO::PARAM_INT); $stmt->bindValue(':status', $status->value(), PDO::PARAM_STR); $stmt->execute(); return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } // 設定者 $pdo = new PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]); $repo = new BugRepository($pdo); ɹGBMTFͷ୅ΘΓʹ1%0&YDFQUJPO͕ൃੜ͢ΔΑ͏ʹͳΔ $ php example.php PHP Fatal error: Uncaught PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'date_reported' in 'field list' in /path/ to/example.php:63
  27. 71.

    public function findAll(int $assignedTo, Status $status) { assert($this->pdo->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_EXCEPTION);

    $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $this->pdo->prepare($sql); ɹൃੜͨ͠҉໧ͷલఏΛBTTFSUʹධՁࣜͰॻ͖ɺ໌ࣔ͢Δ あらたに PDO::ERRMODE_EXCEPTION に 依存するようになったことを assert で明示する
  28. 72.

    $ php example.php PHP Warning: assert(): assert($this->pdo->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_EXCEPTION) failed

    in /path/to/example.php on line 59 PHP Fatal error: Uncaught Error: Call to a member function bindValue() on boolean in /path/to/example.php:63 ɹͬͦ͘͞ද໌ೖΓͷίʔυΛ࣮ߦͯ͠ΈΔ ධՁ͕ࣜग़ͯΘ͔Γ΍͍͢ Ͱ΋ܯࠂ͕ग़Δ͚ͩͰɺམͪͳ͍  མͪͳ͍ͷͰͦͷઌͰผͷΤϥʔʹͳͬͨ  1 ) 1 
  29. 74.

    $ php example.php PHP Fatal error: Uncaught AssertionError: assert($this->pdo->getAttribute(PDO::ATTR_ERRMODE) ===

    PDO::ERRMODE_EXCEPTION) in /path/to/example.php:59 Stack trace: #0 /path/to/example.php(59): assert(false, 'assert($this->p...') #1 /path/to/example.php(75): BugRepository->findAll(12, Object(Status)) #2 {main} thrown in /path/to/example.php on line 59 ɹBTTFSUFYDFQUJPOʹͯ͠࠶࣮ߦ ͖ͪΜͱද໌ҧ൓ͰམͪΔ Α͏ʹͳͬͨ 1 ) 1 
  30. 79.

    ɹ[FOEBTTFSUJPOT ද໌Φϯ 1 ) 1  public function findAll(int $assignedTo,

    Status $status) { assert($this->pdo->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_EXCEPTION); $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $this->pdo->prepare($sql);
  31. 80.

    public function findAll(int $assignedTo, Status $status) { assert($this->pdo->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_EXCEPTION);

    $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $this->pdo->prepare($sql); ɹ[FOEBTTFSUJPOT ද໌Φϑ 1 ) 1  php.ini に zend.assertions = -1 と設定すれば表明を除去できる
  32. 82.

    w Ξαʔγϣϯ͸ɺQVCMJDϝιου಺ͷҾ਺νΣοΫ ʹ࢖༻͠ͳ͍Ͱ͍ͩ͘͞ w Ҿ਺ͷνΣοΫ͸௨ৗɺϝιουͷ࢓༷ ·ͨ͸ن໿ ͷҰ෦ʹͳ͓ͬͯΓɺΞαʔγϣϯͷ༗ޮແޮʹ͔ ͔ΘΒͣɺ͜ͷ࢓༷ʹ४ڌ͢Δඞཁ͕͋Γ·͢ɻ w ΞϓϦέʔγϣϯͷਖ਼͍͠ಈ࡞ʹඞཁͳॲཧΛ࣮ߦ

    ͢ΔͨΊʹΞαʔγϣϯΛ࢖༻͠ͳ͍Ͱ͍ͩ͘͞ IUUQTEPDTPSBDMFDPNKBWBTFKQEPDTUFDIOPUFTHVJEFTMBOHVBHFBTTFSUIUNM ʮΞαʔγϣϯΛ࢖༻ͨ͠ϓϩάϥϛϯάʯΑΓ ɹඞਢͷҾ਺νΣοΫ΍όϦσʔγϣϯʹ࢖Θͳ͍ +BWB
  33. 92.

    Throwable ├── Error │ ├── ArithmeticError │ │ └── DivisionByZeroError

    │ ├── AssertionError │ ├── ParseError │ └── TypeError └── Exception ├── ErrorException ├── LogicException │ ├── BadFunctionCallException │ │ └── BadMethodCallException │ ├── DomainException │ ├── InvalidArgumentException │ ├── LengthException │ └── OutOfRangeException └── RuntimeException ├── OutOfBoundsException ├── OverflowException ├── PDOException ├── RangeException ├── UnderflowException └── UnexpectedValueException 1 ) 1  3VOUJNF&YDFQUJPOܥ ྫ֎ -PHJD&YDFQUJPOܥόά &SSPSܥόά
  34. 94.

    ɹ#VHΫϥε͕ະఆٛͷঢ়گʹ͸Ͳ͏උ͑Δ $ php example.php PHP Fatal error: Class 'Bug' not

    found in /path/to/example.php on line 85 ౴͑ͳʹ΋͠ͳ͍ɻ ͜Ε͸ྫ֎తঢ়گͰ͸ͳ͘ʢจࣈ௨ΓʣόάͰ ͋ΓɺϓϩάϥϛϯάϛεͳͷͰɺΤϥʔϋϯ υϦϯάͯ͠͸ͳΒͳ͍ɻͳʹ΋ͤͣɺ଎΍͔ ʹམͱ͢ɻGBJMGBTU͕ॏཁɻ
  35. 96.

    /** * 検索処理に使用する PDO インスタンスを渡し、バグリポジトリを初期化する。 * * @param PDO $pdo

    PDO インスタンス。 PDO::ATTR_ERRMODE が PDO::ERRMODE_EXCEPTION に 設定されていること * @throws InvalidArgumentException PDO::ATTR_ERRMODE が適切に設定されていない場合 */ public function __construct(PDO $pdo) { if ($pdo->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_EXCEPTION) { throw new InvalidArgumentException('requires PDO::ERRMODE_EXCEPTION'); } $this->pdo = $pdo; } ɹίϯετϥΫλͷࣄલ৚݅Λදݱ͢Δ
  36. 98.

    /** * 指定された担当者 ID およびステータスに合致する Bug を検索し、ヒットした全件を Bug オブジェクトの配列として返す。 *

    * @param int $assignedTo 担当者ID * @param Status $status ステータス * @return Bug[] 条件に合致した Bug オブジェクトの配列を返す。検索に合致するものがな い場合は空配列を返す * @throws LogicException カラム名違いや文法エラー等SQLのミスが存在する場合 * @throws PDOException データベースとのやりとりに何らかの障害が発生した場合 */ public function findAll(int $assignedTo, Status $status): array { assert($this->pdo->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_EXCEPTION); $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; try { $stmt = $this->pdo->prepare($sql); $stmt->bindValue(':assignedTo', $assignedTo, PDO::PARAM_INT); $stmt->bindValue(':status', $status->value(), PDO::PARAM_STR); $stmt->execute(); return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } catch (PDOException $e) { if ($this->isGrammaticalError($e->getCode())) { throw new LogicException($e->getMessage(), $e->errorInfo[1], $e); } throw $e; } } ɹpOE"MMϝιουͷࣄޙ৚݅Λදݱ͢Δ
  37. 99.

    } catch (PDOException $e) { if ($this->isGrammaticalError($e->getCode())) { throw new

    LogicException($e->getMessage(), $e->errorInfo[1], $e); } throw $e; } ɹpOE"MMϝιουͷࣄޙ৚݅Λදݱ͢Δ SQLSTATE の値を調査し、 PDOException の内容が例外で はなくあきらかにバグの場合は、 バグを示す LogicException で包んで投げ直している。 その際に第3引数を忘れずに設定し、スタックトレースを つなぐ
  38. 101.
  39. 102.

    public static function findAll($params) { if (is_null($params)) { throw new

    InvalidArgumentException('params should not be null'); } if (!is_array($params)) { throw new InvalidArgumentException('params should be an array'); } if (count($params) !== 2) { throw new InvalidArgumentException('params should be have exact two items'); } if (!array_key_exists('assignedTo', $params) || !array_key_exists('status', $params)) { throw new InvalidArgumentException('params should have key `assignedTo` and `status` only'); } if (!is_int($params['assignedTo'])) { throw new InvalidArgumentException('params[`assignedTo`] should be an integer'); } if (!is_string($params['status'])) { throw new InvalidArgumentException('params[`status`] should be a string'); } if (!in_array($params['status'], ['OPEN', 'NEW', 'FIXED'], true)) { throw new InvalidArgumentException('params[`status`] should be in `OPEN`,`NEW`,`FIXED`'); } global $CONF; if (!isset($CONF['dsn'])) { throw new LogicException('config key `dsn` not found'); } if (!isset($CONF['usr'])) { throw new LogicException('config key `usr` not found'); } if (!isset($CONF['passwd'])) { throw new LogicException('config key `passwd` not found'); } $pdo = new PDO($CONF['dsn'], $CONF['usr'], $CONF['passwd'], [ PDO::ATTR_EMULATE_PREPARES => false ]); $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $pdo->prepare($sql); $stmt->execute($params); if (!class_exists('Bug')) { throw new LogicException('class `Bug` does not exist'); } return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } public function __construct(PDO $pdo) { $this->pdo = $pdo; } public function findAll(int $assignedTo, Status $status) { $sql = 'SELECT bug_id, summary, date_reported FROM Bugs WHERE assigned_to = :assignedTo AND status = :status'; $stmt = $this->pdo->prepare($sql); $stmt->bindValue(':assignedTo', $assignedTo, PDO::PARAM_INT); $stmt->bindValue(':status', $status->value(), PDO::PARAM_STR); $stmt->execute(); return $stmt->fetchAll(PDO::FETCH_CLASS, Bug::class); } ୈ෦·ͱΊ༧๷ʹউΔ๷ޚͳ͠