Um pequeno e simples esqueleto de estrutura de MVC PHP que encapsula muitos recursos cercados por poderosas camadas de segurança.
O MinIPHP é uma aplicação muito simples, útil para pequenos projetos, ajuda a entender o esqueleto do PHP MVC, a saber como autenticar e autorizar, criptografar dados e aplicar conceitos de segurança, higienização e validação, fazer chamadas de AJAX e muito mais.
Não é uma estrutura completa, nem muito básica, mas não é complicada. Você pode instalar, entender e usá -lo facilmente em qualquer um de seus projetos.
É recuado remover a complexidade das estruturas. Coisas como roteamento, autenticação, autorização, gerenciamento de sessões e cookies do usuário e assim por diante não são algo que eu inventei do arranhão, no entanto, são agregação de conceitos já implementados em outras estruturas, mas, construídas de uma maneira muito mais simples, para que você possa entendê -lo e levá -lo mais longe.
Se você precisar criar um aplicativo maior e aproveitar a maioria dos recursos disponíveis nas estruturas, poderá ver CakePhp, Laravel, Symphony.
De qualquer forma, é importante entender o esqueleto PHP MVC e saber como autenticar e autorizar, aprender sobre problemas de segurança e como você pode derrotar e como criar seu aplicativo usando a estrutura.
A documentação completa também pode ser encontrada aqui - criada pelo gerador automático de páginas do GitHub.
Uma demonstração ao vivo está disponível aqui. A demonstração ao vivo é para o aplicativo de demonstração construído sobre essa estrutura nesta seção. Obrigado a @everterstraat.
Alguns apresentam que não funcionam na demonstração.
Instale via compositor
composer install
Sempre que você fizer uma solicitação para o aplicativo, ele será direcionado para index.php dentro da pasta pública. Portanto, se você fizer uma solicitação: http://localhost/miniPHP/User/update/412 . Isso será dividido e traduzido para
De fato, o Htaccess divide tudo vem depois de http://localhost/miniPHP e o adiciona ao URL como argumento de tentativa de consultas. Portanto, essa solicitação será convertida para: http://localhost/miniPHP?url='User/update/412' .
Em seguida, a classe App , dentro splitUrl() , dividirá a sequência de consulta $_GET['url'] no controlador, método de ação e quaisquer argumentos passados ao método de ação.
Na classe App , Inside run() , ele instanciará um objeto da classe Controller e fará uma chamada ao método de ação, passando qualquer argumento, se existir.
Depois que a classe App está integrando o objeto do controlador, ele chamará $this->controller->startupProcess() método, que por sua vez acionará 3 eventos/métodos consecutivos:
initialize() : use -o para carregar componentesbeforeAction() : execute quaisquer ações lógicas antes de chamar o método de ação do controladortriggerComponents() : tather startup () método de componentes carregados O construtor da classe Controller não deve ser substituído; em vez disso, você pode substituir os métodos initialize() e beforeAction() nas classes estendendo.
Após o processo de inicialização do construtor terminar seu trabalho, o método de ação solicitado será chamado e os argumentos serão aprovados (se houver).
Os componentes são os mundos médios. Eles fornecem lógica reutilizável a ser usada como parte do controlador. Autenticação, autorização, adulteração e validar os tokens CSRF são implementados dentro dos componentes.
É melhor retirar essas peças de lógica da classe do controlador e manter todas as várias tarefas e validações dentro desses componentes.
Cada componente herda da base/super classe chamada Component . Cada um tem uma tarefa definida. Existem dois componentes, um para chamado Auth for Authentication and Authorization, e o outro chamado de segurança para outros problemas de segurança.
Eles são muito simples de lidar e serão chamados Inside Controller Constructor.
O usuário tem credenciais certas?
O AuthComponent cuida da sessão do usuário.
Você tem o direito de acessar ou executar a ação X?. O componente de autenticação cuida da autorização para cada controlador. Assim, cada controlador deve implementar o método isAuthorized() . O que você precisa fazer é retornar o valor boolean .
Por exemplo, para verificar se o usuário atual é administrador ou não, você faria algo assim:
// AdminController
public function isAuthorized (){
$ role = Session:: getUserRole ();
if ( isset ( $ role ) && $ role === " admin " ){
return true ;
}
return false ;
} Se você deseja ir mais longe e aplicar algumas regras de permissão, existe uma classe poderosa chamada Permission responsável pela definição de regras de permissão. Esta classe permite definir "quem tem permissão para executar um método de ação específico no controlador atual".
Assim, por exemplo, para permitir que os administradores executem qualquer ação nas notas, enquanto os usuários normais só podem editar suas anotações:
// 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 );
}Agora, você pode verificar a autorização com base na função do usuário, recurso e para cada método de ação.
O SecurityComponent cuida de várias tarefas de segurança e validação.
É importante restringir os métodos de solicitação. Como exemplo, se você tiver um método de ação que aceite valores de formulário, portanto, apenas a solicitação de postagem será aceita. A mesma idéia para Ajax, Get, ..etc. Você pode fazer isso dentro do método beforeAction() .
// NotesController
public function beforeAction (){
parent :: beforeAction ();
$ actions = [ ' create ' , ' delete ' ];
$ this -> Security -> requireAjax ( $ actions );
$ this -> Security -> requirePost ( $ actions );
}Além disso, se você precisar de todas as solicitações para obter conexão segura, poderá configurar o controlador inteiro ou ações específicas para redirecionar todas as solicitações para HTTPS em vez de HTTP.
// NotesController
public function beforeAction (){
parent :: beforeAction ();
$ actions = [ ' create ' , ' delete ' ]; // specific action methods
$ actions = [ ' * ' ]; // all action methods
$ this -> Security -> requireSecure ( $ actions );
}Ele verifica e valida se a solicitação for proveniente do mesmo domínio. Embora possam ser falsificados, é bom mantê -los como parte de nossas camadas de segurança.
Validar o formulário enviado proveniente da solicitação de postagem. A armadilha deste método é que você precisa definir os campos de formulário esperados ou dados que serão enviados com solicitação de postagem.
Por padrão, a estrutura validará a adulteração do formulário quando a solicitação de postagem for feita e garantirá que o token CSRF seja passado com os campos do formulário. Nesta situação, se você não passar no token do CSRF, ele será considerado um tópico de segurança.
// 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 ;
}
}Os tokens de CSRF são importantes para validar os formulários enviados e para garantir que eles não sejam falsificados. Um hacker pode enganar o usuário a fazer uma solicitação a um site ou clicar em um link e assim por diante.
Eles são válidos por uma certa duração (> = 1 dia), então serão regenerados e armazenados na sessão do usuário.
A validação do CSRF está desativada por padrão. Se você deseja validar o token CSRF, atribua validateCsrfToken ao true como mostrado no exemplo abaixo. A validação do CSRF será forçada quando a solicitação for postagem e a adulteração for ativada.
Agora, você não precisa verificar manualmente o token do CSRF em todas as solicitações. O componente de segurança verificará o token na solicitação versus o token armazenado na sessão.
// 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 ;
}
}Os tokens de CSRF são gerados por sessão. Você pode adicionar um campo de formulário oculto ou no 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
Você também pode atribuir o token CSRF a uma variável JavaScript.
<script>config = <?= json_encode(Session::generateCsrfToken()); ?>;</script>
index.php na pasta raiz pública. Às vezes, você precisa ter um controle sobre esses componentes, como quando deseja ter um controlador sem autenticação ou autorização, ou um componente de segurança está ativado. Isso pode ser feito pela substituição do método initialize() dentro da classe do controlador e a carga necessária apenas componentes.
Exemplo 1 : Não carregue nenhum componente, nenhuma autenticação ou autorização ou validações de segurança.
public function initialize (){
$ this -> loadComponents ([]);
}Exemplo 2 : Carregar segurança e componente de autenticação, mas não se autentique e autorize, caso você queira usar o componente de autenticação dentro dos métodos de ação. Logincontroller é um exemplo sobre como acessar uma página sem exigir um usuário logado .
public function initialize (){
$ this -> loadComponents ([
' Auth ' ,
' Security '
]);
}Exemplo 3 : Carregar segurança e componente de autenticação e autenticar o usuário e autorizar o controlador atual. Este é o comportamento padrão na classe Core/Controller
public function initialize (){
$ this -> loadComponents ([
' Auth ' => [
' authenticate ' => [ ' User ' ],
' authorize ' => [ ' Controller ' ]
],
' Security '
]);
}Dentro do método de ação, você pode fazer uma chamada para modelar para obter alguns dados e/ou renderizar páginas dentro da pasta Visualizações
// 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 ));
}No MVC, o modelo representa as informações (os dados) e as regras de negócios; A visualização contém elementos da interface do usuário, como texto, entradas de formulário; e o controlador gerencia a comunicação entre o modelo e a visualização. Fonte
Todas as operações como criar, excluir, atualizar e validação são implementadas nas classes 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 " );
}
}No 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 a estrutura, você provavelmente fará login, registro e logout. Essas ações são implementadas em aplicativos/modelos/login & app/controladores/logincontroller . Na maioria das situações, você não precisará modificar nada relacionado às ações de login, basta entender o comportamento da estrutura.
Observe que se você não tiver SSL, é melhor criptografar dados manualmente no lado do cliente, se assim for, leia isso e também isso.
Sempre que o usuário se registrar, um email será enviado com o token concatenado com ID de usuário criptografado. Este token será expirado após 24 horas. É muito melhor expirar esses tokens e reutilizar o email registrado se eles estiverem expirados.
As senhas são hash com os algoritmos mais recentes no PHP v5.5
$ hashedPassword = password_hash ( $ password , PASSWORD_DEFAULT , array ( ' cost ' => Config:: get ( ' HASH_COST_FACTOR ' )));Se o usuário esqueceu sua senha, ele poderá restaurá -la. A mesma idéia de tokens expirados vai aqui.
Além disso, o usuário do bloco de certa duração (> = 10min) se ele exceder o número de senhas esquecidas tentativas (5) durante uma certa duração (> = 10min).
Os ataques de força bruta é quando um hacker tenta toda a combinação de entrada possível até encontrar a senha correta.
Solução:
Os captchas são particularmente eficazes na prevenção de logins automatizados. Usando o Captcha, uma incrível biblioteca PHP CAPTCHA.
Bloquear endereços IP é a última solução a pensar. O endereço IP será bloqueado se o mesmo IP falhar ao fazer o login várias vezes usando credenciais diferentes (> = 10).
Os objetos de dados PHP (DOP) são usados para preparar e executar consultas de banco de dados. Classe Database interna, existem vários métodos que ocultam a complexidade e permitem instanciar o objeto de banco de dados, preparar, vincular e executar em poucas linhas.
SELECT, INSERT, UPDATE, DELETE são suficientes para usuáriosAdmin .utf8mb4 no nível do banco de dados.utf8 Charset do MySQL apenas armazenou símbolos codificados UTF-8 que consistem em um a três bytes. Mas, não pode para símbolos com quatro bytes.utf8 . Mas, se você deseja atualizar para utf8mb4 , siga estes links:utf8mb4 A classe Encryption é responsável por criptografar e descriptografia de dados. A criptografia é aplicada a coisas como cookies, ID de usuário, ID de postagem, ..etc. As cordas criptografadas são autenticadas e são diferentes toda vez que você criptografa.
A validação é uma pequena biblioteca para validar as entradas do usuário. Todas as regras de validação estão dentro da 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 ());
} A classe Handler é responsável por lidar com todas as exceções e erros. Ele usará o Logger para registrar erros. O relatório de erro é desligado por padrão, porque todos os erros serão registrados e salvos no app/logs/log.txt .
Se o erro encontrado ou a exceção foi lançada, o aplicativo mostrará o erro interno do sistema (500).
Um local onde você pode registrar qualquer coisa e salvá -lo no app/log/log.txt . Você pode escrever quaisquer falhas, erros, exceções ou quaisquer outras ações ou ataques maliciosos.
Logger:: log ( " COOKIE " , self :: $ userId . " is trying to login using invalid cookie " , __FILE__ , __LINE__ ); Os e -mails são enviados usando o PHPMAILER via SMTP, outra biblioteca para enviar e -mails. Você não deve usar mail() Função do PHP.
No App/Config , existem dois arquivos, um chamado config.php para configurações de aplicativos principais e outro para JavaScript chamado javascript.php . As configurações JavaScript serão atribuídas a uma variável JavaScript em seu rodapé.php .
Para enviar solicitação e receber uma resposta, você pode depender das chamadas do AJAX para fazê -lo. Essa estrutura depende muito das solicitações do AJAX para executar ações, mas você ainda pode fazer a mesma coisa para solicitações normais com apenas pequenos ajustes.
O objeto de configuração é atribuído a pares de valor-chave no rodapé.php. Esses pares de valor-chave podem ser adicionados no código do lado do servidor usando Config::setJsConfig('key', "value"); , que será atribuído e depois para configurar objeto.
Ajax Um espaço para nome que possui duas funções principais para enviar solicitação de Ajax. Um para chamadas normais de Ajax e outro para fazer upload de arquivos.
Ajudantes um espaço para nome que possui variedade de funções exibe erros, serializar, redirecionar, codehtml e assim por diante
App A Namespace que é usado para initalizar todos os eventos JavaScript para a página atual
Eventos Um espaço para nome usado para declarar todos os eventos que podem ocorrer, como quando o usuário clica em um link para criar, excluir ou atualizar.
Para mostrar como usar a estrutura em uma situação da vida real, a estrutura vem com implementação para recursos como gerenciar gerenciamento de perfil de usuário, painel, feed de notícias, fazer upload e download de arquivos, postagens e comentários, paginação, painel de administração, gerenciar backups do sistema, notificadoras de notificação, relatórios de relatórios, ... etc.
Passos:
Editar arquivo de configuração no app/config/config.php com suas credenciais
Executar consultas SQL no diretório de instalação em ordem
Conecte-se
Configuração de e -mail
Você precisa configurar os dados da sua conta SMTP no app/config/config.php . Mas , se você não tiver conta SMTP, salva e -mails no aplicativo/logs/log.txt usando o Logger.
Para fazer isso, no núcleo/e-mail, comente $mail->Send() e Logger::log("EMAIL", $mail->Body);
Todo usuário pode alterar seu nome, email, senha. Também faça upload da imagem do perfil (ou seja, atribuído inicialmente ao padrão.png).
Sempre que o usuário pedir para alterar seu email, uma notificação será enviada para o e -mail antigo do usuário e o novo.
A notificação enviada para o e -mail antigo está dando ao usuário a chance de revogar a alteração do email, enquanto a notificação enviada ao novo email está solicitando confirmação. O usuário ainda pode fazer login com seu e -mail antigo até confirmar a alteração.
Isso é feito no UserController , nos métodos updateProfileInfo() , revokeEmail() e updateEmail() . Na maioria das situações, você não precisará modificar o comportamento desses métodos.
Você pode fazer upload e baixar arquivos.
file_uploads como trueupload_max_filesize, max_file_uploads, post_max_sizePense no feed de notícias como tweets no Twitter e em postagens como quando você abre um problema no Github.
Eles são implementados no topo desta estrutura.
Os administradores podem executar ações onde os usuários normais não podem. Eles podem excluir, editar, criar qualquer feed de notícias, postar ou comentar. Eles também têm controle sobre todos os perfis de usuário, criam e restaurando backups.
Somente os administradores têm acesso para ver todos os usuários registrados. Eles podem excluir, editar suas informações.
Na maioria das situações, você precisará criar backups para o sistema e restaurá -los sempre que quiser.
Isso é feito usando o MySqldump para criar e restaurar backups. Todos os backups serão armazenados em aplicativos/backups .
Você viu as notificações vermelhas no Facebook ou a azul no Twitter?. A mesma ideia está aqui. Mas, é implementado usando gatilhos. Os gatilhos são definidos em _ instalação/gatrigers.sql .
Portanto, sempre que o usuário criar um novo feed de notícias, postar ou fazer upload de um arquivo, isso aumentará a contagem para todos os outros usuários e exibirá uma notificação vermelha na barra de navegação.
Os usuários podem relatar bugs, recursos e aprimoramentos. Depois de enviarem o formulário, um email será enviado para ADMIN_EMAIL definido em app/config/config.php
Digamos que você queira criar um aplicativo simples para TODO. Aqui, vou passo a passo sobre como criar um aplicativo TODO usando a estrutura com e sem chamadas AJAX.
(1) Se você seguir as etapas de instalação de instalação acima, não terá nenhum problema na criação de contas de usuário iniciais.
(2) Crie uma tabela com id como int, conteúdo varchar, user_id como chave estrangeira para a tabela 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) Crie Todocontroller
Crie um arquivo chamado 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) Crie a classe de modelo de nota chamada Todo.php em aplicativo/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 internas/
(a) Criar 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) Vistas internas/ Criar pasta TODO que terá index.php , que conterá nossa 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 JavaScript para enviar chamadas AJAX e manipular 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 ) ;
}
}
} ) ;
}
}
}Escrevi esse script no meu tempo livre durante meus estudos. Isso é de graça, não remunerado. Estou dizendo isso porque vi muitos desenvolvedores agirem muito rudes em relação a qualquer software, e o comportamento deles é realmente frustrante. Eu não sei por quê?! Todos tendem a reclamar e dizer palavras duras. Aceito o feedback, mas de uma maneira boa e respeitosa.
Existem muitos outros scripts on -line para compra que fazem a mesma coisa (se não menos), e seus autores estão ganhando um bom dinheiro, mas eu escolho mantê -lo público, disponível para todos.
Se você aprendeu algo, ou economizei seu tempo, apoie o projeto, espalhando a palavra.
Contribua criando novos problemas, enviando solicitações de puxar no github ou você pode enviar um e -mail em: [email protected]
Construído sob licença do MIT.