MVC na Linguagem PHP - II
A pedido de um leitor, decidimos escrever um artigo em que exemplificamos este maravilhoso (ou não) modelo MVC utilizando a linguagem de programação PHP. Depois de publicarmos a primeira parte, onde explicámos a estrutura da pequena framework que iremos criar, aqui está a segunda parte. Hoje iremos trabalhar no coração da aplicação, ou seja, na diretoria appcore/libs. Nesta pasta mãe, irão ser colocados todos os ficheiros PHP que serão as classes mãe de todos os outros ficheiros. Bootstrap.php Vamos começar por criar um ficheiro denominado Bootstrap.php onde iremos colocar todo o seguinte código: getUrl(); if (empty($this->url[0])) { $this->url[0] = 'index'; } $this->controller(); $this->method(); return false; } /* This function get the content of 'url' variable of HTTP GET method. See the .htaccess for more information. / private function getUrl() { $url = isset($GET['url']) ? $GET['url'] : null; $url = rtrim($url, '/'); $url = filtervar($url, FILTERSANITIZEURL); $this->url = explode('/', $url); } /* This function initializes the controller that matches with the current url. @return bool / private function controller() { $file = DIRCONTROLLERS . $this->url[0] . '.php'; if (fileexists($file)) { require $file; $controller = "Controller" . $this->url[0]; $this->controller = new $controller($this->url[0]); return false; } else { $this->error(); return false; } } / This function calls the method depending on the url fetched above. / private function method() { $length = count($this->url); if ($length > 1) { if (!methodexists($this->controller, $this->url[1])) { $this->error(); } } switch ($length) { case 5: //Controller->Method(Param1, Param2, Param3) $this->controller->{$this->url[1]}($this->url[2], $this->url[3], $this->url[4]); break; case 4: //Controller->Method(Param1, Param2) $this->controller->{$this->url[1]}($this->url[2], $this->url[3]); break; case 3: //Controller->Method(Param1, Param2) $this->controller->{$this->url[1]}($this->url[2]); break; case 2: //Controller->Method(Param1, Param2) $this->controller->{$this->url[1]}(); break; default: $this->controller->index(); break; } } /* Display an error page if there's no controller that corresponds with the current url. / private function error() { require DIRCONTROLLERS . $this->errorFile; $this->controller = new ControllerError(); $this->controller->index(); exit; } } O código acima é aquele que irá inicializar todo a aplicação. Em primeiro lugar, gostava de dizer que a estrutura do URL da aplicação será a seguinte: http://site/controlador/método/arg1/arg2/[arg...] Vamos então ver, por partes, o que faz cada uma das funções declaradas acima. init A função init é a função onde tudo começa. Em primeiro lugar, esta função chama a função getUrl que recebe o URL atual (será analisada mais à frente). De seguida, esta função define a página como index caso nenhuma página esteja definida no URL. Então a função chama duas outras que iremos ver de seguida. getUrl Esta função recebe a variável url que foi passada através do método GET (em breve veremos as modificações que têm que ser efetuadas no .htaccess para que seja passada esta variável). Aqui é utilizado o operador ternário de forma a que a variável $url seja igual a null caso não haja nenhum conteúdo na variável url que foi passada através do método GET. Depois é removida a última barra (/) da variável com a função rtrim. De seguida é aplicado um filtro à array de forma a remover todos os caracteres não permitidos aqui. Finalmente, a variável do url da classe ($url) é igualada à "explosão" da variável $url que se irá tornar num array . controller Esta função, em primeiro lugar, define o caminho do ficheiro do controlador correspondente ao URL atual. O caminho será igual à constante DIRCONTROLLERS + a primeira parte do url + a extensão do ficheiro que é .php. De seguida, é feita a verificação se o controlador em questão existe. Se existir, o controlador é inicializado, caso contrário, o fluxo da aplicação é direcionado para a função de erro (error). method Esta função é como um GPS: ela é que envia o fluxo para o sítio correto. Isto vai ser feito dependendo do que foi enviado no URL. Esta função irá executar o método em questão que, caso não seja especificado nenhum, é o método index do controlador em questão. error Finalmente, temos a função de erro que irá inicializar o controlador dos erros. O ficheiro que corresponde a este controlador é definido na variável $errorFile, que eu coloquei error.php. Controller.php De momento, o que foi feito até agora pode aparentar não ter muito sentido, mas com o encaixar das peças tudo irá ser mais claro. Vejamos agora a classe mãe dos Controladores. view = new View(); $path = ROOT . 'models/' . $name . '.php'; if (fileexists($path)) { require $path; $modelName = "Model" . $name; $this->model = new $modelName(); } } } Esta classe é claramente menos complexa que a anterior e conta apenas com o seu construtor que recebe o nome do controlador e inicializa, automaticamente, a View. De seguida, o construtor constrói o caminho até ao modelo do controlador em questão, que caso o controlador se chame "about" , o caminho para o modelo seria ROOT . 'models/about.php'. Depois é feita a verificação se existe o ficheiro do modelo e, caso este exista, é inicializado o modelo do controlador. Mas, porque é que esta verificação é feita? Porque nem todas as páginas utilizarão a base de dados. Páginas estáticas como, por exemplo, a página sobre, não necessitam, geralmente, de manipulação de dados. Database.php A classe Database vai estar intimamente ligada com a classe PDO e será com ela que inicializaremos a ligação à base de dados e não com a PDO, pois a Database é baseada na PDO. Ora veja: exec("SET NAMES 'utf8';"); } /* Function used to select something of the database. @param string $sql An SQL string @param array $array Parameters to bind @param const|int $fetchMode A PDO Fetch mode @return mixed / public function select($sql, $array = array(), $fetchMode = PDO::FETCHASSOC) { $sth = $this->prepare($sql); foreach ($array as $key => $value) { $sth->bindValue("$key", $value); } $sth->execute(); return $sth->fetchAll($fetchMode); } / Function used to insert things in the database. @param string $table A name of table to insert into @param string $data An associative array / public function insert($table, $data) { ksort($data); $fieldNames = implode(', ', arraykeys($data)); $fieldValues = ':' . implode(', :', arraykeys($data)); $sth = $this->prepare("INSERT INTO $table ($fieldNames) VALUES ($fieldValues)"); foreach ($data as $key => $value) { $sth->bindValue(":$key", $value); } $sth->execute(); } / Function used to update things on the database. @param string $table A name of table to insert into @param string $data An associative array @param string $where the WHERE query part / public function update($table, $data, $where) { ksort($data); $fieldDetails = NULL; foreach ($data as $key => $value) { $fieldDetails .= "$key=:$key,"; } $fieldDetails = rtrim($fieldDetails, ','); $sth = $this->prepare("UPDATE $table SET $fieldDetails WHERE $where"); foreach ($data as $key => $value) { $sth->bindValue(":$key", $value); } $sth->execute(); } / Function used to delete things from the database. @param string $table @param string $where @param integer $limit @return integer Affected Rows / public function delete($table, $where, $limit = 1) { return $this->exec("DELETE FROM $table WHERE $where LIMIT $limit"); } } Não há muito a falar sobre esta classe visto que ela contém apenas algumas funções para agilizar diversas operações como inserções na base de dados, seleções, atualizações e eliminações. Acrescento que o construtor deve receber todos aqueles itens que escrevemos nas constantes da configuração: todos os dados relativos à conexão à Base de Dados. Model.php Vejamos então a classe superior de todos os modelos que irão constituir a nossa aplicação: db = new Database(DBTYPE, DBHOST, DBNAME, DBUSER, DBPASS); } } Como pode ver, a classe é pequena e o seu construtor apenas inicializa uma instância da conexão à Base de Dados que será utilizada em todos os modelos. O modelo da View que já foi muito falado acima também é muito pequeno. data = $data; } } Neste é inicializada uma variável chamada $data que irá conter todo o conteúdo que deverá ser enviado para o HTML de forma a ser imprimido. Temos também a função render que é aquela que vai incluir (ou requerer) todos os ficheiros para serem apresentados. Dividi os ficheiros em três partes: o cabeçalho (header), o principal e o rodapé (footer_). Temos, por fim, a função setData que será utilizada para definir a variável da vista. Utilizei uma função para não haver manipulação direta das variáveis da classe. > Provavelmente reparou que coloquei vários comentário em Inglês. No final desta saga de artigos vou colocar esta framework* simples no GitHub de forma a que todos os que queiram possam contribuir ou até mesmo utilizar. :) Até ao próximo artigo. O principal já está feito. Faltam os dois ficheiros que iniciarão tudo (e mais algumas coisinhas).
Discussion in the ATmosphere