Un petit squelette PHP MVC PHP simple qui résume de nombreuses fonctionnalités entourées de puissantes couches de sécurité.
MiniPHP est une application très simple, utile pour les petits projets, aide à comprendre le squelette PHP MVC, à savoir authentifier et autoriser, crypter les données et appliquer les concepts de sécurité, la désinfection et la validation, faire des appels Ajax et plus encore.
Ce n'est pas un cadre complet, ni très basique, mais ce n'est pas compliqué. Vous pouvez facilement l'installer, le comprendre et l'utiliser dans l'un de vos projets.
Il est en retrait de supprimer la complexité des frameworks. Des choses comme le routage, l'authentification, l'autorisation, la gestion de la session des utilisateurs et les cookies, etc. ne sont pas quelque chose que j'ai inventé à partir de zéro, cependant, ils sont une agrégation de concepts déjà implémentés dans d'autres cadres, mais, construits de manière beaucoup plus simple, vous pouvez donc le comprendre et aller plus loin.
Si vous devez créer une application plus grande et profiter de la plupart des fonctionnalités disponibles dans les cadres, vous pouvez voir CakePhp, Laravel, Symphony.
Quoi qu'il en soit, il est important de comprendre le squelette PHP MVC et de savoir comment authentifier et autoriser, en savoir plus sur les problèmes de sécurité et comment pouvez-vous vaincre et comment créer votre propre application en utilisant le cadre.
Une documentation complète peut également être trouvée ici - créée par GitHub Automatic Page Generator.
Une démo en direct est disponible ici. La démo en direct concerne l'application de démonstration construite au-dessus de ce cadre dans cette section. Merci à @everterstraat.
Certaines fonctionnalités ne fonctionnent pas dans la démo.
Installer via le compositeur
composer install
Chaque fois que vous faites une demande à l'application, il sera dirigé vers index.php dans le dossier public. Donc, si vous faites une demande: http://localhost/miniPHP/User/update/412 . Cela sera divisé et traduit en
En fait, HTACCESS divise que tout vient après http://localhost/miniPHP et l'ajoute à l'URL comme argument QueryString. Ainsi, cette demande sera convertie en: http://localhost/miniPHP?url='User/update/412' .
Ensuite, la classe App , à l'intérieur splitUrl() , divisera la chaîne de requête $_GET['url'] en contrôleur, méthode d'action et tout argument passé à la méthode d'action.
Dans la classe App , à l'intérieur run() , il instanciera un objet de la classe de contrôleur et fera une méthode d'appel à l'action, passant tous les arguments s'ils existent.
Après l'objet de contrôleur de la classe App , il appellera $this->controller->startupProcess() , qui à son tour déclenchera 3 événements / méthodes consécutives:
initialize() : utilisez-le pour charger des composantsbeforeAction() : effectuez toutes les actions logiques avant d'appeler la méthode d'action du contrôleurtriggerComponents() : Trigger Startup () Méthode de composants chargés Le constructeur de la classe Controller ne doit pas être remplacé, mais vous pouvez plutôt remplacer les méthodes initialize() et beforeAction() dans les classes d'extension.
Une fois le processus de démarrage de la constructeur terminé son travail, alors, la méthode d'action demandée sera appelée et les arguments seront passés (le cas échéant).
Les composants sont les Widlewares. Ils fournissent une logique réutilisable à utiliser dans le cadre du contrôleur. L'authentification, l'autorisation, la falsification de la forme et les jetons CSRF validés sont implémentés à l'intérieur des composants.
Il est préférable de retirer ces morceaux de logique de la classe de contrôleur et de conserver toutes les différentes tâches et validations à l'intérieur de ces composants.
Chaque composant hérite de la base / super classe appelée Component . Chacun a une tâche définie. Il y a deux composants, l'un pour l'authentification appelé Authentification et l'autorisation, et l'autre appelé Security pour d'autres problèmes de sécurité.
Ils sont très simples à gérer, et ils seront appelés constructeur de contrôleur à l'intérieur.
L'utilisateur a-t-il les bonnes informations d'identification?
L'AuthComponent s'occupe de la session utilisateur.
Avez-vous le droit d'accéder ou d'effectuer l'action X ?. Le composant Auth se soucie de l'autorisation pour chaque contrôleur. Ainsi, chaque contrôleur doit implémenter la méthode isAuthorized() . Ce que vous devez faire est de retourner la valeur boolean .
Ainsi, par exemple, afin de vérifier si l'utilisateur actuel est administrateur ou non, vous feriez quelque chose comme ceci:
// AdminController
public function isAuthorized (){
$ role = Session:: getUserRole ();
if ( isset ( $ role ) && $ role === " admin " ){
return true ;
}
return false ;
} Si vous souhaitez aller plus loin et appliquer quelques règles d'autorisation, il existe une classe puissante appelée Permission responsable de la définition des règles d'autorisation. Cette classe vous permet de définir "qui est autorisé à effectuer une méthode d'action spécifique sur le contrôleur actuel".
Ainsi, par exemple, afin de permettre aux administrateurs d'effectuer toute action sur les notes, tandis que les utilisateurs normaux ne peuvent modifier que leurs notes:
// 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 );
}Maintenant, vous pouvez vérifier l'autorisation en fonction du rôle de l'utilisateur, des ressources et pour chaque méthode d'action.
Le SecurityComponent s'occupe de diverses tâches de sécurité et validation.
Il est important de restreindre les méthodes de demande. Par exemple, si vous avez une méthode d'action qui accepte les valeurs de formulaire, donc seule la demande de post sera acceptée. La même idée pour ajax, get, ..etc. Vous pouvez le faire à l'intérieur de la méthode beforeAction() .
// NotesController
public function beforeAction (){
parent :: beforeAction ();
$ actions = [ ' create ' , ' delete ' ];
$ this -> Security -> requireAjax ( $ actions );
$ this -> Security -> requirePost ( $ actions );
}De plus, si vous avez besoin de toutes les demandes pour être via une connexion sécurisée, vous pouvez configurer l'intégralité du contrôleur ou des actions spécifiques pour rediriger toutes les demandes vers HTTPS au lieu de HTTP.
// NotesController
public function beforeAction (){
parent :: beforeAction ();
$ actions = [ ' create ' , ' delete ' ]; // specific action methods
$ actions = [ ' * ' ]; // all action methods
$ this -> Security -> requireSecure ( $ actions );
}Il vérifie et valide si la demande provient du même domaine. Bien qu'ils puissent être truqués, il est bon de les garder dans le cadre de nos couches de sécurité.
Valider le formulaire soumis provenant de la demande de poste. L'écueil de cette méthode est que vous devez définir les champs de formulaire attendus ou les données qui seront envoyées avec la demande post-demande.
Par défaut, le cadre validera la falsification du formulaire lors de la demande de poste, et il s'assurera que le jeton CSRF est passé avec les champs de formulaire. Dans cette situation, si vous n'avez pas réussi le jeton CSRF, il sera considéré comme un fil de sécurité.
// 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 ;
}
}Les jetons CSRF sont importants pour valider les formulaires soumis et pour s'assurer qu'ils ne sont pas truqués. Un pirate peut tromper l'utilisateur pour faire une demande à un site Web, ou cliquer sur un lien, etc.
Ils sont valables pour une certaine durée (> = 1 jour), puis il sera régénéré et stocké dans la session de l'utilisateur.
La validation CSRF est désactivée par défaut. Si vous souhaitez valider le jeton CSRF, affectez validateCsrfToken à true comme indiqué dans l'exemple ci-dessous. La validation CSRF sera forcée lorsque la demande est publiée et la falsification du formulaire est activée.
Maintenant, vous n'avez pas besoin de vérifier manuellement le jeton CSRF sur toutes les demandes. Le composant de sécurité vérifiera le jeton dans la demande par rapport au jeton stocké dans la session.
// 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 ;
}
}Les jetons CSRF sont générés par session. Vous pouvez soit ajouter un champ de formulaire caché, soit dans l'URL comme paramètre de requête.
Formulaire
<input type="hidden" name="csrf_token" value="<?= Session::generateCsrfToken(); ?>" />
URL
<a href="<?= PUBLIC_ROOT . "?csrf_token=" . urlencode(Session::generateCsrfToken()); ?>">Link</a>
Javascrip
Vous pouvez également affecter le jeton CSRF à une variable JavaScript.
<script>config = <?= json_encode(Session::generateCsrfToken()); ?>;</script>
index.php dans le dossier racine publique. Parfois, vous devez avoir un contrôle sur ces composants, comme lorsque vous souhaitez avoir un contrôleur sans authentification ni autorisation, ou un composant de sécurité est activé. Cela peut être fait en remplacer la méthode initialize() dans votre classe de contrôleur et charger uniquement les composants nécessaires.
Exemple 1 : Ne chargez aucun composant, aucune authentification ou autorisation, ni validations de sécurité.
public function initialize (){
$ this -> loadComponents ([]);
}Exemple 2 : Chargez la sécurité et le composant Auth, mais n'authentifiez pas et n'autorisez pas, au cas où vous souhaitez utiliser le composant Auth dans les méthodes d'action. LoginController est un exemple sur la façon d'accéder à une page sans nécessiter un utilisateur connecté .
public function initialize (){
$ this -> loadComponents ([
' Auth ' ,
' Security '
]);
}Exemple 3 : Chargez la sécurité et le composant Auth, et authentifiez l'utilisateur et autorisez le contrôleur actuel. C'est le comportement par défaut dans la classe Core / Controller
public function initialize (){
$ this -> loadComponents ([
' Auth ' => [
' authenticate ' => [ ' User ' ],
' authorize ' => [ ' Controller ' ]
],
' Security '
]);
}À l'intérieur de la méthode d'action, vous pouvez passer un appel à modéliser pour obtenir des données et / ou rendre les pages à l'intérieur du dossier des vues
// 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 ));
}Dans MVC, le modèle représente les informations (les données) et les règles commerciales; La vue contient des éléments de l'interface utilisateur tels que du texte, des entrées de formulaire; et le contrôleur gère la communication entre le modèle et la vue. Source
Toutes les opérations comme Create, Delete, Update et Validation sont implémentées dans les classes de modèles.
// 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 " );
}
}Dans le modèle de 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 ;
}En utilisant le cadre, vous seriez probablement connecté, enregistrer et déconnecter. Ces actions sont implémentées dans App / Models / Login & App / Controllers / LoginController . Dans la plupart des situations, vous n'aurez pas besoin de modifier quoi que ce soit lié aux actions de connexion, il suffit de comprendre le comportement du cadre.
Remarque Si vous n'avez pas de SSL, vous voudriez mieux crypter les données manuellement à côté du client, si oui, lisez ceci et aussi ceci.
Chaque fois que l'utilisateur enregistre, un e-mail sera envoyé avec un jeton concaténé avec l'ID utilisateur chiffré. Ce jeton sera expiré après 24 heures. Il vaut mieux expirer ces jetons et réutiliser l'e-mail enregistré si elles sont expirées.
Les mots de passe sont hachés à l'aide des derniers algorithmes de PHP v5.5
$ hashedPassword = password_hash ( $ password , PASSWORD_DEFAULT , array ( ' cost ' => Config:: get ( ' HASH_COST_FACTOR ' )));Si l'utilisateur a oublié son mot de passe, il peut le restaurer. La même idée de jetons expirés va ici.
De plus, bloquez l'utilisateur pour une certaine durée (> = 10min) s'il dépassait le nombre de tentatives de mots de passe oubliées (5) pendant une certaine durée (> = 10min).
Les attaques brutes-force étrangères, c'est lorsqu'un pirate essaie toute combinaison d'entrée possible jusqu'à ce qu'il trouve le mot de passe correct.
Solution:
Les captchas sont particulièrement efficaces pour prévenir les connexions automatisées. En utilisant CAPTCHA, une bibliothèque PHP CAPTCHA impressionnante.
Le blocage des adresses IP est la dernière solution à laquelle réfléchir. L'adresse IP sera bloquée si la même IP n'a pas réussi à se connecter plusieurs fois en utilisant différentes informations d'identification (> = 10).
Les objets de données PHP (APD) sont utilisés pour préparer et exécuter les requêtes de base de données. À l'intérieur de la classe Database , il existe différentes méthodes qui cachent la complexité et vous permet d'instancier un objet de base de données, de préparer, de lier et d'exécuter en quelques lignes.
SELECT, INSERT, UPDATE, DELETE suffire aux utilisateursAdmin .utf8mb4 au niveau de la base de données.utf8 de MySQL ne stocke que des symboles codés UTF-8 qui se composent d'un à trois octets. Mais, il ne peut pas pour les symboles avec quatre octets.utf8 . Mais, si vous souhaitez passer à utf8mb4 , suivez ces liens:utf8mb4 La classe Encryption est responsable du chiffrement et du décryptage des données. Le chiffrement est appliqué à des choses comme les cookies, l'ID utilisateur, le post ID, ..etc. Les cordes cryptées sont authentifiées et elles sont différentes à chaque fois que vous chiffrez.
La validation est une petite bibliothèque pour valider les entrées utilisateur. Toutes les règles de validation sont à l'intérieur de la classe 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 Class est responsable de la gestion de toutes les exceptions et erreurs. Il utilisera Logger pour enregistrer les erreurs. Les rapports d'erreur sont désactivés par défaut, car chaque erreur sera enregistrée et enregistrée dans APP / Logs / Log.txt .
Si une erreur rencontrée ou une exception a été lancée, l'application affichera une erreur interne du système (500).
Un endroit où vous pouvez enregistrer n'importe quoi et l'enregistrer sur app / log / log.txt . Vous pouvez rédiger des échecs, des erreurs, des exceptions ou toute autre actions ou attaques malveillantes.
Logger:: log ( " COOKIE " , self :: $ userId . " is trying to login using invalid cookie " , __FILE__ , __LINE__ ); Les e-mails sont envoyés à l'aide de phpmailer via SMTP, une autre bibliothèque pour envoyer des e-mails. Vous ne devez pas utiliser la fonction mail() de PHP.
Dans APP / config , il y a deux fichiers, un appelé config.php pour les configurations d'application principales, et un autre pour JavaScript appelé javascript.php . Les configurations JavaScript seront ensuite affectées à une variable JavaScript dans votre pied de page.php .
Afin d'envoyer la demande et de recevoir une réponse, vous pouvez dépendre des appels AJAX pour le faire. Ce cadre dépend fortement des demandes AJAX pour effectuer des actions, mais vous pouvez toujours faire la même chose pour les demandes normales avec juste de petits ajustements.
L'objet config est attribué aux paires de valeurs de clé dans Footer.php. Ces paires de valeurs de clé peuvent être ajoutées dans le code côté serveur à l'aide Config::setJsConfig('key', "value"); , qui sera ensuite attribué à l'objet config .
ajax un espace de noms qui a deux fonctions principales pour l'envoi de la demande Ajax. Un pour les appels AJAX normaux et un autre pour le téléchargement de fichiers.
Aiders Un espace de noms qui a une variété de fonctions affiche les erreurs, sérialiser, rediriger, encoderhtml, etc.
Appliquez un espace de noms utilisé pour initalialiser l'ensemble des événements JavaScript pour la page actuelle
Événements Un espace de noms utilisé pour déclarer tous les événements qui peuvent se produire, comme lorsque l'utilisateur clique sur un lien pour créer, supprimer ou mettre à jour.
Afin de montrer comment utiliser le framework dans une situation réelle, le framework est livré avec l'implémentation pour des fonctionnalités telles que Gérer la gestion du profil utilisateur, le tableau de bord, le flux d'actualités, les fichiers de téléchargement et de téléchargement, les publications et les commentaires, la pagination, le panneau d'administration, la gestion des sauvegardes système, des notificatons, des bugs de rapport, ... etc.
Mesures:
Modifier le fichier de configuration dans app / config / config.php avec vos informations d'identification
Exécuter les requêtes SQL dans _ Directory d'installation dans l'ordre
Se connecter
Configuration par e-mail
Vous devez configurer vos données de compte SMTP dans App / Config / Config.php . Mais , si vous n'avez pas de compte SMTP, vous enregistrez les e-mails dans App / Logs / Log.txt à l'aide du logger.
Pour ce faire, dans Core / Email, commentez $mail->Send() & Uncomment Logger::log("EMAIL", $mail->Body);
Chaque utilisateur peut modifier son nom, son e-mail, son mot de passe. Téléchargez également l'image de profil (c'est-à-dire initialement affecté à Default.png).
Chaque fois que l'utilisateur demande à modifier son e-mail, une notification sera envoyée à l'ancien e-mail de l'utilisateur et à la nouvelle.
La notification envoyée à l'ancien e-mail donne à l'utilisateur la possibilité de révoquer le changement de messagerie, tandis que la notification envoyée au nouvel e-mail demande une confirmation. L'utilisateur peut toujours se connecter avec son ancien e-mail jusqu'à ce qu'il confirme le changement.
Cela se fait dans UserController , dans Methods updateProfileInfo() , revokeEmail() , & updateEmail() . Dans la plupart des situations, vous n'aurez pas besoin de modifier le comportement de ces méthodes.
Vous pouvez télécharger et télécharger des fichiers.
file_uploads sur trueupload_max_filesize, max_file_uploads, post_max_sizeConsidérez le flux d'actualités comme des tweets sur Twitter et dans des articles comme lorsque vous ouvrez un numéro dans Github.
Ils sont mis en œuvre en haut de ce cadre.
Les administrateurs peuvent effectuer des actions où les utilisateurs normaux ne peuvent pas. Ils peuvent supprimer, modifier, créer n'importe quel fil d'actualité, publier ou commentaire. Ils ont également un contrôle sur tous les profils d'utilisateurs, créer et restaurer des sauvegardes.
Seuls les administrateurs ont accès pour voir tous les utilisateurs enregistrés. Ils peuvent supprimer, modifier leurs informations.
Dans la plupart des situations, vous devrez créer des sauvegardes pour le système et les restaurer quand vous le souhaitez.
Cela se fait en utilisant MySQLDump pour créer et restaurer des sauvegardes. Toutes les sauvegardes seront stockées dans l'application / les sauvegardes .
Avez-vous vu les notifications rouges sur Facebook, ou le bleu sur Twitter ?. La même idée est là. Mais il est mis en œuvre à l'aide de déclencheurs à la place. Les déclencheurs sont définis dans _ Installation / Triggers.sql .
Ainsi, chaque fois que l'utilisateur crée un nouveau fil d'actualité, publier ou télécharger un fichier, cela incrémentera le nombre pour tous les autres utilisateurs et affichera une notification rouge dans la barre de navigation.
Les utilisateurs peuvent signaler les bogues, les fonctionnalités et les améliorations. Une fois qu'ils ont soumis le formulaire, un e-mail sera envoyé à ADMIN_EMAIL défini dans l'application / config / config.php
Disons que vous souhaitez créer une application TODO simple. Ici, je vais aller étape par étape sur la façon de créer une application TODO en utilisant le framework avec et sans appels Ajax.
(1) Si vous avez suivi les étapes de configuration de l'installation ci-dessus, vous ne devriez avoir aucun problème à créer des comptes d'utilisateurs initiaux.
(2) Créez une table avec ID comme int, Content varchar, user_id comme clé étrangère à la table 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) Créer un toadocontroller
Créer un fichier appelé TodoController.php dans l'application / contrôleurs
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) Créer une classe de modèles de notes appelée Todo.php dans l'application / modèles
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) Vues à l'intérieur /
(a) Créer des vues header.php & footer.php Inside Views / Layout / 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) Inside Views / Créer un dossier TODO qui aura index.php , qui contiendra notre liste de 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) Code JavaScript pour envoyer des appels ajax et gérer la réponse
// 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 ) ;
}
}
} ) ;
}
}
}J'ai écrit ce script pendant mon temps libre pendant mes études. C'est gratuitement, non rémunéré. Je dis cela parce que j'ai vu de nombreux développeurs agir très grossiers envers tous les logiciels, et leur comportement est vraiment frustrant. Je ne sais pas pourquoi ?! Tout le monde a tendance à se plaindre et à dire des mots durs. J'accepte les commentaires, mais, de manière bonne et respectueuse.
Il existe de nombreux autres scripts en ligne à l'achat qui font la même chose (sinon moins), et leurs auteurs gagnent beaucoup d'argent, mais je choisis de le garder public, disponible pour tout le monde.
Si vous avez appris quelque chose ou si j'ai gagné votre temps, veuillez soutenir le projet en passant le mot.
Contribuer en créant de nouveaux problèmes, en envoyant des demandes de traction sur github ou vous pouvez envoyer un e-mail à: [email protected]
Construit sous licence MIT.