هيكل عظمي صغير وبسيط PHP MVC يلف الكثير من الميزات المحاطة بطبقات أمنية قوية.
MiniPhP هو تطبيق بسيط للغاية ، مفيد للمشاريع الصغيرة ، يساعد على فهم الهيكل العظمي لـ PHP MVC ، ومعرفة كيفية المصادقة على البيانات وترخيصها وتشفيرها وتطبيق مفاهيم الأمان ، والتطهير والتحقق من صحة ، وإجراء مكالمات Ajax والمزيد.
إنه ليس إطارًا كاملاً ، ولا إطارًا أساسيًا للغاية ولكنه ليس معقدًا. يمكنك بسهولة تثبيت وفهمها واستخدامها في أي من مشاريعك.
إنه مسافة بادئة لإزالة تعقيد الأطر. أشياء مثل التوجيه ، والمصادقة ، والترخيص ، وإدارة جلسة المستخدم وملفات تعريف الارتباط ، وهكذا ليست شيئًا اخترعته من الخدش ، ومع ذلك ، فهي تجمع المفاهيم التي تم تنفيذها بالفعل في أطر أخرى ، ولكن ، تم إنشاؤها بطريقة أبسط بكثير ، لذلك ، يمكنك فهمها ، وأخذها أكثر.
إذا كنت بحاجة إلى إنشاء تطبيق أكبر ، والاستفادة من معظم الميزات المتوفرة في الأطر ، يمكنك رؤية CakePhp و Laravel و Symphony.
في كلتا الحالتين ، من المهم فهم الهيكل العظمي لـ PHP MVC ، ومعرفة كيفية المصادقة والتفويض ، والتعرف على مشكلات الأمان وكيف يمكنك الهزيمة ، وكيفية بناء تطبيقك باستخدام الإطار.
يمكن العثور على الوثائق الكاملة أيضًا هنا - تم إنشاؤها بواسطة Github Automatic Generator.
العرض التوضيحي المباشر متاح هنا. العرض التوضيحي المباشر هو للتطبيق التجريبي المبني فوق هذا الإطار في هذا القسم. بفضل eversterat.
بعض الميزات لا تعمل في العرض التوضيحي.
تثبيت عبر الملحن
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 ، Inside run() ، سيتم إنشاء إنشاء كائن من فئة وحدة التحكم ، وإجراء مكالمة إلى طريقة الإجراء ، وتمرير أي وسيط إذا كانت موجودة.
بعد كائن وحدة التحكم في فئة App ، سوف يدعو $this->controller->startupProcess() ، والتي بدورها ستؤدي إلى 3 أحداث/أساليب متتالية:
initialize() : استخدمه لتحميل المكوناتbeforeAction() : تنفيذ أي إجراءات منطقية قبل استدعاء طريقة إجراء وحدة التحكمtriggerComponents() : طريقة بدء التشغيل () للمكونات المحملة يجب ألا يتم تجاوز مُنشئ فئة Controller ، بدلاً من ذلك ، يمكنك تجاوز أساليب initialize() و beforeAction() في الفئات الممتدة.
بعد أن تنتهي عملية بدء التشغيل من Conncutor ، سيتم استدعاء طريقة الإجراء المطلوبة ، وسيتم تمرير الوسائط (إن وجدت).
المكونات هي الوسيطة. أنها توفر المنطق القابل لإعادة الاستخدام لاستخدامه كجزء من وحدة التحكم. يتم تنفيذ المصادقة ، والترخيص ، والعبث النموذج ، والتحقق من صحة الرموز CSRF داخل المكونات.
من الأفضل سحب هذه الأجزاء من المنطق من فئة وحدة التحكم ، والحفاظ على جميع المهام والتحققات المختلفة داخل هذه المكونات.
يرث كل مكون من الفئة القاعدة/السوبر يسمى Component . كل لديه مهمة محددة. هناك مكونان ، أحدهما للاستدعاء للمصادقة والترخيص ، والآخر يسمى الأمان لقضايا الأمن الأخرى.
إنها بسيطة للغاية للتعامل معها ، وسيتم استدعاؤها داخل مُنشئ وحدة التحكم.
هل المستخدم لديه بيانات اعتماد صحيحة؟
يعتني AuthComponent بجلسة المستخدم.
هل لديك الحق في الوصول أو أداء X Action؟. يعتني مكون المصادقة بالترخيص لكل وحدة تحكم. وبالتالي ، يجب على كل وحدة تحكم تنفيذ طريقة 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 بمختلف المهام الأمنية والتحقق من الصحة.
من المهم تقييد أساليب الطلب. على سبيل المثال ، إذا كان لديك طريقة إجراء تقبل قيم النماذج ، فسيتم قبول طلب النشر فقط. نفس الفكرة لأجاكس ، الحصول على ، ..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 مهمة للتحقق من صحة النماذج المقدمة ، والتأكد من أنها غير مزيفة. يمكن للمتسلل خداع المستخدم لتقديم طلب إلى موقع ويب ، أو النقر على رابط ، وما إلى ذلك.
إنها صالحة لمدة معينة (> = يوم واحد) ، ثم سيتم تجديدها وتخزينها في جلسة المستخدم.
يتم تعطيل التحقق من صحة 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 في مجلد الجذر العام. في بعض الأحيان ، تحتاج إلى تحكم في هذه المكونات ، مثل عندما ترغب في الحصول على وحدة تحكم دون مصادقة أو تفويض ، أو تمكين مكون أمان. يمكن القيام بذلك عن طريق override initialize() داخل فئة وحدة التحكم الخاصة بك ، وتحميل المكونات المطلوبة فقط.
مثال 1 : لا تقم بتحميل أي مكون أو أي مصادقة أو تفويض أو صحة أمان.
public function initialize (){
$ this -> loadComponents ([]);
}مثال 2 : تحميل الأمان ، ومكون المصادقة ، ولكن لا تصادق وتسمح ، فقط في حالة رغبتك في استخدام مكون المصادقة داخل طرق الإجراء. LoginController هو مثال على كيفية الوصول إلى صفحة دون طلب مستخدم مسجل .
public function initialize (){
$ this -> loadComponents ([
' Auth ' ,
' Security '
]);
}مثال 3 : تحميل الأمان ، ومكون المصادقة ، ومصادقة المستخدم وتفويضه لوحدة التحكم الحالية. هذا هو السلوك الافتراضي في فئة Core/Controller
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 ، يمثل النموذج المعلومات (البيانات) وقواعد العمل ؛ يحتوي العرض على عناصر من واجهة المستخدم مثل النص ، ومدخلات النماذج ؛ وتدير وحدة التحكم التواصل بين النموذج والعرض. مصدر
يتم تنفيذ جميع العمليات مثل إنشاء وحذف وتحديث والتحديث في فئات النماذج.
// 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 ;
}باستخدام الإطار ، من المحتمل أن تقوم بتسجيل الدخول والتسجيل والتسجيل. يتم تطبيق هذه الإجراءات في التطبيق/النماذج/تسجيل الدخول والتطبيق/وحدات التحكم/LoginController . في معظم المواقف ، لن تحتاج إلى تعديل أي شيء يتعلق بإجراءات تسجيل الدخول ، فقط فهم سلوك الإطار.
ملاحظة إذا لم يكن لديك SSL ، فمن الأفضل أن ترغب في تشفير البيانات يدويًا على جانب العميل ، إذا كان الأمر كذلك ، اقرأ هذا وأيضًا هذا.
عندما يسجل المستخدم ، سيتم إرسال بريد إلكتروني باستخدام Token Concatedenated بمعرف المستخدم المشفر. سيتم انتهاء صلاحية هذا الرمز المميز بعد 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 كافية للمستخدمينAdmin .utf8mb4 على مستوى قاعدة البيانات.utf8 Charset فقط تخزين الرموز المشفرة UTF-8 التي تتكون من واحد إلى ثلاثة بايت. ولكن ، لا يمكن للرموز مع أربعة بايت.utf8 . ولكن ، إذا كنت ترغب في الترقية إلى utf8mb4 ، اتبع هذه الروابط:utf8mb4 فئة Encryption مسؤولة عن تشفير البيانات وفك تشفيرها. يتم تطبيق التشفير على أشياء مثل ملفات تعريف الارتباط ، معرف المستخدم ، المعرف ، ..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 هي المسؤولة عن التعامل مع جميع الاستثناءات والأخطاء. سوف تستخدم المسجل لتسجيل الأخطاء. يتم إيقاف الإبلاغ عن الخطأ بشكل افتراضي ، لأنه سيتم تسجيل كل خطأ وحفظه في التطبيق/السجلات/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.php لتكوينات التطبيق الرئيسية ، وآخر واحد لـ JavaScript يسمى javaScript.php . سيتم تعيين تكوينات JavaScript بعد ذلك إلى متغير JavaScript في footer.php الخاص بك.
من أجل إرسال الطلب واستلام الاستجابة ، قد تعتمد على مكالمات Ajax للقيام بذلك. يعتمد هذا الإطار بشكل كبير على طلبات AJAX لإجراء الإجراءات ، ولكن لا يزال بإمكانك فعل الشيء نفسه للطلبات العادية مع تعديلات صغيرة فقط.
يتم تعيين كائن التكوين إلى أزواج القيمة الرئيسية في footer.php. يمكن إضافة أزواج القيمة الرئيسية هذه في رمز من جانب الخادم باستخدام Config::setJsConfig('key', "value"); ، والتي سيتم تعيينها ثم لتكوين كائن.
Ajax مساحة اسم لها وظيفتين رئيسيتين لإرسال طلب AJAX. واحد لمكالمات Ajax العادية ، والآخر لتحميل الملفات.
المساعدون مساحة اسم لها مجموعة متنوعة من الوظائف تعرض الأخطاء ، التسلسل ، إعادة التوجيه ، encodehtml ، وما إلى ذلك
تطبيق مساحة اسم تستخدم لإثبات أحداث JavaScript بأكملها للصفحة الحالية
الأحداث مساحة اسم تستخدم لإعلان جميع الأحداث التي قد تحدث ، مثل عندما ينقر المستخدم على رابط لإنشاء أو حذف أو تحديث.
من أجل إظهار كيفية استخدام الإطار في حالة واقعية ، يأتي Framework مع تطبيق لميزات مثل إدارة ملف تعريف المستخدم ، ولوحة المعلومات ، وتغذية الأخبار ، وتحميل وتنزيل الملفات ، والمنشورات والتعليقات ، وترثة الإدارة ، وإدارة النسخ الاحتياطية للنظام ، و Notificatons ، والتقارير ، وما إلى ذلك.
خطوات:
تحرير ملف التكوين في التطبيق/config/config.php مع بيانات الاعتماد الخاصة بك
تنفيذ استعلامات SQL في دليل التثبيت _ بالترتيب
تسجيل الدخول
إعداد البريد الإلكتروني
تحتاج إلى تكوين بيانات حساب SMTP في App/config/config.php . ولكن ، إذا لم يكن لديك حساب SMTP ، فأنت تقوم بحفظ رسائل البريد الإلكتروني في التطبيق/السجلات/log.txt باستخدام Logger.
للقيام بذلك ، في Core/Email ، قم بالتعليق $mail->Send() و untomment Logger::log("EMAIL", $mail->Body);
يمكن لكل مستخدم تغيير اسمه والبريد الإلكتروني وكلمة المرور. قم أيضًا بتحميل صورة الملف الشخصي (أي تم تعيينه في البداية إلى Default.png).
عندما يطلب المستخدم تغيير بريده الإلكتروني ، سيتم إرسال إشعار إلى البريد الإلكتروني القديم للمستخدم ، والآخر.
الإخطار المرسل إلى البريد الإلكتروني القديم هو منح المستخدم الفرصة لإلغاء تغيير البريد الإلكتروني ، في حين أن الإشعار المرسل إلى البريد الإلكتروني الجديد يطلب التأكيد. لا يزال بإمكان المستخدم تسجيل الدخول باستخدام بريده الإلكتروني القديم حتى يؤكد التغيير.
يتم ذلك في UserController ، في Methods updateProfileInfo() ، revokeEmail() ، & updateEmail() . في معظم المواقف ، لن تحتاج إلى تعديل سلوك هذه الأساليب.
يمكنك تحميل وتنزيل الملفات.
file_uploads على Trueupload_max_filesize, max_file_uploads, post_max_sizeفكر في تغذية الأخبار كغريدات في Twitter ، وفي منشورات مثل عندما تفتح مشكلة في Github.
يتم تنفيذها في الجزء العلوي من هذا الإطار.
يمكن للمسؤولين تنفيذ الإجراءات التي لا يمكن للمستخدمين العاديين ذلك. يمكنهم حذف أو تحرير أو إنشاء أي ملف أخبار أو نشر أو تعليق. لديهم أيضًا تحكم في جميع ملفات تعريف المستخدمين ، وإنشاء واستعادة النسخ الاحتياطية.
يمكن للمسؤولين فقط الوصول إلى رؤية جميع المستخدمين المسجلين. يمكنهم حذف ، تحرير معلوماتهم.
في معظم المواقف ، ستحتاج إلى إنشاء نسخ احتياطية للنظام ، واستعادتها وقتما تشاء.
يتم ذلك باستخدام MySQLDump لإنشاء واستعادة النسخ الاحتياطية. سيتم تخزين جميع النسخ الاحتياطية في التطبيق/النسخ الاحتياطية .
هل رأيت الإشعارات الحمراء على Facebook ، أو الإلغاء الأزرق على Twitter؟. نفس الفكرة هنا. ولكن ، يتم تنفيذها باستخدام المشغلات بدلاً من ذلك. يتم تعريف المشغلات في _ التثبيت/المشغلات .
لذلك ، عندما يقوم المستخدم بإنشاء ملف أخبار جديد أو نشر أو تحميل ملف ، فإن هذا سيزيد من العد لجميع المستخدمين الآخرين ، وسيعرض إشعارًا أحمر في شريط التنقل.
يمكن للمستخدمين الإبلاغ عن الأخطاء والميزات والتحسينات. بمجرد إرسال النموذج ، سيتم إرسال بريد إلكتروني إلى ADMIN_EMAIL محدد في التطبيق/config/config.php
لنفترض أنك تريد إنشاء تطبيق TODO بسيط. هنا ، سأذهب خطوة بخطوة حول كيفية إنشاء تطبيق TODO باستخدام الإطار مع وبدون مكالمات AJAX.
(1) إذا اتبعت خطوات إعداد التثبيت أعلاه ، فلن تواجه أي مشكلة في إنشاء حسابات مستخدم أولية.
(2) قم بإنشاء جدول يحتوي على معرف كـ int ، المحتوى 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) وجهات النظر الداخلية/
( أ footer.php إنشاء header.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> (ب) داخل العرض/ إنشاء مجلد TODO الذي سيكون له index.php ، والذي سيحتوي على قائمة 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) رمز 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]
بنيت تحت رخصة معهد ماساتشوستس للتكنولوجيا.