Un pequeño esqueleto de marco PHP MVC PHP que encapsula muchas características rodeadas de potentes capas de seguridad.
MiniPHP es una aplicación muy simple, útil para proyectos pequeños, ayuda a comprender el esqueleto PHP MVC, saber cómo autenticar y autorizar, cifrar datos y aplicar conceptos de seguridad, desinfección y validación, hacer llamadas AJAX y más.
No es un marco completo, ni muy básico, pero no es complicado. Puede instalar, comprenderlo y usarlo fácilmente en cualquiera de sus proyectos.
Está sangrado para eliminar la complejidad de los marcos. Cosas como el enrutamiento, la autenticación, la autorización, la administración de la sesión de los usuarios y las cookies, y así que no son algo que he inventado desde cero, sin embargo, son la agregación de conceptos ya implementados en otros marcos, pero, de una manera mucho más simple, por lo que puede entenderlo y llevarlo más allá.
Si necesita construir una aplicación más grande y aprovechar la mayoría de las características disponibles en los marcos, puede ver CakePhp, Laravel, Symphony.
De cualquier manera, es importante comprender el esqueleto PHP MVC y saber cómo autenticar y autorizar, aprender sobre problemas de seguridad y cómo puede derrotar y cómo construir su propia aplicación utilizando el marco.
La documentación completa también se puede encontrar aquí, creada por GitHub Automatic Page Generator.
Una demostración en vivo está disponible aquí. La demostración en vivo es para la aplicación de demostración construida sobre este marco en esta sección. Gracias a @everterstraat.
Algunas características no funcionan en la demostración.
Instalar a través del compositor
composer install
Siempre que realice una solicitud a la solicitud, se dirigirá a index.php dentro de la carpeta pública. Entonces, si realiza una solicitud: http://localhost/miniPHP/User/update/412 . Esto será dividido y traducido a
De hecho, Htaccess divide que todo viene después de http://localhost/miniPHP y lo agrega a la URL como argumento de consulta. Entonces, esta solicitud se convertirá en: http://localhost/miniPHP?url='User/update/412' .
Luego, la clase App , dentro de splitUrl() , dividirá la cadena de consulta $_GET['url'] en el controlador, el método de acción y cualquier argumento aprobado al método de acción.
En la clase App , Inside run() , instanciará un objeto de la clase de controlador y hará un método de llamado a la acción, aprobando cualquier argumento si existen.
Después de que la clase App intenta el objeto del controlador, llamará a $this->controller->startupProcess() , que a su vez activará 3 eventos/métodos consecutivos:
initialize() : úselo para cargar componentesbeforeAction() : Realice cualquier acción lógica antes de llamar al método de acción del controladortriggerComponents() : método de inicio () de activación de componentes cargados El constructor de la clase Controller no debe anularse, sino que puede anular los métodos initialize() & beforeAction() en las clases de extensión.
Después del proceso de inicio de la finalización de ConstructCutor, su trabajo, entonces, se llamará al método de acción solicitado y se aprobarán los argumentos (si los hay).
Los componentes son los artículos intermedios. Proporcionan una lógica reutilizable para ser utilizada como parte del controlador. La autenticación, la autorización, la manipulación de formularios y los tokens CSRF se implementan dentro de los componentes.
Es mejor sacar estas piezas de lógica de la clase de controlador y mantener todas las tareas y validaciones dentro de estos componentes.
Cada componente hereda de la base/súper clase llamado Component . Cada uno tiene una tarea definida. Hay dos componentes, uno para autenticación y autorización llamados, y el otro llamado Seguridad para otros problemas de seguridad.
Son muy simples de tratar y se llamarán constructor de controladores internos.
¿El usuario tiene credenciales correctas?
El AuthComponent se encarga de la sesión de usuario.
¿Tiene derecho a acceder o realizar la acción X? El componente de Auth se encarga de la autorización para cada controlador. Por lo tanto, cada controlador debe implementar el método isAuthorized() . Lo que debe hacer es devolver el valor boolean .
Entonces, por ejemplo, para verificar si el usuario actual es administrador o no, haría algo como esto:
// AdminController
public function isAuthorized (){
$ role = Session:: getUserRole ();
if ( isset ( $ role ) && $ role === " admin " ){
return true ;
}
return false ;
} Si desea llevarlo más lejos y aplicar algunas reglas de permiso, hay una clase poderosa llamada Permission responsable de definir las reglas de permiso. Esta clase le permite definir "a quién puede realizar un método de acción específico en el controlador actual".
Entonces, por ejemplo, para permitir que los administradores realicen cualquier acción en las notas, mientras que los usuarios normales solo pueden editar sus notas:
// 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 );
}Ahora, puede verificar la autorización basada en el rol del usuario, el recurso y para cada método de acción.
El componente de seguridad se encarga de varias tareas de seguridad y validación.
Es importante restringir los métodos de solicitud. Como ejemplo, si tiene un método de acción que acepta los valores de formulario, por lo tanto, solo se aceptará la solicitud posterior. La misma idea para Ajax, Get, ..etc. Puede hacer esto dentro del método beforeAction() .
// NotesController
public function beforeAction (){
parent :: beforeAction ();
$ actions = [ ' create ' , ' delete ' ];
$ this -> Security -> requireAjax ( $ actions );
$ this -> Security -> requirePost ( $ actions );
}Además, si requiere que todas las solicitudes sean a través de una conexión segura, puede configurar todo el controlador o acciones específicas para redirigir todas las solicitudes a HTTPS en lugar de HTTP.
// NotesController
public function beforeAction (){
parent :: beforeAction ();
$ actions = [ ' create ' , ' delete ' ]; // specific action methods
$ actions = [ ' * ' ]; // all action methods
$ this -> Security -> requireSecure ( $ actions );
}Verifica y valida si la solicitud proviene del mismo dominio. Aunque pueden ser falsos, es bueno mantenerlos como parte de nuestras capas de seguridad.
Validar el formulario enviado proveniente de la solicitud posterior. La trampa de este método es que debe definir los campos de formulario esperados o los datos que se enviarán con la solicitud posterior.
De manera predeterminada, el marco validará para la manipulación de formulario cuando se realice una solicitud posterior, y se asegurará de que el token CSRF pase con los campos de formulario. En esta situación, si no pasó el token CSRF, se considerará como un hilo de seguridad.
// 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 ;
}
}Los tokens CSRF son importantes para validar los formularios enviados y asegurarse de que no sean falsificados. Un hacker puede engañar al usuario para que haga una solicitud a un sitio web, o haga clic en un enlace, y así sucesivamente.
Son válidos para una cierta duración (> = 1 día), luego se regenerará y almacenará en la sesión del usuario.
La validación de CSRF está deshabilitada de forma predeterminada. Si desea validar el token CSRF, asigne validateCsrfToken a true como se muestra en el ejemplo a continuación. La validación de CSRF se forzará cuando la solicitud sea POST y la manipulación de formulario está habilitada.
Ahora, no necesita verificar manualmente el token CSRF en todas las solicitudes. El componente de seguridad verificará el token en la solicitud versus el token almacenado en la sesión.
// 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 ;
}
}Los tokens CSRF se generan por sesión. Puede agregar un campo de forma oculta o en la URL como parámetro de consulta.
Forma
<input type="hidden" name="csrf_token" value="<?= Session::generateCsrfToken(); ?>" />
Url
<a href="<?= PUBLIC_ROOT . "?csrf_token=" . urlencode(Session::generateCsrfToken()); ?>">Link</a>
Javascript
También puede asignar el token CSRF a una variable JavaScript.
<script>config = <?= json_encode(Session::generateCsrfToken()); ?>;</script>
index.php en la carpeta de raíz pública. A veces debe tener un control sobre estos componentes, como cuando desea tener un controlador sin autenticación o autorización, o un componente de seguridad está habilitado. Esto se puede hacer al anular el método initialize() dentro de su clase de controlador y cargar solo los componentes necesarios.
Ejemplo 1 : No cargue ningún componente, no haya autenticación o autorización, o validaciones de seguridad.
public function initialize (){
$ this -> loadComponents ([]);
}Ejemplo 2 : Carga de seguridad y componente de autenticación, pero no se autentique y autorice, en caso de que desee usar el componente de autenticación dentro de los métodos de acción. Logincontroller es un ejemplo sobre cómo acceder a una página sin requerir un usuario registrado .
public function initialize (){
$ this -> loadComponents ([
' Auth ' ,
' Security '
]);
}Ejemplo 3 : Cargar seguridad y componente de autenticación, y autenticar usuario y autorizar el controlador actual. Este es el comportamiento predeterminado en la clase de núcleo/controlador
public function initialize (){
$ this -> loadComponents ([
' Auth ' => [
' authenticate ' => [ ' User ' ],
' authorize ' => [ ' Controller ' ]
],
' Security '
]);
}Dentro del método de acción, puede hacer una llamada al modelado para obtener algunos datos y/o realizar páginas dentro de la carpeta Vistas
// 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 ));
}En MVC, el modelo representa la información (los datos) y las reglas comerciales; La vista contiene elementos de la interfaz de usuario, como texto, entradas de formulario; y el controlador gestiona la comunicación entre el modelo y la vista. Fuente
Todas las operaciones como Crear, Eliminar, Actualizar y Validación se implementan en clases de modelos.
// 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 " );
}
}En el modelo de notas
// 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 ;
}Usando el marco, probablemente lo iniciaría, registraría y lo cierre. Estas acciones se implementan en APP/Models/Login & App/Controllers/Logincontroller . En la mayoría de las situaciones, no necesitará modificar nada relacionado con las acciones de inicio de sesión, solo comprender el comportamiento del marco.
Tenga en cuenta que si no tiene SSL, es mejor que quiera cifrar datos manualmente en el lado del cliente, si es así, lea esto y también esto.
Cada vez que el usuario se registre, se enviará un correo electrónico con token concatenado con ID de usuario cifrado. Este token será expirado después de las 24 horas. Es mucho mejor expirar estos tokens y reutilizar el correo electrónico registrado si están expirados.
Las contraseñas se hash utilizan los últimos algoritmos en PHP v5.5
$ hashedPassword = password_hash ( $ password , PASSWORD_DEFAULT , array ( ' cost ' => Config:: get ( ' HASH_COST_FACTOR ' )));Si el usuario olvidó su contraseña, puede restaurarla. La misma idea de fichas caducadas va aquí.
Además, bloquee al usuario con cierta duración (> = 10 minutos) si excedió el número de intentos de contraseñas olvidadas (5) durante una cierta duración (> = 10min).
Los ataques de fuerza bruta de estrangulamiento son cuando un hacker intenta toda la combinación de entrada posible hasta que encuentre la contraseña correcta.
Solución:
Los CAPTCHA son particularmente efectivos para prevenir inicios de sesión automatizados. Usando Captcha, una impresionante biblioteca PHP Captcha.
Bloquear las direcciones IP es la última solución en la que pensar. La dirección IP se bloqueará si la misma IP no pudo iniciar sesión varias veces usando diferentes credenciales (> = 10).
Los objetos de datos de PHP (PDO) se usan para preparar y ejecutar consultas de bases de datos. Dentro de la clase Database , hay varios métodos que ocultan la complejidad y le permiten instanciar el objeto de la base de datos, preparar, vincular y ejecutar en pocas líneas.
SELECT, INSERT, UPDATE, DELETE es suficiente para los usuariosAdmin .utf8mb4 en el nivel de la base de datos.utf8 Charset solo almacena símbolos codificados UTF-8 que consisten en uno o tres bytes. Pero, no puede para símbolos con cuatro bytes.utf8 . Pero, si desea actualizar a utf8mb4 , siga estos enlaces:utf8mb4 La clase Encryption es responsable de encriptar y descifrarse de datos. El cifrado se aplica a cosas como cookies, ID de usuario, ID de publicación, ..etc. Las cadenas encriptadas se autentican y son diferentes cada vez que cifras.
La validación es una pequeña biblioteca para validar las entradas del usuario. Todas las reglas de validación están dentro de la clase 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 ());
} La clase Handler es responsable de manejar todas las excepciones y errores. Utilizará Logger para registrar errores. El informe de error se desactiva de forma predeterminada, porque cada error se registrará y guardará en App/logs/log.txt .
Si se inició el error o se inició una excepción, la aplicación mostrará un error interno del sistema (500).
Un lugar donde puede registrar cualquier cosa y guardarlo en app/log/log.txt . Puede escribir cualquier fallas, errores, excepciones o cualquier otra acción o ataques maliciosos.
Logger:: log ( " COOKIE " , self :: $ userId . " is trying to login using invalid cookie " , __FILE__ , __LINE__ ); Los correos electrónicos se envían utilizando PHPMailer a través de SMTP, otra biblioteca para enviar correos electrónicos. No debe usar la función mail() de PHP.
En la aplicación/configuración , hay dos archivos, uno llamado config.php para las configuraciones de la aplicación principal y otro para javascript llamado javaScript.php . Las configuraciones de JavaScript se asignarán a una variable JavaScript en su pie de página.php .
Para enviar una solicitud y recibir una respuesta, puede depender de las llamadas AJAX para hacerlo. Este marco depende en gran medida de las solicitudes de AJAX para realizar acciones, pero aún puede hacer lo mismo para las solicitudes normales con solo pequeños ajustes.
El objeto de configuración se asigna a pares de valor clave en el pie Footer.php. Estos pares de valor clave se pueden agregar en el código del lado del servidor usando Config::setJsConfig('key', "value"); , que se asignará luego al objeto de configuración .
AJAX Un espacio de nombres que tiene dos funciones principales para enviar una solicitud AJAX. Uno para llamadas AJAX normales y otra para cargar archivos.
ayudantes un espacio de nombres que tiene una variedad de funciones que muestran errores, serializar, redirigir, codehtml, etc.
Aplicar un espacio de nombres que se usa para initalizar todos los eventos de JavaScript para la página actual
Eventos Un espacio de nombres que se usa para declarar todos los eventos que pueden ocurrir, como cuando el usuario hace clic en un enlace para crear, eliminar o actualizar.
Para mostrar cómo usar el marco en una situación de la vida real, el marco viene con la implementación para características como administrar la gestión del perfil de usuario, el tablero de tablas, las noticias, la carga y la descarga de archivos, publicaciones y comentarios, paginación, panel de administración, administración de copias de seguridad del sistema, notificatones, errores de informes, ... etc.
Pasos:
Editar archivo de configuración en app/config/config.php con sus credenciales
Ejecutar consultas SQL en _ Directorio de instalación en orden
Acceso
Configuración de correo electrónico
Debe configurar los datos de su cuenta SMTP en APP/config/config.php . Pero , si no tiene una cuenta SMTP, entonces guarda correos electrónicos en App/Logs/Log.txt usando Logger.
Para hacer eso, en el núcleo/correo electrónico, comentar $mail->Send() y uncomment Logger::log("EMAIL", $mail->Body);
Cada usuario puede cambiar su nombre, correo electrónico, contraseña. También cargue una imagen de perfil (es decir, inicialmente asignada a default.png).
Cada vez que el usuario pide cambiar su correo electrónico, se enviará una notificación al correo electrónico anterior del usuario y al nuevo.
La notificación enviada al correo electrónico antiguo está dando al usuario la oportunidad de revocar el cambio de correo electrónico, mientras que la notificación enviada al nuevo correo electrónico solicita confirmación. El usuario aún puede iniciar sesión con su correo electrónico anterior hasta que confirme el cambio.
Esto se realiza en UserController , en Methods updateProfileInfo() , revokeEmail() y updateEmail() . En la mayoría de las situaciones, no necesitará modificar el comportamiento de estos métodos.
Puede cargar y descargar archivos.
file_uploads en verdadupload_max_filesize, max_file_uploads, post_max_sizePiense en las noticias como tweets en Twitter, y en publicaciones como cuando abres un problema en GitHub.
Se implementan en la parte superior de este marco.
Los administradores pueden realizar acciones donde los usuarios normales no pueden. Pueden eliminar, editar, crear cualquier fuente de noticias, publicar o hacer comentarios. También tienen control sobre todos los perfiles de usuario, crean y restauran copias de seguridad.
Solo los administradores tienen acceso para ver a todos los usuarios registrados. Pueden eliminar, editar su información.
En la mayoría de las situaciones, deberá crear copias de seguridad para el sistema y restaurarlas cuando lo desee.
Esto se hace usando MySQLDump para crear y restaurar copias de seguridad. Todas las copias de seguridad se almacenarán en aplicaciones/copias de seguridad .
¿Viste las notificaciones rojas en Facebook o la azul en Twitter? La misma idea está aquí. Pero, se implementa utilizando desencadenantes. Los desencadenantes se definen en _ Instalación/disparadores.sql .
Entonces, cada vez que el usuario crea una nueva fuente de noticias, publica o cargue un archivo, esto aumentará el recuento para todos los demás usuarios y mostrará una notificación roja en la barra de navegación.
Los usuarios pueden informar errores, características y mejoras. Una vez que enviaron el formulario, se enviará un correo electrónico a ADMIN_EMAIL definido en App/config/config.php
Supongamos que desea construir una aplicación simple. Aquí, iré paso a paso sobre cómo crear una aplicación TODO usando el marco con y sin llamadas AJAX.
(1) Si siguió los pasos de configuración de instalación anteriores, no debería tener ningún problema con la creación de cuentas de usuario iniciales.
(2) Cree una tabla con ID como int, contenido varchar, user_id como clave extranjera para la tabla 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) Crear todocontroller
Cree un archivo llamado TodoController.php Inside App/Controllers
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) Crear clase de modelo de nota llamada Todo.php en APP/Modelos
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) Vistas interiores/
(a) Crear header.php & footer.php dentro de las vistas/diseño/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) Vistas interiores/ Crear carpeta TODO que tendrá index.php , que contendrá nuestra lista 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) Código de JavaScript para enviar llamadas AJAX y manejar responder
// 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 ) ;
}
}
} ) ;
}
}
}He escrito este guión en mi tiempo libre durante mis estudios. Esto es gratis, no pagado. Estoy diciendo esto porque he visto a muchos desarrolladores muy groseros con cualquier software, y su comportamiento es realmente frustrante. ¿No sé por qué? Todos tienden a quejarse y decir palabras duras. Acepto los comentarios, pero, de una manera buena y respetuosa.
Hay muchos otros scripts en línea para la compra que hace lo mismo (si no menos), y sus autores están ganando buen dinero, pero elijo mantenerlo público, disponible para todos.
Si aprendió algo, o ahorré su tiempo, admite el proyecto difundiendo la palabra.
Contribuya creando nuevos problemas, enviando solicitudes de extracción en GitHub o puede enviar un correo electrónico a: [email protected]
Construido bajo la licencia del MIT.