Nenhum produto encontrado nessa seleção.
Para terminar nossa aplicação com estrutura MVC em PHP, vamos criar um sistema de cadastro de notícias bem simples, com campos de título, data, autor, texto e URL da imagem.
Além disso, criaremos uma área administrativa para gerenciar as notícias, onde poderemos criar, apagar, editar e listar notícias cadastradas.
Este é apenas um exemplo sobre como iremos utilizar nossa aplicação MVC em PHP. A partir desse tutorial você estará apto a criar seus próprios modelos, controladores e views simplesmente seguindo o mesmo modelo.
Download
Antes de qualquer coisa, faça o download da aplicação e acompanhe em seu computador. Segue o link para download:
- Download crud-1-0.zip
Caso queira contribuir com o projeto, acesso no Github:
Se tiver perdido alguma parte desse tutorial, deixo os links para facilitar sua vida:
- Utilizando estrutura MVC em PHP – Parte 1
- Utilizando estrutura MVC em PHP – Parte 2
- Utilizando estrutura MVC em PHP – Parte 3
- Utilizando estrutura MVC em PHP – Parte 4
- Utilizando estrutura MVC em PHP – Parte 5 (este artigo)
Então vamos codificar um pouco…
Criando o controller para as notícias
Primeiramente vamos criar o nosso controller, portando acesse a pasta “controllers” e crie um arquivo chamado “noticias-controller.php”.
Neste controller teremos que ter duas ações:
- index (pública) – Lista as notícias na página e não precisa de usuário e senha para o acesso;
- adm (restrita) – Gerenciamento das notícias – CRUD com PDO
Veja o código do controller:
<?php /** * NoticiasController - Controller de exemplo * * @package TutsupMVC * @since 0.1 */ class NoticiasController extends MainController { /** * $login_required * * Se a página precisa de login * * @access public */ public $login_required = false; /** * $permission_required * * Permissão necessária * * @access public */ public $permission_required; /** * INDEX * * Carrega a página "/views/noticias/index.php" */ public function index() { // Título da página $this->title = 'Notícias'; // Carrega o modelo para este view $modelo = $this->load_model('noticias/noticias-adm-model'); /** Carrega os arquivos do view **/ // /views/_includes/header.php require ABSPATH . '/views/_includes/header.php'; // /views/_includes/menu.php require ABSPATH . '/views/_includes/menu.php'; // /views/noticias/index.php require ABSPATH . '/views/noticias/noticias-view.php'; // /views/_includes/footer.php require ABSPATH . '/views/_includes/footer.php'; } // index /** * ADM * * Carrega a página "/views/noticias/noticias-adm-view.php" */ public function adm() { // Page title $this->title = 'Gerenciar notícias'; $this->permission_required = 'gerenciar-noticias'; // Verifica se o usuário está logado if ( ! $this->logged_in ) { // Se não; garante o logout $this->logout(); // Redireciona para a página de login $this->goto_login(); // Garante que o script não vai passar daqui return; } // Verifica se o usuário tem a permissão para acessar essa página if (!$this->check_permissions($this->permission_required, $this->userdata['user_permissions'])) { // Exibe uma mensagem echo 'Você não tem permissões para acessar essa página.'; // Finaliza aqui return; } // Carrega o modelo para este view $modelo = $this->load_model('noticias/noticias-adm-model'); /** Carrega os arquivos do view **/ // /views/_includes/header.php require ABSPATH . '/views/_includes/header.php'; // /views/_includes/menu.php require ABSPATH . '/views/_includes/menu.php'; // /views/noticias/index.php require ABSPATH . '/views/noticias/noticias-adm-view.php'; // /views/_includes/footer.php require ABSPATH . '/views/_includes/footer.php'; } // adm } // class NoticiasController
Veja que temos duas ações agora, index e adm. Ambas serão acessadas pelas URLs:
- index: dominio.com/noticias/
- adm: dominio.com/noticias/adm/
Agora vamos criar o modelo para as notícias.
Criando o model para as notícias
Nosso model será compartilhado pelos dois views que teremos, um para a ação “adm” outro para “index”.
Lembre-se: Modelos ficam dentro da pasta models separados por pastas. Nosso model estará dentro da pasta noticias com o nome de noticias-adm-model.php.
Veja seu código:
<?php /** * Modelo para gerenciar notícias * * @package TutsupMVC * @since 0.1 */ class NoticiasAdmModel extends MainModel { /** * $posts_per_page * * Receberá o número de posts por página para configurar a listagem de * notícias. Também utilizada na paginação. * * @access public */ public $posts_por_pagina = 5; /** * Construtor para essa classe * * Configura o DB, o controlador, os parâmetros e dados do usuário. * * @since 0.1 * @access public * @param object $db Objeto da nossa conexão PDO * @param object $controller Objeto do controlador */ public function __construct( $db = false, $controller = null ) { // Configura o DB (PDO) $this->db = $db; // Configura o controlador $this->controller = $controller; // Configura os parâmetros $this->parametros = $this->controller->parametros; // Configura os dados do usuário $this->userdata = $this->controller->userdata; } /** * Lista notícias * * @since 0.1 * @access public * @return array Os dados da base de dados */ public function listar_noticias () { // Configura as variáveis que vamos utilizar $id = $where = $query_limit = null; // Verifica se um parâmetro foi enviado para carregar uma notícia if ( is_numeric( chk_array( $this->parametros, 0 ) ) ) { // Configura o ID para enviar para a consulta $id = array ( chk_array( $this->parametros, 0 ) ); // Configura a cláusula where da consulta $where = " WHERE noticia_id = ? "; } // Configura a página a ser exibida $pagina = ! empty( $this->parametros[1] ) ? $this->parametros[1] : 1; // A paginação inicia do 0 $pagina--; // Configura o número de posts por página $posts_por_pagina = $this->posts_por_pagina; // O offset dos posts da consulta $offset = $pagina * $posts_por_pagina; /* Esta propriedade foi configurada no noticias-adm-model.php para prevenir limite ou paginação na administração. */ if ( empty ( $this->sem_limite ) ) { // Configura o limite da consulta $query_limit = " LIMIT $offset,$posts_por_pagina "; } // Faz a consulta $query = $this->db->query( 'SELECT * FROM noticias ' . $where . ' ORDER BY noticia_id DESC' . $query_limit, $id ); // Retorna return $query->fetchAll(); } // listar_noticias /** * Obtém a notícia e atualiza os dados se algo for postado * * Obtém apenas uma notícia da base de dados para preencher o formulário de * edição. * Configura a propriedade $this->form_data. * * @since 0.1 * @access public */ public function obtem_noticia () { // Verifica se o primeiro parâmetro é "edit" if ( chk_array( $this->parametros, 0 ) != 'edit' ) { return; } // Verifica se o segundo parâmetro é um número if ( ! is_numeric( chk_array( $this->parametros, 1 ) ) ) { return; } // Configura o ID da notícia $noticia_id = chk_array( $this->parametros, 1 ); /* Verifica se algo foi postado e se está vindo do form que tem o campo insere_noticia. Se verdadeiro, atualiza os dados conforme a requisição. */ if ( 'POST' == $_SERVER['REQUEST_METHOD'] && ! empty( $_POST['insere_noticia'] ) ) { // Remove o campo insere_notica para não gerar problema com o PDO unset($_POST['insere_noticia']); // Verifica se a data foi enviada $data = chk_array( $_POST, 'noticia_data' ); /* Inverte a data para os formatos dd-mm-aaaa hh:mm:ss ou aaaa-mm-dd hh:mm:ss */ $nova_data = $this->inverte_data( $data ); // Adiciona a data no $_POST $_POST['noticia_data'] = $nova_data; // Tenta enviar a imagem $imagem = $this->upload_imagem(); // Verifica se a imagem foi enviada if ( $imagem ) { // Adiciona a imagem no $_POST $_POST['noticia_imagem'] = $imagem; } // Atualiza os dados $query = $this->db->update('noticias', 'noticia_id', $noticia_id, $_POST); // Verifica a consulta if ( $query ) { // Retorna uma mensagem $this->form_msg = '<p class="success">Notícia atualizada com sucesso!</p>'; } } // Faz a consulta para obter o valor $query = $this->db->query( 'SELECT * FROM noticias WHERE noticia_id = ? LIMIT 1', array( $noticia_id ) ); // Obtém os dados $fetch_data = $query->fetch(); // Se os dados estiverem nulos, não faz nada if ( empty( $fetch_data ) ) { return; } // Configura os dados do formulário $this->form_data = $fetch_data; } // obtem_noticia /** * Insere notícias * * @since 0.1 * @access public */ public function insere_noticia() { /* Verifica se algo foi postado e se está vindo do form que tem o campo insere_noticia. */ if ( 'POST' != $_SERVER['REQUEST_METHOD'] || empty( $_POST['insere_noticia'] ) ) { return; } /* Para evitar conflitos apenas inserimos valores se o parâmetro edit não estiver configurado. */ if ( chk_array( $this->parametros, 0 ) == 'edit' ) { return; } // Só pra garantir que não estamos atualizando nada if ( is_numeric( chk_array( $this->parametros, 1 ) ) ) { return; } // Tenta enviar a imagem $imagem = $this->upload_imagem(); // Verifica se a imagem foi enviada if ( ! $imagem ) { return; } // Remove o campo insere_notica para não gerar problema com o PDO unset($_POST['insere_noticia']); // Insere a imagem em $_POST $_POST['noticia_imagem'] = $imagem; // Configura a data $data = chk_array( $_POST, 'noticia_data' ); $nova_data = $this->inverte_data( $data ); // Adiciona a data no POST $_POST['noticia_data'] = $nova_data; // Insere os dados na base de dados $query = $this->db->insert( 'noticias', $_POST ); // Verifica a consulta if ( $query ) { // Retorna uma mensagem $this->form_msg = '<p class="success">Notícia atualizada com sucesso!</p>'; return; } // :( $this->form_msg = '<p class="error">Erro ao enviar dados!</p>'; } // insere_noticia /** * Apaga a notícia * * @since 0.1 * @access public */ public function apaga_noticia () { // O parâmetro del deverá ser enviado if ( chk_array( $this->parametros, 0 ) != 'del' ) { return; } // O segundo parâmetro deverá ser um ID numérico if ( ! is_numeric( chk_array( $this->parametros, 1 ) ) ) { return; } // Para excluir, o terceiro parâmetro deverá ser "confirma" if ( chk_array( $this->parametros, 2 ) != 'confirma' ) { // Configura uma mensagem de confirmação para o usuário $mensagem = '<p class="alert">Tem certeza que deseja apgar a notícia?</p>'; $mensagem .= '<p><a href="' . $_SERVER['REQUEST_URI'] . '/confirma/">Sim</a> | '; $mensagem .= '<a href="' . HOME_URI . '/noticias/adm/">Não</a></p>'; // Retorna a mensagem e não excluir return $mensagem; } // Configura o ID da notícia $noticia_id = (int)chk_array( $this->parametros, 1 ); // Executa a consulta $query = $this->db->delete( 'noticias', 'noticia_id', $noticia_id ); // Redireciona para a página de administração de notícias echo '<meta http-equiv="Refresh" content="0; url=' . HOME_URI . '/noticias/adm/">'; echo '<script type="text/javascript">window.location.href = "' . HOME_URI . '/noticias/adm/";</script>'; } // apaga_noticia /** * Envia a imagem * * @since 0.1 * @access public */ public function upload_imagem() { // Verifica se o arquivo da imagem existe if ( empty( $_FILES['noticia_imagem'] ) ) { return; } // Configura os dados da imagem $imagem = $_FILES['noticia_imagem']; // Nome e extensão $nome_imagem = strtolower( $imagem['name'] ); $ext_imagem = explode( '.', $nome_imagem ); $ext_imagem = end( $ext_imagem ); $nome_imagem = preg_replace( '/[^a-zA-Z0-9]/', '', $nome_imagem); $nome_imagem .= '_' . mt_rand() . '.' . $ext_imagem; // Tipo, nome temporário, erro e tamanho $tipo_imagem = $imagem['type']; $tmp_imagem = $imagem['tmp_name']; $erro_imagem = $imagem['error']; $tamanho_imagem = $imagem['size']; // Os mime types permitidos $permitir_tipos = array( 'image/bmp', 'image/x-windows-bmp', 'image/gif', 'image/jpeg', 'image/pjpeg', 'image/png', ); // Verifica se o mimetype enviado é permitido if ( ! in_array( $tipo_imagem, $permitir_tipos ) ) { // Retorna uma mensagem $this->form_msg = '<p class="error">Você deve enviar uma imagem.</p>'; return; } // Tenta mover o arquivo enviado if ( ! move_uploaded_file( $tmp_imagem, UP_ABSPATH . '/' . $nome_imagem ) ) { // Retorna uma mensagem $this->form_msg = '<p class="error">Erro ao enviar imagem.</p>'; return; } // Retorna o nome da imagem return $nome_imagem; } // upload_imagem /** * Paginação * * @since 0.1 * @access public */ public function paginacao () { /* Verifica se o primeiro parâmetro não é um número. Se for é um single e não precisa de paginação. */ if ( is_numeric( chk_array( $this->parametros, 0) ) ) { return; } // Obtém o número total de notícias da base de dados $query = $this->db->query( 'SELECT COUNT(*) as total FROM noticias ' ); $total = $query->fetch(); $total = $total['total']; // Configura o caminho para a paginação $caminho_noticias = HOME_URI . '/noticias/index/page/'; // Itens por página $posts_per_page = $this->posts_por_pagina; // Obtém a última página possível $last = ceil($total/$posts_per_page); // Configura a primeira página $first = 1; // Configura os offsets $offset1 = 3; $offset2 = 6; // Página atual $current = $this->parametros[1] ? $this->parametros[1] : 1; // Exibe a primeira página e reticências no início if ( $current > 4 ) { echo "<a href='$caminho_noticias$first'>$first</a> ... "; } // O primeiro loop toma conta da parte esquerda dos números for ( $i = ( $current - $offset1 ); $i < $current; $i++ ) { if ( $i > 0 ) { echo "<a href='$caminho_noticias$i'>$i</a>"; // Diminiu o offset do segundo loop $offset2--; } } // O segundo loop toma conta da parte direita dos números // Obs.: A primeira expressão realmente não é necessária for ( ; $i < $current + $offset2; $i++ ) { if ( $i <= $last ) { echo "<a href='$caminho_noticias$i'>$i</a>"; } } // Exibe reticências e a última página no final if ( $current <= ( $last - $offset1 ) ) { echo " ... <a href='$caminho_noticias$last'>$last</a>"; } } // paginacao } // NoticiasAdmModel
Este é um model bem grande, mas ele fará tudo o que precisamos.
Seus métodos fazem o seguinte:
- listar_noticias() – Retorna um array com todas as notícias;
- obtem_noticia() – Obtém uma única notícia. Também atualiza os dados caso algo seja enviado;
- insere_noticia() – Insere uma notícia na base de dados;
- apaga_noticia() – Apaga uma notícia da base de dados;
- upload_imagem() – Faz o upload de imagens se alguma for enviada;
- paginacao() – Cria uma paginação para apresentar as notícias na página inicial de notícias;
Os métodos estão bem comentados, portanto, leia os comentários e se ficar com dúvidas, basta questionar.
Agora vamos criar os views.
Criando um view para mostrar notícias
Agora vamos criar um dos views que precisamos, o para a área administrativa.
Lembre-se: Views ficam dentro da pasta views separados por pastas. Nosso view estará dentro da pasta noticias com o nome de noticias-adm-view.php e noticias-view.php.
Veja seu o código do arquivo noticias-adm-view.php.
/views/noticias/noticias-adm-view.php
<?php // Evita acesso direto a este arquivo if ( ! defined('ABSPATH')) exit; // Configura as URLs $adm_uri = HOME_URI . '/noticias/adm/'; $edit_uri = $adm_uri . 'edit/'; $delete_uri = $adm_uri . 'del/'; // Carrega o método para obter uma notícia $modelo->obtem_noticia(); // Carrega o método para inserir uma notícia $modelo->insere_noticia(); // Carrega o método para apagar a notícia $modelo->form_confirma = $modelo->apaga_noticia(); // Remove o limite de valores da lista de notícias $modelo->sem_limite = true; ?> <div class="wrap"> <?php // Mensagem de configuração caso o usuário tente apagar algo echo $modelo->form_confirma; ?> <!-- Formulário de edição das notícias --> <form method="post" action="" enctype="multipart/form-data"> <table class="form-table"> <tr> <td> Título: <br> <input type="text" name="noticia_titulo" value="<?php echo htmlentities( chk_array( $modelo->form_data, 'noticia_titulo') ); ?>" /> </td> </tr> <tr> <td> Imagem: <br> <input type="file" name="noticia_imagem" value="" /> </td> </tr> <tr> <td> Data: <br> <input type="text" name="noticia_data" value="<?php $data = chk_array( $modelo->form_data, 'noticia_data'); if ( $data && $data != '0000-00-00 00:00:00' ) echo date('d-m-Y H:i:s', strtotime( $data ) ); ?>" /> </td> </tr> <tr> <td> Autor: <br> <input type="text" name="noticia_autor" value="<?php echo htmlentities( $_SESSION['userdata']['user_name'] ); ?>" /> </td> </tr> <tr> <td> Texto da notícia: <br> <textarea name="noticia_texto"><?php echo htmlentities( chk_array( $modelo->form_data, 'noticia_texto') ); ?></textarea> </td> </tr> <tr> <td colspan="2"> <?php // Mensagem de feedback para o usuário echo $modelo->form_msg; ?> <input type="submit" value="Save" /> </td> </tr> </table> <input type="hidden" name="insere_noticia" value="1" /> </form> <!-- LISTA AS NOTICIAS --> <?php $lista = $modelo->listar_noticias(); ?> <table class="list-table"> <?php foreach( $lista as $noticia ):?> <tr> <td><?php echo $noticia['noticia_titulo']?></td> <td> <a href="<?php echo $edit_uri . $noticia['noticia_id']?>"> Editar </a> <a href="<?php echo $delete_uri . $noticia['noticia_id']?>"> Apagar </a> </td> </tr> <?php endforeach; ?> </table> </div> <!-- .wrap -->
Este view faz todo o sistema de CRUD das notícias utilizando as ações do nosso model.
/views/noticias/noticias-view.php
Veja agora como apresentaremos os dados para o usuário final (este é o view da ação index):
<?php // Evita acesso direto a este arquivo if ( ! defined('ABSPATH')) exit; ?> <div class="wrap"> <?php // Número de posts por página $modelo->posts_por_pagina = 10; // Lista notícias $lista = $modelo->listar_noticias(); ?> <?php foreach( $lista as $noticia ):?> <!-- Título --> <h1> <a href="<?php echo HOME_URI?>/noticias/index/<?php echo $noticia['noticia_id']?>"> <?php echo $noticia['noticia_titulo']?> </a> </h1> <?php // Verifica se estamos visualizando uma única notícia if ( is_numeric( chk_array( $modelo->parametros, 0 ) ) ): // single ?> <p> <?php echo $modelo->inverte_data( $noticia['noticia_data'] );?> | <?php echo $noticia['noticia_autor'];?> </p> <p> <img src="<?php echo HOME_URI . '/views/_uploads/' . $noticia['noticia_imagem']; ?>"> </p> <?php echo $noticia['noticia_texto'];?> <?php endif; // single ?> <?php endforeach; ?> <?php $modelo->paginacao();?> </div> <!-- .wrap -->
Isso deverá gerar uma lista com as notícias, links para a página de uma notícia única e uma paginação em baixo.
Dentro da notícia outros campos serão apresentados, como imagem, autor, data e texto.
Veja um exemplo:
Lembre-se, isto é apenas um exemplo da funcionalidade do sistema, você deve criar seus próprios modelos, controles e views.
Download e outras partes do artigo
Segue o link para download:
- Download crud-1-0.zip
Caso queira contribuir com o projeto, acesso no Github:
Se tiver perdido alguma parte desse tutorial, deixo os links para facilitar sua vida:
- Utilizando estrutura MVC em PHP – Parte 1
- Utilizando estrutura MVC em PHP – Parte 2
- Utilizando estrutura MVC em PHP – Parte 3
- Utilizando estrutura MVC em PHP – Parte 4
- Utilizando estrutura MVC em PHP – Parte 5 (este artigo)
Em caso de dúvidas, questione!