A software architectural pattern
WTF?
Começando do começo, o MVC é um padrão de arquitetura de software. Ele define com o software será estruturado, não somente quanto à organização de pastas, e arquivos, mas também como cada parte deve agir e que função desempenha cada componente do software.
O padrão MVC atende perfeitamente às necessidades da POO, pois provê, de forma simples, a comunicação entre camadas.
Sim, mas MVC...
MVC significa MODEL, VIEW, CONTROLLER (modelo, visão, controlador). Cada palavra dessa representa uma camada do seu software, e cada camada executa uma função específica dentro da aplicação.
VIEW
A view é a camada de apresentação (exibição). As views serão responsáveis por mostrar conteúdo ao usuário, e é com elas que ele irá interagir. Então, no nosso caso, já que o que o usuário vai ver são as nossas páginas HTML, elas serão as nossas views.
CONTROLLER
A controller é a camada de negócios; as regras de negócio são definidas principalmente nos controladores e nos relacionamentos entre as tabelas do banco de dados, assim como as validações de formulários, logins, redirecionamentos, etc. As controllers, ora essa, controlam a aplicação, e costumamos dizer que elas funcionam como uma ponte entre a view e a model.
MODEL
A model é a camada que vai interagir com o Banco de Dados. É nela que vamos aplicar o conhecimento que obtivemos no guia de CRUD. Na model a gente vai lançar as inserções, requisições, atualizações, deleções, desde as declarações mais básicas até as declarações mais complexas, usando PHP & MySQL.
Você lembra da definição de modelo que demos lá no guia de POO? Pois é. Os nossos modelos serão naquele esquema.
Então...
Para facilitar o desenvolvimento e a manutenção dos nossos projetos, vamos segmentar nosso MVC em pastas, como segue:
.
├── assets
│...├── css
│...├── img
│...└── js
├── controllers
├── helpers
├── models
├── vendor
└── views
PERAÊ... Tem pasta demais aí.
É que aí tem a estrutura de pastas de todo um projeto, e não utilizamos somente o que o MVC pede. Cada pasta extra tem uma função complementar à estrutura do MVC.
- assets: Toda a estilização do projeto (css e js) e os arquivos de imagem.
- helpers: São arquivos que auxiliam as controllers em funcionalidades mais gerais do que específicas, como: tradutores
- vendor: Todo e qualquer código externo (que não foi desenvolvido pela equipe do projeto).
Mãos à obra!
Vamos construir aqui uma model, uma controller e uma view para inserir usuários na tabela users num banco de dados. Preste atenção em tudo. Explicaremos a maioria das coisas, mas qualquer dúvida, não se acanhe em perguntar a seu mentor ou outro Infobrother.
Model -> models/user.php
1 <?php
2 class User extends Connection {
3 ....private $id;
4 ....private $name;
5 ....private $email;
6 ....private $password;
7
8 ....function __construct($attributes) {
9 ........$this->id = (empty($attributes['id']))? null: $attributes['id'];
10........$this->name = (empty($attributes['name']))? null: $attributes['name'];
11........$this->email = (empty($attributes['email']))? null: $attributes['email'];
12........$this->password = (empty($attributes['password']))? null: $attributes['password'];
13....}
14
15....public function insert() {
16........$connect = self::start();
17........$stm = $connect->prepare("INSERT INTO users(name, email, password) VALUES (:name, :email, :password)");
18........$stm->BindValue(":name", $this->name, PDO::PARAM_STR);
19........$stm->BindValue(":email", $this->email, PDO::PARAM_STR);
20........$stm->BindValue(":password", $this->password, PDO::PARAM_STR);
21........return $stm->execute();
22....}
23}
24?>
PARÔ, 2 ALTOS! Quié isso?
Lembre aí dos Guias de POO e de MySQL. Lembrou? Prooonto.
Então... A Model é a construção da nossa entidade, e é a partir dela que vamos criar nossos objetos.
O que vemos nas linhas de 3 à 6 são os atributos da nossa classe, que receberão exatamente os mesmos nomes das colunas da tabela correspondente no Banco de Dados.
Da linha 8 à 13 temos o nosso construtor. E perceba que entre os parenteses do construtor, temos uma variável que, nessa situação, chamaremos de parâmetro. Isso significa que o construtor está esperando que você envie informações pra ele. Informações essas, que deverão vir na forma de um array associativo, onde cada posição do array se refere a um dos nossos atributos.
No "recheio" do construtor temos 4 linhas muito semelhantes. Deixa eu explicar o que elas fazem. Vou pegar como exemplo a linha 9.
9 ........$this->id = (empty($attributes['id']))? null: $attributes['id'];
O primeiro lance é esse $this
aí. O $this é uma pseudo-variável que está disponível quando um método é chamado a partir de um contexto de objeto. $this é uma referência ao objeto criado. Enfim, quando você instancia, cria, produz, um novo objeto, você tem acesso ao $this.
E daí que nessa linha a gente usa $this->id
, ou seja, o id
de um objeto daquela classe. E aí com o '=
' você diz que aquele id
vai ser alguma coisa.
MAS vai ser o quê? Vai ser o resultado dessa novidade da tecnologia mundial que vem em seguida, que é o nosso operador ternário.
O operador ternário é a estrutura abaixo, que funciona como se fosse um if {} else {}
.
(Minha_condição_é_verdade)? Se_for_Execute_isso: Se_não_for_Execute_isso;
No nosso caso, a pergunta é: (empty($attributes['id'))?
empty é uma função do próprio PHP que verifica se uma variável ou posição de array está VAZIA, NULA, ou SE NÃO ESTÁ SETADA.
Então ela tá verificando se a posição id
do nosso array $attributes
não existe ou se ela está vazia. Se isso for verdade, o id
do nosso objeto vai ser null
. Se isso não for verdade, o id
do nosso objeto vai ser a posição id
do nosso array $attributes
. E aí, o restante é análogo.
A função insert()
Essa é a função que será responsável por inserir linhas na nossa tabela de usuários. Aqui eu quero que você lembre que cada objeto é ou será uma linha da nossa tabela de usuários, e que a model é a camada mais próxima do banco.
Na linha 16 a gente recebe na variável $connect
um objeto PDO que é gerado ao fazermos a conexão com o banco, mas calma aí que depois eu te explico.
Na linha 17, utilizamos o método prepare
da classe PDO
. O método prepare
é utilizado com a ideia de apenas iniciar a query e aguardar pela inclusão de valores posteriormente.
No nosso caso, a query é um comando MySQL de inserção que, em português, está dizendo: "Insira nesta tabela, em cada coluna especificada, estas informações aqui." Se o servidor de banco de dados preparar o comunicado com êxito, o prepare
retorna um objeto PDOStatement
que é armazenado em $stm
. Se o servidor de banco de dados não pôde se preparar com êxito, a declaração retorna FALSE
.
O lance é que as informações que de fato serão inseridas ainda não estão associadas aos parâmetros passados em VALUE()
. Até aqui você só disse onde você quer inserir (Na tabela tal, coluna tal), mas ainda não disse o quê.
Após o prepare, temos acesso ao método BindValue
da classe PDOStatement
. A função dele é vincular um valor a um parâmetro. No nosso caso, temos que o parâmetro ":name"
passa a ter o valor atual do nosso atributo $name
(O $this->name
do objeto atual). O PDO::PARAM_
define o tipo de dado que está sendo passado, confira TODOS.
Em seguida, temos mais um método do PDOStatement, o execute
, que como o nome indica, executa um declaração preparada.
OBS.:
Caso você esteja fazendo uma função de seleção, você deve retornar o resultado do método fetch
do PDOStatement
. O método fetch
retorna uma linha de um conjunto de resultados associado a um objeto PDOStatement
. O PDO::FETCH_
determina a forma como o fetch vai retornar o resultado, confira TODAS. Converse com seu mentor sobre isso.
Conectando com o banco de dados
Bom, como já dito, na linha 16 a gente recebe na variável $connect
um objeto PDO que é gerado após ser feita a conexão com o banco. Mas o que é esse tal de PDO?
O PDO (PHP Data Objects) é uma classe nativa da linguagem PHP. Ela define uma interface leve e consistente para acessar bancos de dados em PHP.
O PDO fornece uma camada de abstração de acesso a dados, o que significa que, independentemente de qual banco de dados você está usando, você usa as mesmas funções para emitir consultas e buscar dados. O PDO não fornece uma abstração de banco de dados; não reescreve SQL ou emula características ausentes.
A conexões com o banco serão estabelecidas através da criação de instâncias da classe base PDO. O construtor da PDO aceita parâmetros para especificar a fonte de banco de dados (conhecido como o DSN) e, opcionalmente, para o nome de usuário e senha (se houver). Como segue:
1 $connect = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
Essa é a sintaxe, mas o ideal é que façamos em uma classe com uma função, para que possamos utilizar recursos de POO para facilitar nosso trabalho.
1 <?php
2 class Connection {
3 ....protected static function start() {
4 ........$pdo = new PDO('mysql:host=localhost;dbname=test', "root", "");
5 ........$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
5 ........return $pdo;
6 ....}
6 }
6 ?>
A partir daí, a conexão com o banco estará feita, e todas as nossas models passarão a herdar esta classe para que as nossas funções possam estabelecer conexão com o banco de dados. E esse estabelecer da conexão é o que vemos na linha 16 da nossa model:
16........$connect = self::start();
Sim, o self é uma palavra reservada do PHP, que permite fazer referência a uma função que pertence à classe atual.
Controller -> controllers/userController.php
1 <?php
2 require_once('../models/user.php');
3
4 class UserController {
5 ....public static function create() {
6 ........if (!empty($_POST['name']) && !empty($_POST['personalEmail']) && !empty($_POST['password'])) {
7 ............$user = new User($_POST);
8 ............try {
9 ................$user->insert();
10................$_SESSION['msg'] = '">Usuário criado com sucesso!';
11............}
12............catch(pdoexception $e) {
13................$_SESSION['msg'] = 'fail">Erro.';
14............}
15........}
16........header("location: ../views/home.php");
17....}
18}
19
20$postActions = array('create', 'update', 'changeStatus');
21$getActions = array('delete');
22
23if(isset($_POST['action']) && in_array($_POST['action'], $postActions)) {
24....UserController::$_POST['action']();
25}
26elseif((key($_GET))!==null && in_array(key($_GET), $getActions)) {
27....$command = key($_GET);
28....UserController::$command();
29} else {
30....header('Location: ../');
31}
Essa é a nossa Controller. Ela é a ponte que interliga o nosso sistema.
Perceba que na controller não temos um construtor, e daí que todas as nossas funções serão funções de classe (funções estáticas). Ou seja, não trabalharemos com objetos ou instâncias da classe da controller.
Pois bem, vamos falar na nossa função create()
.
Pra início de conversa, declaramos na linha 5 a função como static, ou seja, fizemos dela uma função de classe. A linha 6 é uma verificação importantíssima, que impede que todos os campos que queríamos que fossem preenchidos no nosso formulário de criação de usuários tenham algum conteúdo. Você lembra do método empty()
que vimos na explicação da model, né? Pois é, se empty()
verifica se um campo está vazio, !empty()
verifica se um campo NÃO ESTÁ VAZIO. A exclamação é uma negação.
Na linha 7, criamos uma nova instância da classe User da model. Isso só foi possível porque na linha 2, fizemos a inclusão do arquivo da model no nosso arquivo da controller com a função require_once do PHP. Ao criar a nova instância, passamos como parâmetro a variável superglobal $_POST
, que nada mais é do que um array associativo com todas as informações que vieram do nosso formulário via POST. É assim que o construtor da nossa model vai ter o que colocar em seus atributos. ;)
Das linhas 8 à 14, temos o bloco try {} catch() {}
. Que, em palavras resumidas, vai tentar (try) executar algo, e em caso de falha, vai pegar (catch) a falha (Exceção), que poderá ser exibida ou não.
Na linha 9, executamos a função insert()
da nossa model User. Como ela está dentro do bloco try
, o que estamos fazendo é tentando inserir algo no banco de dados. Se der certo, criamos e armazenamos uma mensagem de sucesso, e beleza. O lance é que se não der certo (linhas 12 - 14), capturamos o problema que deu, e criamos e armazenamos uma mensagem de erro, evitando que a aplicação quebre na face do usuário.
Na linha 16, utilizamos a função header
do PHP para redirecionar para uma determinada página após executar a função insert.
Converse com seu mentor, ou tente entender como funciona o código da linha 20 em diante, sobre para que serve a $_SESSION, como utilizá-la, e como fazer com que as mensagens de sucesso e falha sejam exibidas na tela. Ah, e procure saber a diferença entre exceção e erro.
Fica a seu cargo desenvolver as funções para o restante do CRUD, além do formulário.