Skip to content

DaisukeMatsuura/PHP_Framework

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PHP7製のフレームワークを作ろう!

1. ClassLoaderクラスを作ろう

ClassLoaderクラスはオートロードに関する処理をまとめたクラス。

  • PHPにオートローダークラスを登録する処理(register()メソッド)
  • オートロードが実行された際にクラスファイルを読み込む処理(loadClass()メソッド)

2. bootstrap.phpを作ろう

作成したClassLoaderをオートロードに登録するための設定ファイルです。

現段階では明示的にrequireを用いてクラスファイルを読み込みます。

registerDir()で coreディレクトリと modelsディレクトリをオートロードの対象ディレクトリに設定し、register() でオートロードに登録します。

3. Requestクラスを作ってURLを制御しよう

HTTPメソッド(GET/POST)の判定や $_GET、$_POST などの値の取得、リクエストされたURLの取得、サーバのホスト名やSSLでのアクセスかどうかと行った判定などの機能の実装をしているクラスです。

isPost() メソッドで、HTTPメソッドが POST かどうかの判定を行います。また、getGet() メソッドや getPost() メソッドでそれぞれ $_GET、$_POST 変数からの値の取得を行います。

getHost() メソッドで、サーバのホスト名の取得をします。isSsl() メソッドでは HTTPS でのアクセスかどうかの判定を行います。getRequestUri() メソッドではリクエストされたURLのホスト部分以降を返します。

getBaseUrl() メソッドでベースURL(ホスト部分より後ろから、フロントコントローラまでの値)を取得し、getPathInfo() メソッドで PATH_INFO(ベースURLより後ろの値)を取得します。これらの値を利用してURLの制御を行って行きます。

4. Routerクラスを作ってルーティングを制御しよう

Routerクラスはルーティング定義配列とPATH_INFOを受け取り、ルーティングパラメータを特定する役割をもちます。

compileRoutes() メソッドで、受け取ったルーティング定義配列中の動的パラメータ指定を正規表現で扱える形式に変換します。resolve() メソッドで、変換済みのルーティング定義配列とPATH_INFOマッチングを行い、ルーティングパラメータの特定を行います。特定できなかった場合は False を返します。

5. Responseクラスを作ろう

Responseクラスはレスポンスを表すクラスで、HTTPヘッダとHTMLなどのコンテンツを返すのが主な役割です。

setContent() メソッドで、$contentプロパティにHTMLなどの実際にクライアントに返す内容を格納します。setStatusCode() メソッドで、$status_codeプロパティ、$status_textプロパティにHTTPのステータスコードを格納します。setHttpHeader() メソッドで、$http_headersプロパティにHTTPヘッダを格納します。send() メソッドで各プロパティに設定された値を元にレスポンスの送信を行います。

6. PDOによるデータベースアクセス

DbManagerクラスでは、PHPに標準で付属するデータベース抽象化ライブラリであるPDO(PHP Data Object)クラスを使って、データベースへのアクセスを管理します。

DbRepositoryクラスは、実際にはデータベース上のテーブルごとにこのクラスを継承したクラスを作成し、そこにデータベースアクセス処理を記述して行きます。userテーブルがあれば、UserRepositoryクラスを作成する、と行った具合です。

DbManagerクラスとDbRepositoryクラスの関係性としては、DbManagerクラスの内部にテーブルごとのRepositoryクラスを保持するようにします。DbManagerクラスのインスタンスが $db_manager 変数に入っているとして、$db_manager->get('User') のようにして UserRepository クラスを取得するようにします。

DbManagerクラスの使い方
$db_manager = new DbManager();
$db_manager->connect('master', array(
    'dsn'      => 'mysql:dbname=mydb;host=localhost',
    'user'     => 'myuser',
    'password' => 'mypass',
));
$db_manager->getConnection('master');
$db_manager->getConnection();  #=> master が返ってくる
DbRepositoryクラスと接続情報のマッピング

DbManagerクラス内、getConnection() には引数の指定がなければ最初に作成したコネクションを取得するようにしてある。それ以外のものを使用する場合に備え、getConnectionForRepository()メソッドの実装も行っている。

$repository_connection_mapプロパティにテーブルごとのRepositoryクラスと接続名の対応を格納し、getConnectionForRepository()メソッドでRepositoryクラスに対応する接続を取得しようとした際に、$repository_connection_mapプロパティに設定されているものは getConnection()メソッドに接続名を指定し、そうでなければ最初に作成したものを取得するという機能になる。

Repositoryクラスの管理

DbRepositoryクラスは後ほど記述するが、1度インスタンスを作成したら、それ以降インスタンスを生成する必要は無いように実装してあり、全てのインスタンスをDbManagerクラスで管理できるようにしている。それらは全て $repositories に格納している。

DbRepositoryクラス

DbRepositoryクラスはデータベースへのアクセスを行うクラスで、テーブルごとにDbRepositoryクラスの子クラスを作成するようにします。

各Repositoryクラスには、例えばuserテーブルであればUserRepositoryクラスを定義し、userテーブルへレコードの新規作成を行う insert()メソッドや id というカラムを元にデータを取得する fetchById()メソッドなどを必要に合わせて作成していくことを想定しています。

それぞれのメソッド内部ではSQLを実行することになりますが、SQLの実行時に頻繁に出てくるような処理をDbRepositoryに抽象化しておきます。

DbRepository内でのPDOクラスについて

__construct()メソッドとsetConnection()メソッドはDbManagerクラスからPDOクラスのインスタンスを受け取って内部に保存するメソッドです。ここで受け取ったPDOクラスのインスタンスに対してSQL文を実行します。

プリペアドステートメントについても補足しておきます。

$name = $_POST['name'];
$sql = "INSERT INTO user (name) VALUES('" . $name . "')";

上記の例はSQLに脆弱性があります。ユーザの入力に対しては下記の例のように適切に入力値をエスケープする必要があります。

$sql = 'INSERT INTO user (name) VALUES(:name)';
$stmt = $con->prepare($sql);

$params = array(':name' => $_POST['name']);
$stmt->execute($params);

SQLには直接変数を入れずに、「:name」のような文字列を指定します。:nameは動的パラメータが入るという指定で、プレースホルダと呼びます。プリペアドは準備済みという意味です。このプリペアドステートメントを用いると、プレースホルダ部分に入ってくる値は適切にエスケープされます。

7. Sessionクラスを作ろう

Sessionクラスはセッション情報を管理するクラスです。$_SESSION 変数のラッパークラスに相当します。

set()メソッドと get()メソッドで $_SESSION への設定と取得を行います。remove()メソッドは $_SESSION から指定した値を削除するメソッドです。clear()メソッドは$_SESSIONを空にするメソッドです。

ログイン状態の制御

setAuthenticated()メソッドと isAuthenticated()メソッドはユーザがログイン状態を制御するメソッドです。セッション内に _authenticated というキーでログインしているかどうかのフラグを格納し、これを用いてログイン状態の判定を行います。

あくまで認証の機構としてセッションを利用するだけなので、このメソッドがこのクラスにあるのは少々不適切かもしれませんが、フレームワークの簡略化のため今回はSessionクラスに実装します。

セッション固定攻撃に対してフレームワーク側で多少なりとも対策できるよう、ログイン状態の変更を行う setAuthenticated() メソッド内で regenerate()を実行するようにします。

8. Applicationクラスを作ろう

Applicationクラスはフレームワークの中心となるクラスです。このクラスではRequestクラスやRouterクラス、Responseクラス、Sessionクラスなどのオブジェクトの管理を行うほか、ルーティングの定義、コントローラの実行、レスポンスの送信など、アプリケーション全体の流れを司ります。

また、管理するのはオブジェクトだけではなく、アプリケーションの様々なディレクトリへのパスの管理なども行います。この他にも、デバッグモードで実行できるような機能も持たせます。

setDebugMode() メソッド

まず1つめが setDebugMode()メソッドです。デバッグモードに応じてエラー表示処理を変更するようにしています。

initialize() メソッド

次に呼び出すのが initialize()メソッドで、クラスの初期化処理を行います。Routerクラスはインスタンスを作成する際にルーティング定義配列を渡すように実装している為、ルーティング定義配列を返す registerRoutes() メソッドを呼び出すようにしています。

registerRoutes() メソッドは抽象メソッドとして定義しています。このように抽象メソッドとして定義しておけば呼び出し側は変える必要がなく、個別のアプリケーションで registerRoutes() メソッドを定義しないとエラーになるため定義漏れも無くなります。

configure() メソッド

initialize() メソッドの直後に呼び出されるのが configure() メソッドです。これは空のメソッドとして定義しています。個別のアプリケーションで様々な設定をできるように定義しています。

情報取得用のメソッド

コンストラクタで呼び出しているメソッドの他にも、様々なメソッドを定義しています。設定したプロパティの値を取得するメソッドとコントローラやモデルのディレクトリへのパスを返すメソッドです。

getRootDir() メソッドはアプリケーションのルートディレクトリへのパスを返すメソッドで、これはアプリケーションごとに設定するように抽象メソッドとして定義しています。パスを返すメソッドをわざわざ定義しているのは、必要があればディレクトリ構成を変更できるようにするためです。

コントローラの呼び出しと実行

Applicationクラスでは全体の処理の流れも管理します。コントローラを呼び出してアクションを実行する処理を実装します。

Routerからコントローラを特定しレスポンスの送信を行うまでを管理する run() メソッド、実際にアクションを実行する runAction() メソッド、runAction() メソッドの中でコントローラクラスを生成する findController() メソッドの3つでコントローラの呼び出し実行を行います。

run()メソッド

Routerクラスの resolve()メソッドを呼び出してルーティングパラメータを取得し、コントローラ名とアクション名を特定します。それらの値を元に runAction()メソッドを呼び出してアクションを実行します。

run() メソッドはアプリケーションがユーザのリクエストに対応するためのトリガーとなるメソッドです。これを実行することで、Applicationクラス内部に持つオブジェクトを流れに沿って呼び出し、レスポンスを返します。

runAction()メソッド

runAction()メソッドは実際にアクションを実行するメソッドです。コントローラのクラス名はコントローラ名に Controller を付けるというルールにします。またルーティングにはコントローラ名の先頭を小文字で指定するようにしたいので、ucfirst()関数を用いて先頭を大文字にしています。

runAction()メソッドはコントローラ名とアクション名を直接受け取るため、内部で別のアクションを呼び出したい場合にコントローラ名を直接指定して呼び出すことも可能です。

findController()メソッド

findController()メソッドではコントローラクラスが読み込まれていない場合に、クラスファイルの読み込みを行います。クラスファイルの読み込みが完了したらコントローラクラスを生成しています。Controllerクラスの実装時には、コンストラクタにApplicationクラス自身を渡すようにします。

コントローラの生成が完了したらrunAction()メソッドに戻って、Controllerクラスのrun()メソッドを呼び出しています。この戻り値として受け取った値をレスポンスに設定しています。

9. Controllerクラスの作成とViewとの連携

コントローラとアクション

アクションは1つの画面に対応します。例えばデータの入力画面や一覧画面はそれぞれ1つのアクションを定義します。コントローラはいくつかのアクションのまとまりです。実際に作成する際は画面ごとにアクションを作成します。

アクションが呼び出される時点では既に、フレームワークの初期化やデータベースとの接続、ルーティングが行われています。そのため画面を追加するたびにそういった処理を呼び出す必要はなく、それらの情報が整った状態で開発を進めていくことが可能です。

Controllerクラスの処理の流れ

Controllerクラスの処理の流れとしては run()メソッドの中で個別のアクションを呼び出し、アクションの内部でモデルにあたるDbRepositoryクラスからデータの取得などの処理を行った後、Viewクラスを生成してアクションに応じたHTMLのレンダリングを行い、run()メソッドの実行結果としてレンダリングしたHTMLを文字列として返す、といったようになります。

なお、一般的なMVCモデルでのコントローラはビューファイルにデータを渡す部分までを担い、ビューファイルのレンダリングは別のクラスが行います。

今回のフレームワークでコントローラにビューファイルをレンダリングする機能を持たせています。これはビューファイルのレンダリングをコントローラ内で行えた方が、例えばレンダリング結果をファイルに保存してリダイレクトする、などといった処理を行う上で都合が良いためです。

Controllerクラスの機能

Controllerクラスは画面を実際に制御するための重要なクラスなので、いくつかの機能に分割して説明をします。Controllerには主に次のような機能を実装します。

  • アクションを実行する run() メソッド
  • ビューファイルをレンダリングする render() メソッド 内部でViewクラスを呼び出す
  • リダイレクトを行う redirect() メソッド
  • 404エラー画面に遷移する forword404() メソッド
  • CSRF対策を行う generateCsrfToken() メソッドと checkCsrfToken()
  • ログイン状態の制御機構 run() メソッドの内部で行う
Controllerクラスの実装

Controllerクラスはアプリケーションごとに子クラスを作成し、その中にアクションを定義するようにするため、Controllerクラス自体は抽象クラスとして定義します。

get_class()関数は指定したオブジェクトのクラス名を取得する関数です。get_class($this)で自分自身のクラス名を取得し、substr()関数で10文字分である Controller を取り除いています。最終的にstrtolower()関数で小文字に変換しています。

Controller::run()メソッド

このフレームワークではアクションはControllerクラスに実装されたメソッドにあたります。アクションにあたるメソッド名は「アクション名+Action()」というルールで扱います。

run()メソッドの第1引数としてアクション名を受け取ります。アクション名は $action_name プロパティに格納します。これを元にアクションの名前を $action_method変数に格納し、method_exists()関数を用いてメソッドが存在しているかチェックします。

9. Viewクラスを作ろう

ビューの仕組み

このフレームワークではViewクラスがビューファイルの読み込みなどの役割を担います。Viewクラスでは、Viewクラス内にHTMLを書くのではなく、HTMLファイルの読み込みと、HTMLに変数を渡すということを行います。

ビューファイルの配置

HTMLはルートディレクトリにあるviewsディレクトリ内に配置します。その際、viewsディレクトリ直下にHTMLをたくさん置くのではなく、コントローラごとにディレクトリを分けるようにします。例えば、userコントローラのshowアクションなら、views/user/show.phpとなります。アクションとの関連性を明確にすることで管理しやすくするためにこうしますが、アクションに指定できるようにもします。

レイアウトファイル

HTMLを記述する際に毎回、<html><head>....のように全体のHTML構造を書くのは少々煩わしいので、全体での共通の部分と、アクションごとに変わる部分を分離します。

全体で共通の部分をレイアウトと呼ぶようにします。レイアウトはviews/layout.phpに配置するようにします。こうすることでビューファイルを配置する手間を抑えられます。

Viewクラス

Viewクラスはビューファイルの読み込みや、ビューファイルに渡す変数の制御を行います。

ビューファイルの読み込みはrequireを用いれば実行可能です。しかしその際、読み込んだファイル内で出力が行われていると、読み込んだ時点で出力が行われてしまいます。フレームワークでは出力情報を文字列として読み込み、レスポンスに設定する必要があります。

そこで アウトプットバッファリング という仕組みを用いて、出力を文字列として取得します。また、requireを用いてファイルを読み込むと、requireを実行した側でアクセス可能な変数に対し、読み込まれた側のファイルでもアクセスすることができます。この仕組みを用いてビューファイルでも変数を参照できるようにします。

しかし、例えば、アクション内で利用している変数すべてを読み込むようにすると、ビューファイル内でどのような値を出力されているのかがわかりづらくなり、管理コストが上がってしまいます。そこでビューファイルで必要な変数のみを明示的に指定できる仕組みを作成します。

render()メソッド

render()メソッドは実際にビューファイルの読み込みを行うメソッドです。第1引数はビューファイルへのパスを指定します。これを $base_dirプロパティの中から探します。今回ビューファイルの拡張子は.phpで固定します。第2引数はビューファイルに渡す変数を指定します。これは連想配列で指定します。第3引数はレイアウトファイル名を指定します。レイアウトファイルの指定が必要なのはControllerクラスから呼び出された際のみなので、ここではデフォルト値をFalseにしています。Falseの場合、レイアウトの読み込みは行いません。

変数展開について

<?php
//連想配列のキーと値を元に変数低回が行われる
extract(array('foo' => 'bar'));

//変数$fooには"bar"が入っている
echo $foo;  // => "bar"

ビューファイルでは1つ1つの値を変数として利用できる方が扱いやすいため変数展開を行います。連想配列のそれそれの要素を変数展開する最も簡単な方法がextract()関数を利用することです。この状態でビューファイルを読み込むと、ビューファイルから展開した変数にアクセスすることが可能です。

10. フレームワークの完成

ここまででフレームワークの作成はすべて完了です。 作成したファイルは以下のようになっています。※順番はアルファベット順

application/
    bootstrap.php
    controllers/
    core/
        Application.php
        ClassLoader.php
        Controller.php
        DbManager.php
        DbRepository.php
        HttpNotFoundException.php
        Request.php
        Response.php
        Router.php
        Session.php
        UnauthorizedActionException.php
        View.php
    models/
    views/
    web/
        .htaccess
        index.php

フレームワークを使った際の開発内容

このフレームワークを用いて開発する際は次の項目を実際に作っていく必要があります。

  • Applicationクラス

    • ルートディレクトリの指定
    • アクションにあわせたルーティング定義
    • 接続するデータベースの指定(DbManagerクラスを操作)
    • ログインアクションの指定
  • index.php

    • Applicationの呼び出しと実行
  • Controllerクラス (C:コントローラ)

    • 役割にあわせて子クラスの作成
    • 作成する画面にあわせたアクション定義、処理の実装
    • ログインが必要なアクションの指定
  • DbRepositoryクラス (M:モデル)

    • データベース上のテーブルごとに子クラスの作成
    • データベースアクセス処理の実装
  • ビューファイル (V:ビュー)

    • アクションにあわせたHTMLの記述
    • レイアウトファイルの記述

    フレームワークはMVCモデルを採用しているため、モデル/ビュー/コントローラに相当する箇所をアプリケーションに応じて作成を行います。

    データベースへの接続やログイン管理など、フレームワークに様々な機能が備わっているため、必要な作業に注力できるようになります。

About

This repository is a framework made in PHP.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages