パーフェクトPHP デバッグ (フレームワーク サンプルコード)

チェックポイント

2017年11月25日 公開

2021年04月04日 更新


オブジェクト指向

抽象クラス
親クラス
abstract
親クラス
abstractメソッド
子クラス 子クラス
(abstractメソッド)
インスタンス化 不可 宣言のみ インスタンス化 必要 定義
Application getRootDir
registerRoutes
MiniBlogApplication getRootDir
registerRoutes
Controller AccountController
StatusController
DbRepository UserRepository
StatusRepository
FollowingRepository
フレームワーク Mini Blog アプリケーション
extends
クラス内の $this (クラスのインスタンス)
Application.php 23行目 他
$this->setDebugMode($debug); $this = MiniBlogApplication
Controller.php 26行目 他
$this->controller_name = strtolower(substr(get_class($this), 0, -10)); $this = AccountController
$this = StatusController
DbRepository.php 19行目 他
$this->setConnection($con); $this = UserRepository
$this = StatusRepository
$this = FollowingRepository

ClassLoader

処理の流れ ユーザ登録機能作成 参照

メソッド名 処理
register spl_autoload_register(array($this, 'loadClass'));
  loadClass を、オートロードスタックに登録する
registerDir $this->dirs[0]: C:\XAMPP\xampp_5.6.31\htdocs\mini-blog.localhost/core
$this->dirs[1]: C:\XAMPP\xampp_5.6.31\htdocs\mini-blog.localhost/models
loadClass core, models内のクラスを読み込む (ClassLoader 除く)
オートロード core, models内の読み込まれてないクラスが、new や extends された時、
オートロードスタックに登録された loadClass を実行する

フロントコントローラと.htaccess

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>

C:\XAMPP\xampp_5.6.31\htdocs\mini-blog.localhost\web\.htaccess

処理内容: 指定されたファイルが存在しなければ、すべて index.php にアクセスする

Request

メソッド名 処理
getBaseUrl ベースURLを取得する
getPathInfo PATH_INFOを取得する

ベースURL, PATH_INFO

http://mini-blog.localhost/
REQUEST_URI SCRIPT_NAME ベースURL PATH_INFO
/ /index.php /
/status/post /index.php /status/post
/user/user1 /index.php /user/user1
/user/user1/status/1 /index.php /user/user1/status/1
/account /index.php /account
/account/signup /index.php /account/signup
/follow /index.php /follow

REQUEST_URI - ベースURL = PATH_INFO
IDE (NetBeans IDE 8.2) 未使用

http://mini-blog.localhost/index.php/
REQUEST_URI SCRIPT_NAME ベースURL PATH_INFO
/index.php/ /index.php /index.php /
/index.php/status/post /index.php /index.php /status/post
/index.php/user/user1 /index.php /index.php /user/user1
/index.php/user/user1/status/1 /index.php /index.php /user/user1/status/1
/index.php/account /index.php /index.php /account
/index.php/account/signup /index.php /index.php /account/signup
/index.php/follow /index.php /index.php /follow

REQUEST_URI - ベースURL = PATH_INFO
IDE (NetBeans IDE 8.2) 未使用

$_POST (getPost)

CSRF対策
キー 値 (例)
['_token'] 'ed38d8176bff02c2b0364055da5e8cb4d72fc85d'

<input type="hidden" name="_token" value="<?php echo $this->escape($_token); ?>" />

Router

動画 (YouTube)パーフェクトPHP をデバッグしました (デバッグ例 応用編) ルーティング 参照

メソッド名 処理
__construct __construct($definitions)    $definitions = $this->registerRoutes()
参考 Application.php 54行目 (new Router($this->registerRoutes()))
参考 MiniBlogApplication.php 17行目 (protected function registerRoutes())
compileRoutes ルーティング定義↓ から $this->routes↓ を作成する
resolve $this->routes↓ を検索して PATH_INFOから controller, action を特定する
ルーティング定義 (MiniBlogApplication)
PATH_INFO controller action
/ status index
/status/post status post
/user/:user_name status user
/user/:user_name/status/:id status show
/account account index
/account/:action account
/follow account follow
$this->routes ($this = Router)
パターン $params['controller'] $params['action']
/ status index
/status/post status post
/user/(?P<user_name>[^/]+) status user
/user/(?P<user_name>[^/]+)/status/(?P<id>[^/]+) status show
/account account index
/account/(?P<action>[^/]+) account
/follow account follow

action を特定する
    (Router.php 62行目) preg_match('#^' . $pattern . '$#', $path_info, $matches)
    例 /account/signup から $matches['action']=>'signup'↓ が設定される
名前付きキャプチャ: (?P<user_name>[^/]+)

$matches
$path_info $matches ($params)

/user/user1
/user/user2
/user/user3
(controller, action 以外)
$matches['user_name']=>'user1'
$matches['user_name']=>'user2'
$matches['user_name']=>'user3'

/user/user1/status/1
/user/user1/status/2
/user/user2/status/3
/user/user2/status/4
/user/user3/status/5
(controller, action 以外)
$matches['user_name']=>'user1', $matches['id']=>'1'
$matches['user_name']=>'user1', $matches['id']=>'2'
$matches['user_name']=>'user2', $matches['id']=>'3'
$matches['user_name']=>'user2', $matches['id']=>'4'
$matches['user_name']=>'user3', $matches['id']=>'5'
/account/signup
/account/register
/account/signin
/account/authenticate
/account/signout
$matches['action']=>'signup'
$matches['action']=>'register'
$matches['action']=>'signin'
$matches['action']=>'authenticate'
$matches['action']=>'signout'

準備 | テストデータ 参照

動的パラメータ
正規表現 (パターン) 一致した文字列
/user/(?P<user_name>[^/]+) /user/user1
/user/user2
/user/user3
/user/(?P<user_name>[^/]+)/status/(?P<id>[^/]+) /user/user1/status/1
/user/user1/status/2
/user/user2/status/3
/user/user2/status/4
/user/user3/status/5
/account/(?P<action>[^/]+) /account/signup
/account/register
/account/signin
/account/authenticate
/account/signout

準備 | テストデータ 参照

Response

プロパティ Application.php runAction内
$content response->setContent($content)

DbManager

  動画 (YouTube)パーフェクトPHP をデバッグしました (デバッグ例 応用編) DbManagerクラス 参照

 

connect (接続情報)
$name PDO インスタンス
master $con = new PDO dsn mysql:dbname=mini_blog;host=localhost;charset=utf8
user root
password
options

接続情報のセット (MiniBlogApplication.php 39行目, DbManager.php 20行目)
PDOクラスのコンストラクタに渡す情報

$this->connections
キー ($name)
master $con (PDO インスタンス)
$this->repository_connection_map 未使用 (複数のデータベース)
キー ($repository_name) 値 ($name)
Status master
User master
Following master

setRepositoryConnectionMap($repository_name, $name)

$this->repositories
キー ($repository_name) 値 ($repository) メソッド名 (DbManager)
User UserRepositoryインスタンス db_manager->get('User')
Status StatusRepositoryインスタンス db_manager->get('Status')
Following FollowingRepositoryインスタンス db_manager->get('Following')

DbRepository

テーブル クラス
userテーブル UserRepository
statusテーブル StatusRepository
followingテーブル FollowingRepository
__construct($con)
プロパティ setConnection($con) DbManager
$con UserRepository->con
StatusRepository->con
FollowingRepository->con
DbManager->connections['master'] = $con
(PDO インスタンス)
メソッド メソッド 実装
execute($sql, $params = array()) $stmt = $this->con->prepare($sql);
$stmt->execute($params);
return $stmt;
($stmt: PDOstatement インスタンス)
fetch($sql, $params = array()) return $this->execute($sql, $params)->fetch(PDO::FETCH_ASSOC);
fetchAll($sql, $params = array()) return $this->execute($sql, $params)->fetchAll(PDO::FETCH_ASSOC);

prepare (プレースホルダ部分に入ってくる値をエスケープする)

PDO::FETCH_ASSOC (取得結果を連想配列で受け取る)

Session

静的プロパティ 処理
static $sessionStarted
  self::$sessionStarted
session_start() が複数回呼ばれないようにチェック
static $sessionIdRegenerated
  self::$sessionIdRegenerated
session_regenerate_id(true) が複数回呼ばれないようにチェック

インスタンス化されなくても使える (処理が関数外に移っても変数の値が保持される)

$_SESSION

user (ログインユーザ)
キー
['user']['id'] '1'
['user']['user_name'] 'user1'
['user']['password'] 'ee5281d035bd1bd7786301be4274a68b006ae916'
['user']['created_at'] 2017-11-01 00:00:00

session->get('user')
session->set('user', $user)
ユーザID: user1、パスワード: password でログイン
準備 | テストデータ 参照

_authenticated (認証 ログイン状態)
キー
['_authenticated'] true(ログイン中) or false(未ログイン)

session->setAuthenticated()
session->isAuthenticated()

CSRF対策
キー 値 (例)
['csrf_tokens/account/signin'][0] ... 'ed38d8176bff02c2b0364055da5e8cb4d72fc85d'
['csrf_tokens/account/signin'][9] 'bfb55f8dd338b02efea1ad1a12c249b722b499ae'
['csrf_tokens/account/signup'][0] ... '117d0e6d9da8ec9e595110d7e517993901ee77ce'
['csrf_tokens/account/signup'][9] '17fbad25e479cdc7e712b9977d2b9d9fdb85ecb3'
['csrf_tokens/account/follow'][0] ... '4b710f10e11206dd0e5ba2b23fea9c993b20a6c3'
['csrf_tokens/account/follow'][9] '77e5617a7d149f6f4774f4f607affaa3a171cdc9'
['csrf_tokens/status/post'][0] ... '62138aa5b21213242eee2135892b9589609cc9d6'
['csrf_tokens/status/post'][9] '27da715c32fe321f52f20ddf0a45e462a611edf3'
CSRF対策 (例 'account/signin')
メソッド 処理
session->get('csrf_tokens/account/signin', array()) $_SESSION['csrf_tokens/account/signin'] --> $tokens
session->set('csrf_tokens/account/signin', $tokens) $tokens --> $_SESSION['csrf_tokens/account/signin']

Application

PHPスクリプトの読み込み
PHPスクリプト require_once/require
/controllers require_once Application.php (231行目)  findController
require_once は一度読み込んだファイルは2度読み込まない
(関数の再定義や変数の再代入を防ぐ)

Mini Blog アプリケーション
  AccountController.php
  StatusController.php
/core require ClassLoader.php (40行目)  loadClass
フレームワーク
/models require ClassLoader.php (40行目)  loadClass
DbManager->repositories[] 使用  (DbManager.php  get)
  初回のみ、インスタンスを生成する
    require_once と同じ効果 (関数の再定義や変数の再代入を防ぐ)

Mini Blog アプリケーション
  UserRepository.php
  StatusRepository.php
  FollowingRepository.php
プロパティ インスタンス
MiniBlogApplication->request Request
MiniBlogApplication->response Response
MiniBlogApplication->session Session
MiniBlogApplication->db_manager DbManager
MiniBlogApplication->router Router

Controller

Controller.php (26行目)
$this->controller_name = strtolower(substr(get_class($this), 0, -10))
関数 戻り値
strtolower(substr(get_class($this), 0, -10)) 'user' ($this->controller_name)
get_class($this) (例 $this: UserController) 'UserController'
substr('UserController', 0, -10) 'User' (Controller: 10文字)
strtolower('User') 'user'
$this->controller_name
$this 戻り値
'AccountController' 'account'
'StatusController' 'status'
プロパティ プロパティ (Application) インスタンス
AccountController->request MiniBlogApplication->request Request
AccountController->response MiniBlogApplication->response Response
AccountController->session MiniBlogApplication->session Session
AccountController->db_manager MiniBlogApplication->db_manager DbManager
StatusController->request MiniBlogApplication->request Request
StatusController->response MiniBlogApplication->response Response
StatusController->session MiniBlogApplication->session Session
StatusController->db_manager MiniBlogApplication->db_manager DbManager
CSRF対策 (例 'account/signin')
メソッド 処理
generateCsrfToken('account/signin') $tokens トークン最大10個
$token 生成 (SHA-1 ハッシュ値)、$tokensにセット
return $token

'_token' => $this->generateCsrfToken('account/signin')
checkCsrfToken('account/signin', $token) true: $tokens に $token 有、$tokens の $token 削除
false: 未処理

  動画 (YouTube)パーフェクトPHP をデバッグしました (デバッグ例 応用編) CSRF対策 参照

$auth_actions (ログインが必要なアクション)
AccountController array('index', 'signout', 'follow')
StatusController array('index', 'post')
- true (すべてのアクションがログイン必須) 未使用

View

  動画 (YouTube)パーフェクトPHP をデバッグしました (デバッグ例 応用編) アウトプットバッファリング 参照

HTML構成

例 アカウント登録画面 (ユーザ登録画面)

layout.php (すべての画面で共通)

render 実行順3, 出力順3 (View::render in View::render), $_layout = false

$base_url

$session

$title

$_content

signup.php

render 実行順1, 出力順2 (View::render in Controller::render), $_layout = 'layout'

$base_url

$user_name

$password

$_token

inputs.php

render 実行順2, 出力順1 (View::render in signup.php), $_layout = false

$user_name

$password

エラー画面

errors.php (すべてのエラー画面で共通)

inputs - signup - layout  比較

inputs - signup  比較

(クリックすると、拡大表示します)

signup - layout  比較

(クリックすると、拡大表示します)

アウトプットバッファリング (出力情報を内部にバッファリングする機能)

関数 処理
ob_start() アウトプットバッファリングの開始
ob_implicit_flush(0) バッファの自動フラッシュを無効にする (最後にまとめて出力するため)
ob_get_clean 現在のバッファの内容を取得し、出力バッファを削除する

render

__construct
View->base_dir C:\XAMPP\xampp_5.6.31\htdocs\mini-blog.localhost/views
View->defaults['request'] Request
View->defaults['base_url'] ''
View->defaults['session'] Session
例 アカウント登録画面 (ユーザ登録画面)
View->render メソッド (inputs.php) 実行順2, 出力順1
array_merge extract require inputs.php
View->defaults['request'] $request
View->defaults['base_url'] $base_url
View->defaults['session'] $session
$_variables['user_name'] $user_name $user_name ''
$_variables['password'] $password $password ''
$_variables['_token'] $_token

$content = ob_get_clean() ... inputs.php

View->render メソッド (signup.php) 実行順1, 出力順2
array_merge extract require signup.php
View->defaults['request'] $request
View->defaults['base_url'] $base_url $base_url ''
View->defaults['session'] $session
$_variables['user_name'] $user_name $user_name ''
$_variables['password'] $password $password ''
$_variables['_token'] $_token $_token 'e1968c9ccc99acfccadd1ff7a524f1eb74c18ec1' 例

$content = ob_get_clean() ... signup.php + inputs.php

View->render メソッド (layout.php) 実行順3, 出力順3
array_merge extract require layout.php
View->defaults['request'] $request
View->defaults['base_url'] $base_url $base_url ''
View->defaults['session'] $session $session Session
$_variables['title'] $title $title 'アカウント登録'
$_variables['_content'] $_content $_content signup.php + inputs.php

echo $_content
$content = ob_get_clean() ... layout.php + signup.php + inputs.php

例 ホームページ
View->render メソッド (status.php) 実行順2, 出力順1
array_merge extract require status.php
View->defaults['request'] $request
View->defaults['base_url'] $base_url $base_url ''
View->defaults['session'] $session
$_variables['status']['id'] $status['id'] $status['id'] '1'
$_variables['status']['user_id'] $status['user_id']
$_variables['status']['body'] $status['body'] $status['body'] 'status1 user1 テスト1'
$_variables['status']['created_at'] $status['created_at'] $status['created_at'] '2017-11-01 00:00:00'
$_variables['status']['user_name'] $status['user_name'] $status['user_name'] 'user1'

foreach (投稿status1~status4)
$content = ob_get_clean() ... status.php

View->render メソッド (index.php) 実行順1, 出力順2
array_merge extract require index.php
View->defaults['request'] $request
View->defaults['base_url'] $base_url $base_url ''
View->defaults['session'] $session
$_variables['statuses'] $statuses $statuses 投稿 status1~status4 の4件
$_variables['body'] $body $body ''
$_variables['_token'] $_token $_token 'e1968c9ccc99acfccadd1ff7a524f1eb74c18ec1' 例

foreach (投稿status1~status4)
$content = ob_get_clean() ... index.php + status.php

View->render メソッド (layout.php) 実行順3, 出力順3
array_merge extract require layout.php
View->defaults['request'] $request
View->defaults['base_url'] $base_url $base_url ''
View->defaults['session'] $session $session Session
$_variables['title'] $title $title 'ホーム'
$_variables['_content'] $_content $_content index.php + status.php

foreach (投稿status1~status4)
echo $_content
$content = ob_get_clean() ... layout.php + index.php + status.php

エラー画面
コントローラ アクション Controller->render ($template)
(入力画面)
AccountController registerAction signup
authenticateAction signin
StatusController postAction index

例外処理

HttpNotFoundException (エラー処理)

HttpNotFoundException.php
<?php
 
class HttpNotFoundException extends Exception {};
 

Application.php run (throw)  ルートが見つからない

例 No route found for /account

177            if ($params === false) {
178                throw new HttpNotFoundException('No route found for ' . $this->request->getPathInfo());
179            }

Application.php runAction (throw)  コントローラが特定できない

例 AccountController controller is not found.

209        if ($controller === false) {
210            throw new HttpNotFoundException($controller_class . ' controller is not found.');
211        }

Controller.php forward404 (throw)  エラー

例 Forwarded 404 page from account/register

94    protected function forward404()
95    {
96        throw new HttpNotFoundException('Forwarded 404 page from '
97            . $this->controller_name . '/' . $this->action_name);
98    }


エラー (forward404)
クラス メソッド チェック エラー
Controller run if (!method_exists($this, $action_method)) {
    $this->forward404();
}
クラスメソッド 無
AccountController registerAction
authenticateAction
followAction
if (!$this->request->isPost()) {
    $this->forward404();
}
POSTでない
followAction if (!$following_name) {
    $this->forward404();
}
フォロー名 無
if (!$follow_user) {
    $this->forward404();
}
フォロー 無
StatusController postAction if (!$this->request->isPost()) {
    $this->forward404();
}
POSTでない
userAction if (!$user) {
    $this->forward404();
}
ユーザ 無
showAction if (!$status) {
    $this->forward404();
}
投稿詳細 無
Application.php run (catch)  ブラウザに エラー 表示
185        } catch (HttpNotFoundException $e) {
186            $this->render404Page($e);

参考 NetBeans デバッグ方法 (変数の値を変更して 処理の流れを変える) 参照

UnauthorizedActionException (ログイン画面への遷移)

UnauthorizedActionException.php
<?php
 
class UnauthorizedActionException extends Exception {};
 

Controller.php run (throw)  ログイン必要 && 未ログイン (false 否定 → true)
53        if ($this->needsAuthentication($action) && !$this->session->isAuthenticated()) {
54            throw new UnauthorizedActionException();
55        }

Application.php run (catch)  ログイン画面 へ

runAction('account', 'signin')

187        } catch (UnauthorizedActionException $e) {
188            list($controller, $action) = $this->login_action;
189            $this->runAction($controller, $action);
190        }

参考 処理の流れ (アカウント情報管理とログイン) 参照

Mini Blog Application

メソッド (in AccountController, StatusController)
機能 クラス メソッド
チェック Controller checkCsrfToken($form_name, $token) トークン チェック
Request isPost() POST チェック
Session isAuthenticated() ログイン状態 チェック
FollowingRepository isFollowing($user_id, $following_id) true (1, 2)
false (1, 3)
   注 !isFollowing
null (1, 1)
UserRepository isUniqueUserName($user_name) ユーザ名 重複チェック
データ設定 Controller generateCsrfToken($form_name) トークン 生成
Session clear() ログアウト
Session set($name, $value) ログインユーザ
   ('user', $user)
Session setAuthenticated($bool) ログイン状態 true, false
データ取得 DbManager get($repository_name) 'Following'
   FollowingRepository
'Status'
   StatusRepository
'User'
   UserRepository
Request getPost($name) 'user_name'
'password'
'_token'
'body'
'following_name'
Session get($name) ログインユーザ ('user')
StatusRepository fetchAllByUserId($user_id) status1~status2
status3~status4
status5
StatusRepository fetchAllPersonalArchivesByUserId($user_id) 'user1' (2件), 'user2' (2件)
StatusRepository fetchByIdAndUserName($id, $user_name) status1
UserRepository fetchAllFollowingsByUserId($user_id) フォロー 'user2'
UserRepository fetchByUserName($user_name) 'user1' 1レコード
'user2' 1レコード
'user3' 1レコード
'user4' 1レコード
UserRepository hashPassword($password) 'password'
データ出力 FollowingRepository insert($user_id, $following_id) 1, 3
StatusRepository insert($user_id, $body) 1, 'status6 user1 テスト6'
UserRepository insert($user_name, $password) 'user4', 'password'
画面 Controller forward404()
Controller redirect($url)
Controller render($variables = array(), $template = null, $layout = 'layout')

準備 | テストデータ 参照

Page Top
Page Bottom