強力なセキュリティレイヤーに囲まれた多くの機能をカプセル化する、小さく、シンプルなPHP MVCフレームワークスケルトン。
MINIPHPは非常にシンプルなアプリケーションであり、小規模プロジェクトに役立ち、PHP MVCスケルトンを理解し、データの認証と承認、データの暗号化、およびセキュリティの概念の適用、消毒と検証、Ajaxの呼び出しなどを知ります。
それは完全なフレームワークでも、非常に基本的なフレームワークでもありませんが、複雑ではありません。どのプロジェクトでも簡単にインストール、理解、使用できます。
フレームワークの複雑さを削除するためにインデントされています。ルーティング、認証、承認、ユーザーセッション、Cookieの管理などは、私がゼロから発明したものではありませんが、他のフレームワークに既に実装されている概念の集約ですが、はるかに簡単な方法で構築されているため、理解し、さらに進めることができます。
より大きなアプリケーションを構築する必要があり、フレームワークで利用可能なほとんどの機能を活用する必要がある場合は、CakePHP、Laravel、Symphonyを見ることができます。
いずれにせよ、PHP MVCスケルトンを理解し、認証と承認の方法、セキュリティの問題、およびフレームワークを使用して自分のアプリケーションを構築する方法について学ぶ方法を知ることが重要です。
完全なドキュメントは、GitHub Automatic Page Generatorによって作成されたここにもあります。
ライブデモはこちらから入手できます。ライブデモは、このセクションのこのフレームワークの上に構築されたデモアプリケーション用です。 @EverterStraatに感謝します。
いくつかの機能は、デモでは機能しません。
Composer経由でインストールします
composer install
アプリケーションにリクエストを行うたびに、パブリックフォルダー内のindex.phpに向けられます。したがって、リクエストを行う場合: http://localhost/miniPHP/User/update/412 。これは分割され、翻訳されます
実際、htaccessはすべてがhttp://localhost/miniPHP後に来るすべてのものを分割し、querystring引数としてURLに追加します。したがって、このリクエストは、 http://localhost/miniPHP?url='User/update/412'に変換されます。
次に、 Appクラス、内部splitUrl()は、クエリ文字列$_GET['url']をコントローラー、アクションメソッド、およびアクションメソッドに合格した引数に分割します。
Appクラスでは、内部run()では、コントローラークラスからオブジェクトをインスタンス化し、存在する場合は引数を渡し、アクション対策方法を呼び出します。
Appクラスがコントローラーオブジェクトをintringした後、 $this->controller->startupProcess()メソッドを呼び出します。これは、3連続のイベント/メソッドをトリガーします。
initialize() :それを使用してコンポーネントを読み込みますbeforeAction() :コントローラーのアクションメソッドを呼び出す前に、ロジックアクションを実行するtriggerComponents() :Trigger startup()ロードされたコンポーネントのメソッドControllerクラスのコンストラクターをオーバーライドしないでください。代わりに、拡張クラスのinitialize() & beforeAction()メソッドをオーバーライドできます。
Constrcutorの起動プロセスがジョブを終了した後、要求されたアクション方法が呼び出され、引数が渡されます(もしあれば)。
コンポーネントはミドルウェアです。それらは、コントローラーの一部として使用される再利用可能なロジックを提供します。認証、承認、フォーム改ざん、および検証CSRFトークンは、コンポーネント内に実装されます。
これらのロジックをコントローラークラスから引き出し、これらのコンポーネント内にさまざまなタスクと検証をすべて保持することをお勧めします。
すべてのコンポーネントは、 Componentと呼ばれるベース/スーパークラスから継承されます。それぞれに定義されたタスクがあります。認証と承認のためにAuthと呼ばれる1つは2つのコンポーネントがあり、もう1つは他のセキュリティ問題のセキュリティと呼ばれます。
対処が非常に簡単で、内部コントローラーコンストラクターと呼ばれます。
ユーザーは正しい資格情報を持っていますか?
AuthComponentがユーザーセッションの世話をします。
アクセスする権利がありますか、それともXアクションを実行する権利がありますか? AUTHコンポーネントは、各コントローラーの承認を処理します。したがって、各コントローラーはisAuthorized()メソッドを実装する必要があります。あなたがする必要があるのは、 boolean値を返すことです。
したがって、たとえば、現在のユーザーが管理者であるかどうかを確認するために、次のようなことをします。
// AdminController
public function isAuthorized (){
$ role = Session:: getUserRole ();
if ( isset ( $ role ) && $ role === " admin " ){
return true ;
}
return false ;
}さらに進んで許可ルールを適用したい場合は、許可ルールの定義を担当するPermissionと呼ばれる強力なクラスがあります。このクラスを使用すると、「現在のコントローラーで特定のアクションメソッドを実行できる人」を定義できます。
したがって、たとえば、管理者がメモでアクションを実行できるようにするために、通常のユーザーはメモのみを編集できます。
// NotesController
public function isAuthorized (){
$ action = $ this -> request -> param ( ' action ' );
$ role = Session:: getUserRole ();
$ resource = " notes " ;
// only for admins
// they are allowed to perform all actions on $resource
Permission:: allow ( ' admin ' , $ resource , [ ' * ' ]);
// for normal users, they can edit only if the current user is the owner
Permission:: allow ( ' user ' , $ resource , [ ' edit ' ], ' owner ' );
$ noteId = $ this -> request -> data ( " note_id " );
$ config = [
" user_id " => Session:: getUserId (),
" table " => " notes " ,
" id " => $ noteId
];
// providing the current user's role, $resource, action method, and some configuration data
// Permission class will check based on rules defined above and return boolean value
return Permission:: check ( $ role , $ resource , $ action , $ config );
}これで、ユーザーの役割、リソース、および各アクションメソッドに基づいて承認を確認できます。
SecurityComponentは、さまざまなセキュリティタスクと検証を処理します。
要求方法を制限することが重要です。例として、フォーム値を受け入れるアクションメソッドがある場合、POSTリクエストのみが受け入れられます。 ajax、get、..etcについても同じアイデア。これを内部で行うことができますbeforeAction()メソッド。
// NotesController
public function beforeAction (){
parent :: beforeAction ();
$ actions = [ ' create ' , ' delete ' ];
$ this -> Security -> requireAjax ( $ actions );
$ this -> Security -> requirePost ( $ actions );
}また、すべてのリクエストがセキュリティで保護された接続を介して行う必要がある場合は、すべてのリクエストをHTTPの代わりにHTTPSにリダイレクトするために、コントローラー全体または特定のアクションを構成できます。
// NotesController
public function beforeAction (){
parent :: beforeAction ();
$ actions = [ ' create ' , ' delete ' ]; // specific action methods
$ actions = [ ' * ' ]; // all action methods
$ this -> Security -> requireSecure ( $ actions );
}要求が同じドメインから来ているかどうかをチェックして検証します。偽造することはできますが、セキュリティレイヤーの一部としてそれらを維持することは良いことです。
POSTリクエストから来る送信フォームを検証します。この方法の落とし穴は、予想されるフォームフィールド、またはPOSTリクエストで送信されるデータを定義する必要があることです。
デフォルトでは、Frameworkは、POSTリクエストが行われたときにフォームの改ざんを検証し、CSRFトークンがフォームフィールドで渡されることを確認します。この状況では、CSRFトークンに合格しなかった場合、セキュリティスレッドと見なされます。
// NotesController
public function beforeAction (){
parent :: beforeAction ();
$ action = $ this -> request -> param ( ' action ' );
$ actions = [ ' create ' , ' delete ' ];
$ this -> Security -> requireAjax ( $ actions );
$ this -> Security -> requirePost ( $ actions );
switch ( $ action ){
case " create " :
$ this -> Security -> config ( " form " , [ ' fields ' => [ ' note_text ' ]]);
break ;
case " delete " :
// If you want to disable validation for form tampering
// $this->Security->config("validateForm", false);
$ this -> Security -> config ( " form " , [ ' fields ' => [ ' note_id ' ]]);
break ;
}
}CSRFトークンは、提出されたフォームを検証し、偽造されていないことを確認するために重要です。ハッカーは、ユーザーをだましてWebサイトにリクエストを行うか、リンクをクリックするなどします。
それらは特定の期間(> = 1日)に有効であり、その後、ユーザーのセッションで再生され、保存されます。
CSRF検証はデフォルトで無効になっています。 CSRFトークンを検証する場合は、以下の例に示すように、 validateCsrfToken trueに割り当てます。 CSRF検証は、リクエストが投稿され、フォーム改ざんが有効になった場合に強制されます。
これで、すべてのリクエストでCSRFトークンを手動で検証する必要はありません。セキュリティコンポーネントは、セッションに保存されているトークンとリクエストでトークンを確認します。
// NotesController
public function beforeAction (){
parent :: beforeAction ();
$ action = $ this -> request -> param ( ' action ' );
$ actions = [ ' index ' ];
$ this -> Security -> requireGet ( $ actions );
switch ( $ action ){
case " index " :
$ this -> Security -> config ( " validateCsrfToken " , true );
break ;
}
}CSRFトークンはセッションごとに生成されます。 Hidden Formフィールドを追加するか、URLにクエリパラメーターとして追加できます。
形状
<input type="hidden" name="csrf_token" value="<?= Session::generateCsrfToken(); ?>" />
URL
<a href="<?= PUBLIC_ROOT . "?csrf_token=" . urlencode(Session::generateCsrfToken()); ?>">Link</a>
JavaScript
CSRFトークンをJavaScript変数に割り当てることもできます。
<script>config = <?= json_encode(Session::generateCsrfToken()); ?>;</script>
index.phpにリダイレクトされます。認証や許可なしにコントローラーを持ちたい場合、またはセキュリティコンポーネントが有効になっている場合など、これらのコンポーネントを制御する必要がある場合があります。これは、コントローラークラス内のOverride initialize()メソッドをオーバーライドし、必要なコンポーネントのみをロードすることで実行できます。
例1 :コンポーネント、認証または承認、セキュリティの検証をロードしないでください。
public function initialize (){
$ this -> loadComponents ([]);
}例2 :セキュリティと認証コンポーネントをロードしますが、アクションメソッド内で認証コンポーネントを使用する場合に備えて、認証および承認を許可しないでください。 LoginControllerは、ログインしたユーザーを必要とせずにページにアクセスする方法の例です。
public function initialize (){
$ this -> loadComponents ([
' Auth ' ,
' Security '
]);
}例3 :セキュリティ、およびAUTHコンポーネントをロードし、ユーザーを認証し、現在のコントローラーの承認を認めます。これは、コア/コントローラークラスのデフォルトの動作です
public function initialize (){
$ this -> loadComponents ([
' Auth ' => [
' authenticate ' => [ ' User ' ],
' authorize ' => [ ' Controller ' ]
],
' Security '
]);
}アクションメソッド内で、モデルに電話をかけてデータを取得したり、 [ビュー]フォルダー内のページをレンダリングできます。
// NotesController
public function index (){
// render full page with layout(header and footer)
$ this -> view -> renderWithLayouts (Config:: get ( ' VIEWS_PATH ' ) . " layout/default/ " , Config:: get ( ' VIEWS_PATH ' ) . ' notes/index.php ' );
// render page without layout
$ this -> view -> render (Config:: get ( ' VIEWS_PATH ' ) . ' notes/note.php ' );
// get the rendered page
$ html = $ this -> view -> render (Config:: get ( ' VIEWS_PATH ' ) . ' notes/note.php ' );
// render a json view
$ this -> view -> renderJson ( array ( " data " => $ html ));
}MVCでは、モデルは情報(データ)とビジネスルールを表します。ビューには、テキスト、フォーム入力などのユーザーインターフェイスの要素が含まれています。コントローラーは、モデルとビューの間の通信を管理します。ソース
Create、削除、更新、検証などのすべての操作は、モデルクラスで実装されています。
// NotesController
public function create (){
// get content of note submitted to a form
// then pass the content along with the current user to Note class
$ content = $ this -> request -> data ( " note_text " );
$ note = $ this -> note -> create (Session:: getUserId (), $ content );
if (! $ note ){
$ this -> view -> renderErrors ( $ this -> note -> errors ());
} else {
return $ this -> redirector -> root ( " Notes " );
}
}メモモデルで
// Notes Model
public function create ( $ userId , $ content ){
// using validation class(see below)
$ validation = new Validation ();
if (! $ validation -> validate ([ ' Content ' => [ $ content , " required|minLen(4)|maxLen(300) " ]])) {
$ this -> errors = $ validation -> errors ();
return false ;
}
// using database class to insert new note
$ database = Database:: openConnection ();
$ query = " INSERT INTO notes (user_id, content) VALUES (:user_id, :content) " ;
$ database -> prepare ( $ query );
$ database -> bindValue ( ' :user_id ' , $ userId );
$ database -> bindValue ( ' :content ' , $ content );
$ database -> execute ();
if ( $ database -> countRows () !== 1 ){
throw new Exception ( " Couldn't create note " );
}
return true ;
}フレームワークを使用して、おそらくログイン、登録、ログアウトを行います。これらのアクションは、App/Models/Login & App/Controllers/LoginControllerに実装されています。ほとんどの状況では、ログインアクションに関連するものを変更する必要はありません。フレームワークの動作を理解するだけです。
SSLをお持ちでない場合は、クライアント側で手動でデータを暗号化したい場合があります。もしそうなら、これも読んでください。
ユーザーが登録するたびに、暗号化されたユーザーIDと連結されたトークンとメールが送信されます。このトークンは24時間後に期限切れになります。これらのトークンを期限切れにし、登録された電子メールが有効期限が切れている場合は再利用する方がはるかに良いです。
パスワードは、PHP V5.5の最新アルゴリズムを使用してハッシュされます
$ hashedPassword = password_hash ( $ password , PASSWORD_DEFAULT , array ( ' cost ' => Config:: get ( ' HASH_COST_FACTOR ' )));ユーザーがパスワードを忘れた場合、彼はそれを復元できます。期限切れのトークンの同じアイデアがここにあります。
さらに、特定の期間中に忘れられたパスワードの試行数(5)を超えた場合(> = 10分)、特定の期間(> = 10分)のユーザーをブロックします。
ブルートフォース攻撃を調べることは、ハッカーが正しいパスワードを見つけるまで、可能なすべての入力の組み合わせを試みるときです。
解決:
Captchasは、自動ログインの防止に特に効果的です。 Captchaを使用して、素晴らしいPHP Captcha Libraryを使用しています。
IPアドレスのブロックは、考える最後のソリューションです。同じIPが異なる資格情報(> = 10)を使用して複数回ログインしなかった場合、IPアドレスがブロックされます。
PHPデータオブジェクト(PDO)は、データベースクエリの準備と実行に使用されます。 Databaseクラス内には、複雑さを隠すさまざまな方法があり、データベースオブジェクトをインスタンス化し、数行で準備、バインド、実行します。
SELECT, INSERT, UPDATE, DELETE十分ですAdminクラスで言及されています。utf8mb4を使用する必要があります。utf8 CHARSETは、1〜3バイトで構成されるUTF-8エンコードされたシンボルのみを保存します。しかし、4バイトのシンボルではできません。utf8です。ただし、 utf8mb4にアップグレードしたい場合は、次のリンクに従います。utf8mb4に変更することを忘れないでくださいEncryptionクラスは、データの暗号化と復号化を担当します。暗号化は、Cookie、ユーザーID、投稿ID、..etcなどに適用されます。暗号化された文字列は認証されており、暗号化するたびに異なります。
検証は、ユーザー入力を検証するための小さなライブラリです。すべての検証ルールは、 Validationクラス内にあります。
$ validation = new Validation ();
// there are default error messages for each rule
// but, you still can define your custom error message
$ validation -> addRuleMessage ( " emailUnique " , " The email you entered is already exists " );
if (! $ validation -> validate ([
" User Name " => [ $ name , " required|alphaNumWithSpaces|minLen(4)|maxLen(30) " ],
" Email " => [ $ email , " required|email|emailUnique|maxLen(50) " ],
' Password ' => [ $ password , " required|equals( " . $ confirmPassword . " )|minLen(6)|password " ],
' Password Confirmation ' => [ $ confirmPassword , ' required ' ]])) {
var_dump ( $ validation -> errors ());
}Handlerクラスは、すべての例外とエラーを処理する責任があります。ロガーを使用してエラーを記録します。すべてのエラーがapp/logs/log.txtに記録されて保存されるため、エラーレポートはデフォルトでオフになります。
エラーが発生した場合、または例外がスローされた場合、アプリケーションにシステム内部エラー(500)が表示されます。
何でもログに記録して、 app/log/log.txtに保存できる場所。障害、エラー、例外、またはその他の悪意のあるアクションまたは攻撃を書くことができます。
Logger:: log ( " COOKIE " , self :: $ userId . " is trying to login using invalid cookie " , __FILE__ , __LINE__ );電子メールは、電子メールを送信するための別のライブラリであるSMTPを介してPHPMailerを使用して送信されます。 phpのmail()関数を使用しないでください。
App/Configには、メインアプリケーション構成用のconfig.phpと呼ばれる2つのファイルと、 javascript.phpと呼ばれるjavascript用の2つのファイルがあります。 JavaScript構成は、 Footer.phpのJavaScript変数に割り当てられます。
リクエストを送信して応答を受信するために、Ajaxの呼び出しに依存する可能性があります。このフレームワークは、アクションを実行するためのAJAXリクエストに大きく依存していますが、小さな微調整で通常のリクエストに対して同じことを行うことができます。
configオブジェクトは、footer.phpのキー値ペアに割り当てられます。これらのキー価値ペアはConfig::setJsConfig('key', "value");を使用してサーバー側のコードに追加できます。 、次にオブジェクトを構成するように割り当てられます。
Ajax AJAXリクエストを送信するための2つの主要な機能を備えた名前空間。 1つは通常のAJAXコール用、もう1つはファイルをアップロードするためのものです。
さまざまな関数を備えた名前空間がエラー、シリアル化、リダイレクト、encodeHtmlなどを表示します。
アプリ現在のページのJavaScriptイベント全体を開始するために使用される名前空間
イベントユーザーがリンクをクリックして作成、削除、または更新するときなど、発生する可能性のあるすべてのイベントを宣言するために使用される名前空間。
実際の状況でフレームワークを使用する方法を示すために、フレームワークには、ユーザープロファイル管理、ダッシュボード、ニュースフィード、アップロード、ダウンロードファイル、投稿とコメント、ページネーション、管理パネル、システムバックアップの管理、通知、レポートバグなどなどの機能の実装が伴います。
ステップ:
資格情報を使用して、 app/config/config.phpで構成ファイルを編集します
_インストールディレクトリでSQLクエリを順番に実行します
ログイン
電子メールのセットアップ
smtpアカウントデータをApp/config/config.phpで構成する必要があります。ただし、SMTPアカウントをお持ちでない場合は、Loggerを使用してApp/logs/log.txtにメールを保存します。
それを行うには、コア/電子メールで、 $mail->Send() &comment Logger::log("EMAIL", $mail->Body);
すべてのユーザーは、自分の名前、電子メール、パスワードを変更できます。また、プロファイル画像をアップロードします(つまり、最初にdefault.pngに割り当てられます)。
ユーザーが電子メールを変更するように要求するたびに、通知がユーザーの古いメールと新しい電子メールに送信されます。
古い電子メールに送信された通知は、ユーザーに電子メールの変更を取り消す機会を与え、新しい電子メールに送信された通知は確認を求めています。ユーザーは、変更を確認するまで、古いメールでログインできます。
これは、 UserController 、method updateProfileInfo() 、 revokeEmail() 、& updateEmail()で行われます。ほとんどの状況では、これらの方法の動作を変更する必要はありません。
ファイルをアップロードしてダウンロードできます。
file_uploadsをtrueに設定しますupload_max_filesize, max_file_uploads, post_max_size設定しますニュースフィードは、Twitterでのツイート、およびGithubで問題を開くときのような投稿と考えてください。
これらは、このフレームワークの一番上に実装されています。
管理者は、通常のユーザーができない場合にアクションを実行できます。削除、編集、ニュースフィード、投稿、またはコメントを作成できます。また、すべてのユーザープロファイルを制御し、バックアップを作成および復元します。
管理者のみが登録されているすべてのユーザーを見ることができます。情報を削除、編集できます。
ほとんどの状況では、システムのバックアップを作成し、いつでも必要なときにそれらを復元する必要があります。
これは、MySqldumpを使用してバックアップを作成および復元することによって行われます。すべてのバックアップはアプリ/バックアップに保存されます。
Facebookで赤い通知を見ましたか、それともTwitterで青い通知を見ましたか?同じアイデアがここにあります。しかし、代わりにトリガーを使用して実装されています。トリガーは_インストール/トリガーズSQLで定義されます。
したがって、ユーザーが新しいニュースフィード、投稿、またはファイルのアップロードを作成するたびに、これにより他のすべてのユーザーのカウントが増加し、ナビゲーションバーに赤い通知が表示されます。
ユーザーはバグ、機能、拡張機能を報告できます。フォームを送信すると、 app/config/config.phpで定義されたADMIN_EMAILにメールが送信されます
簡単なTODOアプリケーションを構築したいとしましょう。ここでは、Ajax呼び出しなしでフレームワークを使用してTODOアプリを作成する方法を段階的に進めます。
(1)上記のインストールセットアップ手順に従った場合、初期ユーザーアカウントの作成に問題はないはずです。
(2)int、content varchar、 usersとしてのIDを使用してテーブルを作成するテーブル
CREATE TABLE ` todo ` (
` id ` int ( 11 ) NOT NULL AUTO_INCREMENT,
` user_id ` int ( 11 ) NOT NULL ,
` content ` varchar ( 512 ) NOT NULL ,
PRIMARY KEY ( ` id ` ),
FOREIGN KEY ( ` user_id ` ) REFERENCES ` users ` ( ` id ` ) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB DEFAULT CHARSET = utf8 COLLATE = utf8_general_ci;(3)todocontrollerを作成します
アプリ/コントローラー内のTodoController.phpというファイルを作成します
class TodoController extends Controller{
// override this method to perform any logic before calling action method as explained above
public function beforeAction (){
parent :: beforeAction ();
// define the actions in this Controller
$ action = $ this -> request -> param ( ' action ' );
// restrict the request to action methods
// $this->Security->requireAjax(['create', 'delete']);
$ this -> Security -> requirePost ([ ' create ' , ' delete ' ]);
// define the expected form fields for every action if exist
switch ( $ action ){
case " create " :
// you can exclude form fields if you don't care if they were sent with form fields or not
$ this -> Security -> config ( " form " , [ ' fields ' => [ ' content ' ]]);
break ;
case " delete " :
// If you want to disable validation for form tampering
// $this->Security->config("validateForm", false);
$ this -> Security -> config ( " form " , [ ' fields ' => [ ' todo_id ' ]]);
break ;
}
}
public function index (){
$ this -> view -> renderWithLayouts (Config:: get ( ' VIEWS_PATH ' ) . " layout/todo/ " , Config:: get ( ' VIEWS_PATH ' ) . ' todo/index.php ' );
}
public function create (){
$ content = $ this -> request -> data ( " content " );
$ todo = $ this -> todo -> create (Session:: getUserId (), $ content );
if (! $ todo ){
// in case of normal post request
Session:: set ( ' errors ' , $ this -> todo -> errors ());
return $ this -> redirector -> root ( " Todo " );
// in case of ajax
// $this->view->renderErrors($this->todo->errors());
} else {
// in case of normal post request
Session:: set ( ' success ' , " Todo has been created " );
return $ this -> redirector -> root ( " Todo " );
// in case of ajax
// $this->view->renderJson(array("success" => "Todo has been created"));
}
}
public function delete (){
$ todoId = Encryption:: decryptIdWithDash ( $ this -> request -> data ( " todo_id " ));
$ this -> todo -> delete ( $ todoId );
// in case of normal post request
Session:: set ( ' success ' , " Todo has been deleted " );
return $ this -> redirector -> root ( " Todo " );
// in case of ajax
// $this->view->renderJson(array("success" => "Todo has been deleted"));
}
public function isAuthorized (){
$ action = $ this -> request -> param ( ' action ' );
$ role = Session:: getUserRole ();
$ resource = " todo " ;
// only for admins
Permission:: allow ( ' admin ' , $ resource , [ ' * ' ]);
// only for normal users
Permission:: allow ( ' user ' , $ resource , [ ' delete ' ], ' owner ' );
$ todoId = $ this -> request -> data ( " todo_id " );
if (! empty ( $ todoId )){
$ todoId = Encryption:: decryptIdWithDash ( $ todoId );
}
$ config = [
" user_id " => Session:: getUserId (),
" table " => " todo " ,
" id " => $ todoId ];
return Permission:: check ( $ role , $ resource , $ action , $ config );
}
} (4)アプリ/モデルでTodo.phpと呼ばれるメモモデルクラスを作成します
class Todo extends Model{
public function getAll (){
$ database = Database:: openConnection ();
$ query = " SELECT todo.id AS id, users.id AS user_id, users.name AS user_name, todo.content " ;
$ query .= " FROM users, todo " ;
$ query .= " WHERE users.id = todo.user_id " ;
$ database -> prepare ( $ query );
$ database -> execute ();
$ todo = $ database -> fetchAllAssociative ();
return $ todo ;
}
public function create ( $ userId , $ content ){
// using validation class
$ validation = new Validation ();
if (! $ validation -> validate ([ ' Content ' => [ $ content , " required|minLen(4)|maxLen(300) " ]])) {
$ this -> errors = $ validation -> errors ();
return false ;
}
// using database class to insert new todo
$ database = Database:: openConnection ();
$ query = " INSERT INTO todo (user_id, content) VALUES (:user_id, :content) " ;
$ database -> prepare ( $ query );
$ database -> bindValue ( ' :user_id ' , $ userId );
$ database -> bindValue ( ' :content ' , $ content );
$ database -> execute ();
if ( $ database -> countRows () !== 1 ){
throw new Exception ( " Couldn't create todo " );
}
return true ;
}
public function delete ( $ id ){
$ database = Database:: openConnection ();
$ database -> deleteById ( " todo " , $ id );
if ( $ database -> countRows () !== 1 ){
throw new Exception ( " Couldn't delete todo " );
}
}
}(5)内部ビュー/
(a) header.php & footer.php内部ビュー/レイアウト/todoを作成します
<! DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf- 8 ">
<meta http-equiv="X- UA -Compatible" content=" IE =edge">
<meta name="viewport" content="width=device-width, initial-scale= 1 ">
<meta name="description" content="mini PHP ">
<meta name="author" content="mini PHP ">
<title>mini PHP </title>
<!-- Stylesheets -->
<link rel="stylesheet" href=" <?= PUBLIC_ROOT ; ?> css/bootstrap.min.css">
<link rel="stylesheet" href=" <?= PUBLIC_ROOT ; ?> css/sb-admin-2.css">
<link rel="stylesheet" href=" <?= PUBLIC_ROOT ; ?> css/font-awesome.min.css" rel="stylesheet" type="text/css">
<!-- Styles for ToDo Application -->
<style>
.todo_container{
width:80%;
margin: 0 auto;
margin-top: 5%
}
#todo-list li{
list-style-type: none;
border: 1px solid #e7e7e7;
padding: 3px;
margin: 3px;
}
#todo-list li:hover{
background-color: #eee;
}
form button{
float:right;
margin: 3px;
}
form:after{
content: '';
display: block;
clear: both;
}
</style>
</head>
<body> <!-- footer -->
<script src="https: //ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<!--<script src=" <?= PUBLIC_ROOT ; ?> js/jquery.min.js"></script>-->
<script src=" <?= PUBLIC_ROOT ; ?> js/bootstrap.min.js"></script>
<script src=" <?= PUBLIC_ROOT ; ?> js/sb-admin-2.js"></script>
<script src=" <?= PUBLIC_ROOT ; ?> js/main.js"></script>
<!-- Assign CSRF Token to JS variable -->
<?php Config:: setJsConfig ( ' csrfToken ' , Session:: generateCsrfToken ()); ?>
<!-- Assign all configration variables -->
<script>config = <?= json_encode (Config:: getJsConfig ()); ?> ;</script>
<!-- Run the application -->
<script>$(document).ready(app.init());</script>
<?php Database:: closeConnection (); ?>
</body>
</html> (b) index.phpを持つ内部ビュー/作成TODOフォルダーを作成します。これには、TODOリストが含まれます。
<div class="todo_container">
<h2> TODO Application</h2>
<!-- in case of normal post request -->
<form action= " <?= PUBLIC_ROOT . " Todo/create " ?> " method="post">
<label>Content <span class="text-danger " >*</span></label>
<textarea name= " content" class ="form-control " required placeholder= " What are you thinking? " ></textarea>
<input type='hidden' name = "csrf_token" value = " <?= Session:: generateCsrfToken (); ?> ">
<button type="submit" name="submit" value="submit" class="btn btn-success">Create</button>
</form>
<!-- in case of ajax request
<form action= "#" id="form-create-todo" method="post">
<label>Content <span class="text-danger">*</span></label>
<textarea name="content" class="form-control" required placeholder="What are you thinking?"></textarea>
<button type="submit" name="submit" value="submit" class="btn btn-success">Create</button>
</form>
-->
<br>
<?php
// display success or error messages in session
if (! empty (Session:: get ( ' success ' ))){
echo $ this -> renderSuccess (Session:: getAndDestroy ( ' success ' ));
} else if (! empty (Session:: get ( ' errors ' ))){
echo $ this -> renderErrors (Session:: getAndDestroy ( ' errors ' ));
}
?>
<br><hr><br>
<ul id="todo-list">
<?php
$ todoData = $ this -> controller -> todo -> getAll ();
foreach ( $ todoData as $ todo ){
?>
<li>
<p> <?= $ this -> autoLinks ( $ this -> encodeHTMLWithBR ( $ todo [ " content " ])); ?> </p>
<!-- in case of normal post request -->
<form action= " <?= PUBLIC_ROOT . " Todo/delete " ?> " method="post">
<input type='hidden' name= "todo_id" value=" <?= " todo- " . Encryption:: encryptId ( $ todo [ " id " ]); ?> ">
<input type='hidden' name = "csrf_token" value = " <?= Session:: generateCsrfToken (); ?> ">
<button type="submit" name="submit" value="submit" class="btn btn-xs btn-danger">Delete</button>
</form>
<!-- in case of ajax request
<form class="form-delete-todo" action= "#" method="post">
<input type='hidden' name= "todo_id" value=" <?= " todo- " . Encryption:: encryptId ( $ todo [ " id " ]); ?> ">
<button type="submit" name="submit" value="submit" class="btn btn-xs btn-danger">Delete</button>
</form>
-->
</li>
<?php } ?>
</ul>
</div>(6)ajaxコールを送信し、応答を処理するJavaScriptコード
// first, we need to initialize the todo events whenever the application initalized
// the app.init() is called in footer.php, see views/layout/todo/footer.php
var app = {
init : function ( ) {
events . todo . init ( ) ;
}
} ;
// inside var events = {....} make a new key called "todo"
var events = {
// ....
todo : {
init : function ( ) {
events . todo . create ( ) ;
events . todo . delete ( ) ;
} ,
create : function ( ) {
$ ( "#form-create-todo" ) . submit ( function ( e ) {
e . preventDefault ( ) ;
ajax . send ( "Todo/create" , helpers . serialize ( this ) , createTodoCallBack , "#form-create-todo" ) ;
} ) ;
function createTodoCallBack ( PHPData ) {
if ( helpers . validateData ( PHPData , "#form-create-todo" , "after" , "default" , "success" ) ) {
alert ( PHPData . success + " refresh the page to see the results" ) ;
}
}
} ,
delete : function ( ) {
$ ( "#todo-list form.form-delete-todo" ) . submit ( function ( e ) {
e . preventDefault ( ) ;
if ( ! confirm ( "Are you sure?" ) ) { return ; }
var cur_todo = $ ( this ) . parent ( ) ;
ajax . send ( "Todo/delete" , helpers . serialize ( this ) , deleteTodoCallBack , cur_todo ) ;
function deleteTodoCallBack ( PHPData ) {
if ( helpers . validateData ( PHPData , cur_todo , "after" , "default" , "success" ) ) {
$ ( cur_todo ) . remove ( ) ;
alert ( PHPData . success ) ;
}
}
} ) ;
}
}
}勉強中に自由な時期にこのスクリプトを書きました。これは無料で、未払いです。多くの開発者があらゆるソフトウェアに対して非常に失礼な行動をしているのを見てきたので、私はこれを言っています。理由がわからない?!誰もが文句を言う傾向があり、厳しい言葉を言っています。私はフィードバックを受け入れますが、良い敬意を払っています。
同じことを行う(それ以下ではないにしても)他の多くのスクリプトがオンラインで購入されており、著者はそれから良いお金を稼いでいますが、私はそれを公開し、誰でも利用できることを選択します。
あなたが何かを学んだ場合、または私があなたの時間を節約した場合は、言葉を広めてプロジェクトをサポートしてください。
新しい問題を作成したり、GitHubでプルリクエストを送信したりすることで貢献するか、[email protected]にメールを送信できます。
MITライセンスの下で構築されています。