โครงกระดูกเฟรมเวิร์ก PHP MVC ขนาดเล็กที่เรียบง่ายซึ่งห่อหุ้มคุณสมบัติมากมายที่ล้อมรอบด้วยเลเยอร์ความปลอดภัยที่ทรงพลัง
MiniPHP เป็นแอปพลิเคชั่นที่ง่ายมากมีประโยชน์สำหรับโครงการขนาดเล็กช่วยให้เข้าใจโครงกระดูก PHP MVC รู้วิธีการรับรองความถูกต้องและอนุญาตเข้ารหัสข้อมูลและใช้แนวคิดความปลอดภัยการฆ่าเชื้อและการตรวจสอบความถูกต้อง
มันไม่ใช่เฟรมเวิร์กเต็มรูปแบบหรือเป็นพื้นฐานพื้นฐาน แต่ก็ไม่ซับซ้อน คุณสามารถติดตั้งเข้าใจและใช้งานได้ในโครงการใด ๆ ของคุณ
มันเยื้องที่จะลบความซับซ้อนของเฟรมเวิร์ก สิ่งต่าง ๆ เช่นการกำหนดเส้นทางการรับรองความถูกต้องการอนุญาตจัดการเซสชันผู้ใช้และคุกกี้และอื่น ๆ ไม่ใช่สิ่งที่ฉันคิดค้นมาตั้งแต่เริ่มต้นอย่างไรก็ตามพวกเขาเป็นการรวมตัวกันของแนวคิดที่นำไปใช้ในกรอบอื่น ๆ แต่สร้างขึ้นในวิธีที่ง่ายกว่ามากดังนั้นคุณสามารถเข้าใจได้
หากคุณต้องการสร้างแอปพลิเคชั่นที่ใหญ่ขึ้นและใช้ประโยชน์จากคุณสมบัติส่วนใหญ่ที่มีอยู่ในเฟรมเวิร์กคุณสามารถดู Cakephp, Laravel, Symphony
ไม่ว่าจะด้วยวิธีใดสิ่งสำคัญคือต้องเข้าใจโครงกระดูก PHP MVC และรู้วิธีการรับรองความถูกต้องและอนุญาตเรียนรู้เกี่ยวกับปัญหาด้านความปลอดภัยและคุณจะเอาชนะได้อย่างไรและวิธีการสร้างแอปพลิเคชันของคุณโดยใช้เฟรมเวิร์ก
เอกสารฉบับเต็มสามารถพบได้ที่นี่ - สร้างโดย GitHub Automatic Page Generator
มีการสาธิตสดที่นี่ การสาธิตสดสำหรับแอปพลิเคชันตัวอย่างที่สร้างขึ้นบนกรอบนี้ในส่วนนี้ ขอบคุณ @EvertStraat
คุณสมบัติบางอย่างไม่ได้ผลในการสาธิต
ติดตั้งผ่านนักแต่งเพลง
composer install
เมื่อใดก็ตามที่คุณร้องขอแอปพลิเคชันมันจะถูกส่งไปยัง index.php ภายในโฟลเดอร์สาธารณะ ดังนั้นหากคุณร้องขอ: http://localhost/miniPHP/User/update/412 สิ่งนี้จะถูกแยกและแปลเป็น
ในความเป็นจริง htaccess แยกทุกอย่างมาหลังจาก http://localhost/miniPHP และเพิ่มลงใน URL เป็นอาร์กิวเมนต์ querystring ดังนั้นคำขอนี้จะถูกแปลงเป็น: http://localhost/miniPHP?url='User/update/412'
จากนั้นคลาส App ภายใน splitUrl() จะแยกสตริงแบบสอบถาม $_GET['url'] เป็นคอนโทรลเลอร์วิธีการดำเนินการและอาร์กิวเมนต์ใด ๆ ที่ส่งผ่านไปยังวิธีการดำเนินการ
ในคลาส App ภายใน run() มันจะยกตัวอย่างวัตถุจากคลาสคอนโทรลเลอร์และทำการเรียกใช้วิธีการดำเนินการผ่านอาร์กิวเมนต์ใด ๆ หากมีอยู่
หลังจากวัตถุ App คลาส Intantiates คอนโทรลเลอร์มันจะเรียก $this->controller->startupProcess() ซึ่งจะเรียก 3 เหตุการณ์/วิธีการติดต่อกัน:
initialize() : ใช้เพื่อโหลดส่วนประกอบbeforeAction() : ดำเนินการลอจิกใด ๆ ก่อนที่จะเรียกใช้วิธีการกระทำของคอนโทรลเลอร์triggerComponents() : วิธีการเริ่มต้น () ของส่วนประกอบที่โหลด ตัวสร้างของคลาส Controller ไม่ควร ถูกแทนที่ แต่คุณสามารถแทนที่วิธีการ initialize() & beforeAction() ในคลาสขยาย
หลังจากกระบวนการเริ่มต้นของ Constrcutor เสร็จสิ้นงานแล้ววิธีการดำเนินการที่ร้องขอจะถูกเรียกและอาร์กิวเมนต์จะถูกส่งผ่าน (ถ้ามี)
ส่วนประกอบคือ Middlewares พวกเขาให้ตรรกะที่นำกลับมาใช้ใหม่เพื่อใช้เป็นส่วนหนึ่งของคอนโทรลเลอร์ การรับรองความถูกต้องการอนุญาตการดัดแปลงและตรวจสอบความถูกต้องของโทเค็น CSRF จะถูกนำไปใช้ภายในส่วนประกอบ
เป็นการดีกว่าที่จะดึงชิ้นส่วนของตรรกะเหล่านี้ออกจากคลาสคอนโทรลเลอร์และเก็บงานและการตรวจสอบที่หลากหลายไว้ในส่วนประกอบเหล่านี้
ทุกองค์ประกอบสืบทอดมาจากคลาสฐาน/ซุปเปอร์ที่เรียกว่า Component แต่ละคนมีงานที่กำหนด มีสององค์ประกอบหนึ่งสำหรับเรียกว่า auth สำหรับการรับรองความถูกต้องและการอนุญาตและอีกองค์ประกอบหนึ่งเรียก ว่าความปลอดภัย สำหรับปัญหาความปลอดภัยอื่น ๆ
พวกเขาง่ายมากที่จะจัดการและพวกเขาจะถูกเรียกภายในคอนโทรลเลอร์คอนโทรลเลอร์
ผู้ใช้มีข้อมูลรับรองที่ถูกต้องหรือไม่?
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 );
}ตอนนี้คุณสามารถตรวจสอบการอนุญาตตามบทบาทของผู้ใช้ทรัพยากรและสำหรับแต่ละวิธีการดำเนินการ
คอมโพเนนต์ความปลอดภัยดูแลงานด้านความปลอดภัยและการตรวจสอบต่างๆ
สิ่งสำคัญคือการ จำกัด วิธีการร้องขอ ตัวอย่างเช่นหากคุณมีวิธีการดำเนินการที่ยอมรับค่าฟอร์มดังนั้นจะยอมรับเฉพาะคำขอโพสต์เท่านั้น แนวคิดเดียวกันสำหรับ AJAX, Get, ..etc คุณสามารถทำได้ภายในวิธีการ beforeAction()
// NotesController
public function beforeAction (){
parent :: beforeAction ();
$ actions = [ ' create ' , ' delete ' ];
$ this -> Security -> requireAjax ( $ actions );
$ this -> Security -> requirePost ( $ actions );
}นอกจากนี้หากคุณต้องการให้มีการร้องขอทั้งหมดผ่านการเชื่อมต่อที่ปลอดภัยคุณสามารถกำหนดค่าคอนโทรลเลอร์ทั้งหมดหรือการกระทำที่เฉพาะเจาะจงเพื่อเปลี่ยนเส้นทางคำขอทั้งหมดไปยัง HTTPS แทน HTTP
// NotesController
public function beforeAction (){
parent :: beforeAction ();
$ actions = [ ' create ' , ' delete ' ]; // specific action methods
$ actions = [ ' * ' ]; // all action methods
$ this -> Security -> requireSecure ( $ actions );
}ตรวจสอบและตรวจสอบว่าคำขอมาจากโดเมนเดียวกัน แม้ว่าพวกเขาจะสามารถปลอมได้ แต่ก็เป็นการดีที่จะทำให้พวกเขาเป็นส่วนหนึ่งของเลเยอร์ความปลอดภัยของเรา
ตรวจสอบความถูกต้องของแบบฟอร์มที่ส่งมาจากคำขอโพสต์ ข้อผิดพลาดของวิธีนี้คือคุณต้องกำหนดฟิลด์ฟอร์มที่คาดหวังหรือข้อมูลที่จะส่งด้วยคำขอโพสต์
โดยค่าเริ่มต้นเฟรมเวิร์กจะตรวจสอบความถูกต้องสำหรับการดัดแปลงแบบฟอร์มเมื่อมีการร้องขอการโพสต์และจะทำให้แน่ใจว่าโทเค็น 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 มีความสำคัญในการตรวจสอบแบบฟอร์มที่ส่งและเพื่อให้แน่ใจว่าพวกเขาไม่ได้แกล้งทำ แฮ็กเกอร์สามารถหลอกให้ผู้ใช้ส่งคำขอไปยังเว็บไซต์หรือคลิกที่ลิงค์และอื่น ๆ
พวกเขาจะถูกต้องในช่วงเวลาที่กำหนด (> = 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 ถูกสร้างขึ้นต่อเซสชัน คุณสามารถเพิ่มฟิลด์ฟอร์มที่ซ่อนอยู่หรือใน URL เป็นพารามิเตอร์แบบสอบถาม
รูปร่าง
<input type="hidden" name="csrf_token" value="<?= Session::generateCsrfToken(); ?>" />
url
<a href="<?= PUBLIC_ROOT . "?csrf_token=" . urlencode(Session::generateCsrfToken()); ?>">Link</a>
จาวาสคริปต์
นอกจากนี้คุณยังสามารถกำหนดโทเค็น CSRF ให้กับตัวแปร JavaScript
<script>config = <?= json_encode(Session::generateCsrfToken()); ?>;</script>
index.php ในโฟลเดอร์รูทสาธารณะ บางครั้งคุณต้องมีการควบคุมส่วนประกอบเหล่านี้เช่นเมื่อต้องการมีคอนโทรลเลอร์โดยไม่มีการรับรองความถูกต้องหรือการอนุญาตหรือเปิดใช้งานส่วนประกอบความปลอดภัย สิ่งนี้สามารถทำได้โดยวิธีการแทนที่ initialize() ในคลาสคอนโทรลเลอร์ของคุณและโหลดส่วนประกอบที่จำเป็นเท่านั้น
ตัวอย่างที่ 1 : อย่าโหลดส่วนประกอบใด ๆ ไม่มีการรับรองความถูกต้องหรือการอนุญาตหรือการตรวจสอบความปลอดภัย
public function initialize (){
$ this -> loadComponents ([]);
}ตัวอย่างที่ 2 : โหลดความปลอดภัยและส่วนประกอบ Auth แต่อย่ารับรองความถูกต้องและอนุญาตในกรณีที่คุณต้องการใช้ส่วนประกอบ Auth ภายในวิธีการดำเนินการ LoginController เป็นตัวอย่างเกี่ยวกับ วิธีการเข้าถึงหน้าเว็บโดยไม่ต้องใช้ผู้ใช้ที่เข้าสู่ระบบ
public function initialize (){
$ this -> loadComponents ([
' Auth ' ,
' Security '
]);
}ตัวอย่างที่ 3 : โหลดความปลอดภัยและส่วนประกอบรับรองความถูกต้องและรับรองความถูกต้องของผู้ใช้และอนุญาตสำหรับคอนโทรลเลอร์ปัจจุบัน นี่คือพฤติกรรมเริ่มต้นในคลาส Core/Controller
public function initialize (){
$ this -> loadComponents ([
' Auth ' => [
' authenticate ' => [ ' User ' ],
' authorize ' => [ ' Controller ' ]
],
' Security '
]);
}ภายในวิธีการดำเนินการคุณสามารถโทรไปยังแบบจำลองเพื่อรับข้อมูลและ/หรือแสดงหน้าภายในโฟลเดอร์ Views Views
// 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 โมเดลแสดงถึงข้อมูล (ข้อมูล) และกฎเกณฑ์ทางธุรกิจ มุมมองมีองค์ประกอบของส่วนต่อประสานผู้ใช้เช่นข้อความ, อินพุตแบบฟอร์ม; และคอนโทรลเลอร์จัดการการสื่อสารระหว่างโมเดลและมุมมอง แหล่งที่มา
การดำเนินการทั้งหมดเช่นสร้าง, ลบ, อัปเดตและการตรวจสอบความถูกต้องถูกนำไปใช้ในคลาสโมเดล
// 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
// 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 ;
}การใช้เฟรมเวิร์กคุณอาจจะเข้าสู่ระบบลงทะเบียนและออกจากระบบ การกระทำเหล่านี้ถูกนำไปใช้ใน แอพ/โมเดล/ล็อกอิน & แอพ/คอนโทรลเลอร์/LoginController ในสถานการณ์ส่วนใหญ่คุณไม่จำเป็นต้องแก้ไขสิ่งใดที่เกี่ยวข้องกับการกระทำของล็อกอินเพียงแค่เข้าใจพฤติกรรมของกรอบ
หมายเหตุ หากคุณไม่มี SSL คุณต้องการเข้ารหัสข้อมูลด้วยตนเองที่ด้านไคลเอนต์ถ้าเป็นเช่นนั้นอ่านสิ่งนี้และสิ่งนี้
เมื่อใดก็ตามที่ผู้ใช้ลงทะเบียนอีเมลจะถูกส่งด้วยโทเค็นที่ต่อกันด้วยรหัสผู้ใช้ที่เข้ารหัส โทเค็นนี้จะหมดอายุหลังจาก 24 ชั่วโมง เป็นการดีกว่ามากที่จะหมดอายุโทเค็นเหล่านี้และใช้อีเมลที่ลงทะเบียนอีกครั้งหากพวกเขาหมดอายุ
รหัสผ่าน จะถูกแฮชโดยใช้อัลกอริทึมล่าสุดใน PHP v5.5
$ hashedPassword = password_hash ( $ password , PASSWORD_DEFAULT , array ( ' cost ' => Config:: get ( ' HASH_COST_FACTOR ' )));หากผู้ใช้ลืมรหัสผ่านเขาสามารถกู้คืนได้ ความคิดเดียวกันของโทเค็นที่หมดอายุไปที่นี่
นอกจากนี้ผู้ใช้บล็อกในช่วงเวลาที่กำหนด (> = 10 นาที) หากเขาเกินจำนวนรหัสผ่านที่ถูกลืม (5) ในช่วงระยะเวลาหนึ่ง (> = 10 นาที)
การโจมตีที่ดุร้ายของเดรัจฉานคือเมื่อแฮ็กเกอร์พยายามรวมการป้อนข้อมูลที่เป็นไปได้ทั้งหมดจนกว่าเขาจะพบรหัสผ่านที่ถูกต้อง
สารละลาย:
Captchas มีประสิทธิภาพโดยเฉพาะอย่างยิ่งในการป้องกันการเข้าสู่ระบบอัตโนมัติ ใช้ Captcha ห้องสมุด PHP Captcha ที่ยอดเยี่ยม
การปิดกั้นที่อยู่ IP เป็นทางออกสุดท้ายที่จะคิด ที่อยู่ IP จะถูกบล็อกหาก IP เดียวกันไม่สามารถเข้าสู่ระบบได้หลายครั้งโดยใช้ข้อมูลรับรองที่แตกต่างกัน (> = 10)
วัตถุข้อมูล PHP (PDO) ใช้สำหรับการเตรียมและดำเนินการสืบค้นฐานข้อมูล ภายในคลาส Database มีวิธีการต่าง ๆ ที่ซ่อนความซับซ้อนและให้คุณยกตัวอย่างวัตถุฐานข้อมูลเตรียมผูกมัดและดำเนินการในไม่กี่บรรทัด
SELECT, INSERT, UPDATE, DELETE เพียงพอสำหรับผู้ใช้Adminutf8mb4 ในระดับฐานข้อมูลutf8 charset ของ MySQL มีเพียงสัญลักษณ์ที่เข้ารหัส UTF-8 ที่ประกอบด้วยหนึ่งถึงสามไบต์ แต่มันไม่สามารถสำหรับสัญลักษณ์ที่มีสี่ไบต์utf8 แต่ถ้าคุณต้องการอัพเกรดเป็น utf8mb4 ให้ติดตามลิงค์เหล่านี้:utf8mb4 คลาส Encryption มีหน้าที่รับผิดชอบในการเข้ารหัสและถอดรหัสข้อมูล การเข้ารหัสถูกนำไปใช้กับสิ่งต่าง ๆ เช่นคุกกี้, 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 รับผิดชอบในการจัดการข้อยกเว้นและข้อผิดพลาดทั้งหมด มันจะใช้เครื่องบันทึกเพื่อบันทึกข้อผิดพลาด การรายงานข้อผิดพลาดจะถูกปิดโดยค่าเริ่มต้นเนื่องจากข้อผิดพลาดทุกข้อจะถูกบันทึกและบันทึกใน แอป/logs/log.txt
หากพบข้อผิดพลาดหรือมีข้อยกเว้นแอปพลิเคชันจะแสดงข้อผิดพลาดภายในของระบบ (500)
สถานที่ที่คุณสามารถบันทึกอะไรก็ได้และบันทึกลงใน แอป/log/log.txt คุณสามารถเขียนความล้มเหลวข้อผิดพลาดข้อยกเว้นหรือการกระทำหรือการโจมตีที่เป็นอันตรายอื่น ๆ
Logger:: log ( " COOKIE " , self :: $ userId . " is trying to login using invalid cookie " , __FILE__ , __LINE__ ); อีเมลจะถูกส่งโดยใช้ PHPMailer ผ่าน SMTP ซึ่งเป็นห้องสมุดอื่นสำหรับการส่งอีเมล คุณไม่ควรใช้ฟังก์ชั่น mail() ของ PHP
ใน แอพ/config มีสองไฟล์หนึ่งไฟล์เรียกว่า config.php สำหรับการกำหนดค่าแอปพลิเคชันหลักและอีกไฟล์สำหรับ JavaScript ที่เรียกว่า javascript.php การกำหนดค่า JavaScript จะถูกกำหนดให้กับตัวแปร JavaScript ใน footer.php ของคุณ
ในการส่งคำขอและรับการตอบกลับคุณอาจพึ่งพาการโทร AJAX เพื่อทำเช่นนั้น เฟรมเวิร์กนี้ขึ้นอยู่กับคำขอ AJAX อย่างมากในการดำเนินการ แต่คุณยังสามารถทำสิ่งเดียวกันสำหรับคำขอปกติด้วยการปรับแต่งเพียงเล็กน้อย
วัตถุ กำหนดค่า ถูกกำหนดให้กับคู่คีย์-ค่าใน footer.php คู่คีย์-ค่าเหล่านี้สามารถเพิ่มได้ในรหัสฝั่งเซิร์ฟเวอร์โดยใช้ Config::setJsConfig('key', "value"); ซึ่งจะได้รับมอบหมายให้ กำหนดค่า วัตถุ
Ajax a namespace ที่มีสองฟังก์ชั่นหลักสำหรับการส่งคำขอ AJAX หนึ่งสำหรับการโทร AJAX ปกติและอีกอันสำหรับการอัปโหลดไฟล์
ผู้ช่วย เนมสเปซที่มีฟังก์ชั่นที่หลากหลายแสดงข้อผิดพลาด, อนุกรม, เปลี่ยนเส้นทาง, ENCODEHTML และอื่น ๆ
แอป เนมสเปซที่ใช้ในการ initalize เหตุการณ์ JavaScript ทั้งหมดสำหรับหน้าปัจจุบัน
เหตุการณ์ เป็นเนมสเปซที่ใช้ในการประกาศเหตุการณ์ทั้งหมดที่อาจเกิดขึ้นเช่นเมื่อผู้ใช้คลิกที่ลิงค์เพื่อสร้างลบหรืออัปเดต
เพื่อแสดงวิธีการใช้เฟรมเวิร์กในสถานการณ์จริงเฟรมเวิร์กมาพร้อมกับการใช้งานคุณสมบัติเช่นจัดการการจัดการโปรไฟล์ผู้ใช้แดชบอร์ดฟีดข่าวฟีดอัพโหลดและดาวน์โหลดไฟล์โพสต์และความคิดเห็น
ขั้นตอน:
แก้ไขไฟล์การกำหนดค่าใน แอพ/config/config.php พร้อมข้อมูลรับรองของคุณ
ดำเนินการสืบค้น SQL ในไดเรกทอรี การติดตั้ง _ ตามลำดับ
เข้าสู่ระบบ
การตั้งค่าอีเมล
คุณต้องกำหนดค่าข้อมูลบัญชี SMTP ของคุณใน App/config/config.php แต่ ถ้าคุณไม่มีบัญชี SMTP คุณจะบันทึกอีเมลใน แอพ/logs/log.txt โดยใช้ logger
ในการทำเช่นนั้นใน Core/อีเมลแสดงความคิดเห็น $mail->Send() & uncomment Logger::log("EMAIL", $mail->Body);
ผู้ใช้ทุกคนสามารถเปลี่ยนชื่ออีเมลรหัสผ่าน อัพโหลดรูปภาพโปรไฟล์ (เช่นเริ่มต้นที่กำหนดเป็น default.png)
เมื่อใดก็ตามที่ผู้ใช้ขอให้เปลี่ยนอีเมลของเขาการแจ้งเตือนจะถูกส่งไปยังอีเมลเก่าของผู้ใช้และใหม่
การแจ้งเตือนที่ส่งไปยังอีเมลเก่าทำให้ผู้ใช้มีโอกาสเพิกถอนการเปลี่ยนแปลงอีเมลในขณะที่การแจ้งเตือนที่ส่งไปยังอีเมลใหม่กำลังขอการยืนยัน ผู้ใช้ยังสามารถเข้าสู่ระบบด้วยอีเมลเก่าของเขาจนกว่าเขาจะยืนยันการเปลี่ยนแปลง
สิ่งนี้ทำใน UserController ในวิธีการ updateProfileInfo() , revokeEmail() , & updateEmail() ในสถานการณ์ส่วนใหญ่คุณไม่จำเป็นต้องปรับเปลี่ยนพฤติกรรมของวิธีการเหล่านี้
คุณสามารถอัปโหลดและดาวน์โหลดไฟล์
file_uploads เป็นจริงupload_max_filesize, max_file_uploads, post_max_sizeคิดว่าฟีดข่าวเป็นทวีตใน Twitter และในโพสต์เช่นเมื่อคุณเปิดปัญหาใน GitHub
พวกเขาจะถูกนำไปใช้ที่ด้านบนของกรอบนี้
ผู้ดูแลระบบสามารถดำเนินการที่ผู้ใช้ปกติไม่สามารถทำได้ พวกเขาสามารถลบแก้ไขสร้างฟีดข่าวโพสต์หรือความคิดเห็นใด ๆ นอกจากนี้พวกเขายังสามารถควบคุมโปรไฟล์ผู้ใช้ทั้งหมดสร้างและกู้คืนการสำรองข้อมูล
ผู้ดูแลระบบเท่านั้นที่สามารถเข้าถึงเพื่อดูผู้ใช้ที่ลงทะเบียนทั้งหมด พวกเขาสามารถลบแก้ไขข้อมูลของพวกเขา
ในสถานการณ์ส่วนใหญ่คุณจะต้องสร้างการสำรองข้อมูลสำหรับระบบและกู้คืนเมื่อใดก็ตามที่คุณต้องการ
สิ่งนี้ทำได้โดยใช้ MySqldump เพื่อสร้างและกู้คืนการสำรองข้อมูล การสำรองข้อมูลทั้งหมดจะถูกเก็บไว้ใน แอพ/การสำรองข้อมูล
คุณเห็นการแจ้งเตือนสีแดงบน Facebook หรือสีน้ำเงินบน Twitter หรือไม่? ความคิดเดียวกันอยู่ที่นี่ แต่มันถูกนำไปใช้โดยใช้ทริกเกอร์แทน ทริกเกอร์ถูกกำหนดไว้ใน _ การติดตั้ง/ทริกเกอร์ . sql
ดังนั้นเมื่อใดก็ตามที่ผู้ใช้สร้าง FEED ข่าวใหม่โพสต์หรืออัปโหลดไฟล์สิ่งนี้จะเพิ่มจำนวนการนับสำหรับผู้ใช้รายอื่นทั้งหมดและจะแสดงการแจ้งเตือนสีแดงในแถบการนำทาง
ผู้ใช้สามารถรายงานข้อบกพร่องคุณสมบัติและการปรับปรุง เมื่อพวกเขาส่งแบบฟอร์มอีเมลจะถูกส่งไปยัง ADMIN_EMAIL ที่กำหนดไว้ใน App/config/config.php
สมมติว่าคุณต้องการสร้างแอปพลิเคชั่นที่เรียบง่าย ที่นี่ฉันจะไปทีละขั้นตอนเกี่ยวกับวิธีการสร้างแอป TODO โดยใช้เฟรมเวิร์กที่มี & ไม่มีการโทร AJAX
(1) หากคุณทำตามขั้นตอนการตั้งค่าการติดตั้งด้านบนคุณไม่ควรมีปัญหาใด ๆ กับการสร้างบัญชีผู้ใช้เริ่มต้น
(2) สร้างตารางที่มี ID เป็น int, content varchar, user_id เป็นคีย์ต่างประเทศไปยังตาราง users
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 ภายใน มุมมอง/เลย์เอาต์/สิ่งที่ต้องทำ
<! 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) ภายใน มุมมอง/ สร้างโฟลเดอร์ todo ที่จะมี index.php ซึ่งจะมีรายการสิ่งที่ต้องทำของเรา
<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) รหัส JavaScript เพื่อส่งการโทร AJAX และจัดการการตอบกลับ
// 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