チェックポイント
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 |
例 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
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) 未使用
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)
キー | 値 (例) |
---|---|
['_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 を特定する |
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 |
パターン | $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>[^/]+) 他
$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クラス 参照
$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クラスのコンストラクタに渡す情報
キー ($name) | 値 |
---|---|
master | $con (PDO インスタンス) |
キー ($repository_name) | 値 ($name) |
---|---|
Status | master |
User | master |
Following | master |
setRepositoryConnectionMap($repository_name, $name)
キー ($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 |
プロパティ | 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']['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'] | true(ログイン中) or false(未ログイン) |
session->setAuthenticated()
session->isAuthenticated()
キー | 値 (例) |
---|---|
['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' |
メソッド | 処理 |
---|---|
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スクリプト | 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
関数 | 戻り値 |
---|---|
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 | 戻り値 |
---|---|
'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 |
メソッド | 処理 |
---|---|
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対策 参照
値 | |
---|---|
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 比較
アウトプットバッファリング (出力情報を内部にバッファリングする機能)
関数 | 処理 |
---|---|
ob_start() | アウトプットバッファリングの開始 |
ob_implicit_flush(0) | バッファの自動フラッシュを無効にする (最後にまとめて出力するため) |
ob_get_clean | 現在のバッファの内容を取得し、出力バッファを削除する |
render
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 |
例 アカウント登録画面 (ユーザ登録画面)
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
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
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
例 ホームページ
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
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
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
HttpNotFoundExceptionextends
Exception{};
Application.php run (throw) ルートが見つからない
例 No route found for /account
177 if ($params === false) { 178
thrownew
HttpNotFoundException('No route found for ' . $this->request->getPathInfo()); 179 }
Application.php runAction (throw) コントローラが特定できない
例 AccountController controller is not found.
209 if ($controller === false) { 210
thrownew
HttpNotFoundException($controller_class . ' controller is not found.'); 211 }
Controller.php forward404 (throw) エラー
例 Forwarded 404 page from account/register
94 protected function
forward404()95 { 96
thrownew
HttpNotFoundException('Forwarded 404 page from ' 97 . $this->controller_name . '/' . $this->action_name); 98 }
クラス | メソッド | チェック | エラー |
---|---|---|---|
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
UnauthorizedActionExceptionextends
Exception{};
Controller.php run (throw) ログイン必要 && 未ログイン (false 否定 → true)
53 if ($this->needsAuthentication($action) && !$this->session->isAuthenticated()) { 54
thrownew
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
機能 | クラス | メソッド | |
---|---|---|---|
チェック | 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') |