Construa uma Base de Conhecimento de Suporte usando DB2 pureXML e PHP

Crie um aplicativo que use dados relacionais e XML com o IBM DB2 Express-C

É fácil criar aplicativos que usam um híbrido de dados relacionais e XML, graças ao recurso pureXML® dos servidores de bancos de dados IBM® DB2®. Neste tutorial, você usará PHP para criar um aplicativo da Web que se conecta a um banco de dados IBM DB2 Express-C e armazena parte dos seus dados em colunas de bancos de dados relacionais tradicionais, e outra parte em colunas XML nativas. Você também aprenderá a usar consultas SQL/XML para recuperar, inserir, atualizar e excluir dados desse banco de dados. Além do treinamento prático baseado em projetos, este tutorial fornece as habilidades e conhecimentos conceituais que você precisa para desenvolver seus próprios aplicativos híbridos.

Joe Lennon, Software developer, Core International

Joe Lennon photoJoe Lennon, 24 anos, é desenvolvedor de software em Cork, Irlanda. Autor do livro Beginning CouchDB da Apress (ainda não publicado), tem colaborado com o IBM developerWorks com diversos artigos técnicos e tutoriais. Em seu tempo livre, Joe gosta de jogar futebol, mexer em pequenos mecanismos e bater seus recordes em seu Xbox 360.



11/Dez/2009

Antes de começar

Este tutorial é direcionado aos desenvolvedores de aplicativos da Web que queiram desenvolver aplicativos suportados por um banco de dados IBM DB2. É necessário ter familiaridade com os fundamentos dos códigos HTML, CSS e PHP para seguir este tutorial. É também necessário possuir alguma experiência com sistemas de gerenciamento de bancos de dados e com a linguagem SQL.

Sobre este tutorial

Uma das tecnologias mais úteis e versáteis disponíveis no IBM DB2 é o suporte nativo para dados XML através do recurso pureXML. O pureXML possibilita o armazenamento, a recuperação e a manipulação de dados XML ao lado de dados relacionais e da mesma maneira que eles. Isso possibilita o desenvolvimento de aplicativos que tirem proveito das vantagens tanto dos bancos de dados relacionais quanto dos dados XML. Tais aplicativos podem ser particularmente úteis quando há uma grande quantidade de dados XML que você precisa usar sem ter que primeiro convertê-los em uma estrutura relacional.

Este tutorial fornece instruções passo a passo sobre o uso do PHP para criar um sistema de Base de Conhecimento de Suporte, que armazena seus dados usando uma combinação de colunas relacionais DB2 tradicionais e colunas pureXML. O aplicativo utiliza a potência do SQL/XML para mapear os dados XML como uma coluna relacional. Isso possibilita que você use o PHP para recuperar dados como se eles estivessem armazenados de maneira relacional.

As instruções do tutorial foram escritas sob o pressuposto de que você está criando o aplicativo em um servidor de desenvolvimento local com o Windows® XP e o DB2 Express-C, o Apache HTTP Server e o PHP instalados. Deve ser possível adaptar as instruções para outros sistemas e configurações, mas elas não foram testadas.

Layout do aplicativo

A estrutura de diretórios do aplicativo é relativamente simples. O aplicativo será armazenado em um subdiretório chamado kbase no diretório htdocs da sua instalação do Apache. Em sistemas Windows, o diretório é geralmente encontrado no caminho C:\Program Files\Apache Software Foundation\Apache 2.2\htdocs. Abaixo do diretório kbase haverá quatro subdiretórios:

  • classes — contém as classes PHP que são usadas para obter dados do banco de dados e levá-los até o aplicativo.
  • css — inclui o arquivo de folha de estilo CSS que define a aparência da interface com o usuário do aplicativo da Web.
  • includes — contém arquivos com o código para o cabeçalho do aplicativo, a barra lateral de navegação e o rodapé. Os scripts PHP incluem esses arquivos no início e no fim de cada página para que não seja necessário copiar o mesmo código no arquivo fonte de cada página.
  • sql — contém o script SQL do banco de dados que cria o banco de dados DB2 e suas tabelas. Esse script será usado na próxima seção deste tutorial.

O aplicativo contém uma série de páginas da Web que podem ser categorizadas da seguinte maneira:

  • Administration view — contém páginas que possibilitam a criação e a administração de categorias, artigos e comentários.
  • Client (end user) view — contém a home page do aplicativo, a página de resultados de pesquisas e as páginas para a visualização de uma categoria e os detalhes de um artigo.

Pré-requisitos

Para seguir as etapas deste tutorial, é necessário possuir o seguinte software instalado:

Um guia completo para a instalação e a configuração do software acima está disponível na série de artigos do developerWorks, "Leveraging pureXML in a Flex microblogging application" (veja Recursos para obter o link). A parte 1 dessa série demonstra como instalar o DB2 Express-C. A parte 3 tem instruções detalhadas sobre a instalação do Apache HTTP Server e PHP.


Criando um banco de dados DB2

Nesta seção, você criará um banco de dados para o aplicativo da Base de Conhecimento. É necessário ter o IBM DB2 Express-C instalado e possuir acesso à ferramenta DB2 Command Editor. O banco de dados para o aplicativo contém quatro tabelas, cada uma incluindo uma combinação de colunas comuns e colunas XML.

Crie um banco de dados com SQL e o DB2 Command Editor

A primeira etapa na criação do aplicativo é criar um novo banco de dados IBM DB2. A partir do Windows, inicie o DB2 Command Editor (Figura 1) da seguinte maneira:

Iniciar > Programas > IBM DB2 > DB2COPY1 (Default) > Command Line Tools > Command Editor

Figura 1. DB2 Command Editor
Screenshot of the DB2 Command Editor with nothing entered.

A interface principal do Command Editor é dividida em duas seções. A seção superior é a área do editor. É onde são inseridos os comandos que você deseja enviar ao banco de dados. A seção inferior é a área dos resultados. Essa área exibe as mensagens de resposta do banco de dados após a execução das instruções na área do editor.

Copie e cole o código da Listagem 1 na área do editor do seu DB2 Command Editor. Execute o código através de um clique na seta verde no canto esquerdo da barra de ferramentas na área do editor. Espere pacientemente, pois pode demorar mais ou menos um minuto para o código ser concluído.

Listagem 1. database.sql
CREATE DATABASE kbase USING CODESET UTF-8 TERRITORY us;
CONNECT TO kbase;

CREATE TABLE category (
    id        int not null generated by default as identity,
    data            xml not null,
    primary key(id)
);

CREATE TABLE article (
    id        int not null generated by default as identity,
    date_created    timestamp not null,
    date_modified    timestamp not null,
    view_count        int,
    category_id        int not null,
    data            xml not null,
    primary key(id),
    foreign key(category_id) references category(id) 
        on delete cascade
);

CREATE TABLE comment (
    id        int not null generated by default as identity,
    date_left        timestamp not null,
    approved        smallint not null,
    article_id        int not null,
    data            xml not null,
    primary key(id),
    foreign key(article_id) references article(id) 
        on delete cascade
);

CREATE TABLE rating (
    id         int not null generated by default as identity,
    date_rated        timestamp not null,
    article_id        int not null,
    data            xml not null,
    primary key(id),
    foreign key(article_id) references article(id) 
        on delete cascade
);

O código cria um novo banco de dados chamado kbase com quatro tabelas: category, article, comment e rating. Cada tabela tem uma coluna de ID e uma coluna de dados. Algumas das tabelas têm também colunas Meta como date_created e chaves estrangeiras. A coluna de ID em cada tabela é usada como um identificador exclusivo para os dados nessa tabela. A coluna de dados tem um tipo de dado XML e guarda os dados principais em formato XML. Você verá mais adiante como esses dados XML são usados em cada tabela.

Depois de executar o código database.sql, você deve receber uma resposta bem-sucedida do servidor DB2, como mostrado na Figura 2.

Figura 2. Resultado da execução do database.sql
DB2 Command Editor with code from Listing 1 in the editor area and messages indicating successful execution in the results area.

Com o banco de dados criado, você está pronto para prosseguir para a criação do aplicativo da Base de Conhecimento. Na próxima seção, você criará uma classe PHP para a conexão com o banco de dados IBM DB2.


A classe PHP de conexão com o banco de dados

A extensão DB2 para PHP inclui uma série de funções que possibilitam a conexão e a interação com os dados em um banco de dados IBM DB2. Nesta seção, você criará uma classe PHP que inclui essas funções. Isso possibilita o uso de uma menor quantidade de código durante as interações com o banco de dados. Outras classes PHP podem usar essa classe de bancos de dados. É possível chamar os seus métodos de objetos para iniciar uma conexão com o banco de dados, evitar cadeias potencialmente perigosas, executar consultas do banco de dados e retornar os resultados dessas consultas.

Defina a classe DB

Nesta seção, você criará uma classe PHP chamada DB que lida com a conexão com o banco de dados DB2. É sempre uma boa ideia manter o código da conexão do banco de dados separado do resto do aplicativo. Dessa maneira, se for necessário modificar o nome do host da conexão, o nome do usuário, a senha ou até mesmo o driver do banco de dados, só é preciso fazê-lo em um único local.

Crie um arquivo chamado db.php, copie nele o código da Listagem 2, e salve o arquivo no subdiretório de classes do seu projeto.

Listagem 2. db.php
<?php
class DB {
    private $conn;

    function __construct() {
        $database = "kbase";
        $hostname = "localhost";
        $port = 50000;
        $user = "username";
        $password = "password";

        $db_connect_string = "DRIVER={IBM DB2 ODBC DRIVER};"
            . "DATABASE=$database;"
            . "HOSTNAME=$hostname;PORT=$port;PROTOCOL=TCPIP;"
            . "UID=$user;PWD=$password;";

        $this->conn = db2_connect($db_connect_string, '', '');

        if(!$this->conn) {
            die(db2_conn_errormsg($this->conn));
        }
    }

    function safe_no_html($string, $include_quotes=true) {
        htmlentities($string, ENT_QUOTES, 'utf-8');

        if($include_quotes) 
return "'".db2_escape_string($string)."'";
        else return db2_escape_string($string);
    }

    function query($sql) {
        $result = db2_exec($this->conn, $sql);

        if(!$result) {
            die(db2_stmt_errormsg());
        } else {
            return $result;
        }
    }

    function get_row($result) {
        return db2_fetch_array($result);
    }
}
?>

Essa classe contém uma única variável de membro privada, $conn, que é usada para armazenar a conexão com o banco de dados. O construtor de classes (a função __construct) contém as configurações do banco de dados. Dentro do construtor de classes, substitua os valores das variáveis $user e $password pelos valores corretos da sua instalação do DB2. Essas configurações são usadas para formar a cadeia de conexão do banco de dados, que por sua vez é usada para fazer a conexão com o banco de dados usando a função db2_connect.

A classe DB também inclui três funções que podem ser usadas para trabalhar com os dados no banco de dados:

  • safe_no_html— aceita a cadeia e retorna o valor que é seguro para inserir no banco de dados. Ela usa a função db2_escape_string para evitar quaisquer caracteres que possam causar erros no banco de dados, e limpa qualquer código HTML ou JavaScript potencialmente danoso usando a função htmlentities. O segundo argumento opcional, $include_quotes, pode ser definido como false se não for necessário que o valor de retorno inclua aspas simples em volta do resultado.
  • query— recebe um único argumento, $sql, que deve incluir uma instrução SQL a ser executada no banco de dados. Ela usa a função db2_exec para executar uma instrução no DB2, e se for bem-sucedida retorna um recurso $result. Se um erro ocorre, o aplicativo para de processar, e a mensagem de erro DB2 é emitida ao usuário.

    Nota: A emissão de mensagens do banco de dados ao usuário é muito conveniente durante o desenvolvimento de um aplicativo, mas nunca deve ser usada em um ambiente de produção, pois pode revelar detalhes importantes que podem ser usados em ataques. Além disso, quando estiver trabalhando em um ambiente de produção e executando instruções via PHP, você deve usar instruções preparadas ao invés de cadeias SQL inalteradas.

  • get_row— recebe um recurso $result como argumento e retorna a próxima linha de resultados usando a função db2_fetch_array.

Na próxima seção, você criará duas classes de aplicativos que usam a classe DB para a comunicação com o servidor DB2.


Classes de aplicativos.

O aplicativo da Base de Conhecimento possui dois componentes primários: categorias e artigos. Cada artigo pertencerá a uma, e somente uma, categoria. Além disso, cada artigo pode possuir de zero a vários comentários e de zero a várias classificações associadas a ele. Nesta seção, você criará duas classes PHP (Category e Article) que incorporam todas as funções que podem ser executadas nesses componentes.

Crie a classe Category

A primeira classe a ser criada é a classe Category. Essa classe contém as variáveis e métodos de instância para criar objetos de Category. Cada categoria possui uma variável $id, que é armazenada em uma coluna int no banco de dados, e uma variável name, que é armazenada em uma coluna XML chamada data. Para cada linha na tabela category no banco de dados, haverá um documento XML armazenado na coluna data, com o formato mostrado na Listagem 3.

Listagem 3. XML para linha na tabela category
<category> <name>Category Name</name> </category>

Nota: Você deve estar se perguntando por que as tags externas <category> são necessárias aqui. Elas não são realmente necessárias nesse caso, mas se você quisesse adicionar mais campos de dados no futuro, seria necessário incluí-los entre as tags <category>, pois o DB2 pureXML exige que haja um único nó externo em suas colunas XML.

A classe Category tem várias funções. Ela tem os métodos getter e setter para trabalhar com propriedades definidas na classe, uma função save para salvar categorias novas e já existentes no banco de dados, uma função delete para excluir categorias, e mais funções para recuperar informações sobre uma ou mais categorias.

A Listagem 4 contém um extrato do arquivo category.php, que define a classe Category. Para obter a definição completa da classe, faça o download do código fonte na seção de Downloads. Salve seu arquivo category.php no subdiretório de classes do seu projeto.

Listagem 4. Extrato do arquivo category.php
...
    function save() {
        $sql = "";
        if($this->id) 
            $sql = "UPDATE category SET data = (xmlparse(document
 '<category><name>".$this->db->safe_no_html($this->name, 
false)."</name></category>')) WHERE id = $this->id";
else $sql = "INSERT INTO category(data) VALUES((xmlparse(document 
'<category><name>".$this->db->safe_no_html($this->name, 
false)."</name></category>')))";

        $result = $this->db->query($sql);
        if($result) return true;
        else return false;
    }

    function delete() {
        $sql = "DELETE FROM category WHERE id = $this->id";
        $result = $this->db->query($sql);
        if($result) return true;
        else return false;
    }

    function getAllCategories() {
        $sql = 'SELECT c.id, x.name FROM category c, '
            .' XMLTABLE(\'$d/category\' PASSING c.data AS "d" '
            .' COLUMNS name VARCHAR(200) PATH \'name\') AS x '
            .' ORDER BY x.name';
        $result = $this->db->query($sql);
        $rows = array();
        if($result) {
            while($row = $this->db->get_row($result)) {
                $rows[] = array($row[0], $row[1]);
            }
        }
        return $rows;
    }
...

A classe possui três variáveis. A variável $db é usada para instanciar a conexão com o banco de dados. A conexão do banco de dados é usada pela classe toda vez que ela precisa recuperar, salvar ou excluir dados. A variável $db é chamada como um novo objeto DB quando a classe Category é construída. Os métodos da classe DB então se tornam disponíveis para a classe Category através da sintaxe:

$this->db->method_name()

É possível usar essa sintaxe nas funções que trabalham com o banco de dados. Por exemplo:

$this->db->query($sql)

A função save é bem engenhosa por lidar tanto com a criação de novos registros quanto com a atualização daqueles já existentes. Ela verifica se o objeto tem um valor definido para a propriedade $id. Se tiver, ela executa uma instrução UPDATE; mas se não tiver, ela usa uma instrução INSERT para criar um novo registro.

SQL/XML explicado

Se você tem familiaridade com SQL e o desenvolvimento de aplicativos suportados por bancos de dados relacionais, deve ter percebido as funções incomuns usadas nas instruções SQL na classe Category. Essas funções são extensões especiais ao padrão SQL disponibilizadas pelo IBM DB2. Esse tipo de SQL estendido é denominado SQL/XML.

A primeira utilização do SQL/XML é evidente na função save. A função usa a função XMLPARSE para analisar o texto passado a ela como XML. Isso funciona bem nesse caso porque os dados XML com os quais você está trabalhando são muito básicos. Porém, você irá encontrar exemplos de exigências mais avançadas na análise de XML quando criar a classe Article.

O SQL/XML também é usado em getAllCategories, load e getCategoryNameById através da função XMLTABLE. Investiguemos isso em mais detalhes, observando a instrução SELECT na função getAllCategories mostrada na Listagem 5. Toda a formatação da cadeia PHP foi removida da listagem visando a legibilidade.

Listagem 5. Instrução SELECT na função getAllCategories
SELECT c.id, x.name
FROM category c, XMLTABLE('$d/category' PASSING c.data AS "d"
    COLUMNS name VARCHAR(200) PATH 'name') AS x
ORDER BY x.name

Todos os dados nessa instrução SELECT foram recuperados da tabela category. Essa tabela apresenta uma coluna chamada data que possui um tipo de XML. A Listagem 6 pega a tag XML <name> dessa coluna e a mapeia como um nome de coluna relacional. O resultado é colocado em uma tabela hipotética chamada x, o que a torna acessível no resto da consulta.

Listagem 6. Mapeando a tag <name> como um nome de coluna relacional
XMLTABLE('$d/category' PASSING c.data AS "d"
    COLUMNS name VARCHAR(200) PATH 'name') AS x

Isso possibilita o acesso aos conteúdos da tag <name> como se fosse uma coluna típica de bancos de dados relacionais.

Crie a classe Article

A outra classe de aplicativo que você deve criar é a classe Article. A Listagem 7 contém um extrato do arquivo article.php, que define a classe Article. Para obter a definição completa da classe, faça o download do código fonte na seção de Downloads. Salve seu arquivo article.php no subdiretório de classes do seu projeto.

Listagem 7. Extrato do arquivo article.php
...
    function save() {
        $sql = "";

        if($this->id) {
            $sql = "UPDATE article SET data = (XMLDOCUMENT(XMLELEMENT(NAME 
\"article\", XMLCONCAT("
                . " XMLELEMENT(NAME \"title\", ".$this->db->safe_no_html
($this->title)."), "
                . " XMLELEMENT(NAME \"content\", ".$this->db->safe_no_html
($this->content)."))))), "
                . " category_id = $this->category_id, date_modified = CURRENT 
TIMESTAMP WHERE id = $this->id";
        } else {
            $sql = "INSERT INTO article(date_created, date_modified, view_count, 
category_id, data) VALUES ( "
                . " CURRENT TIMESTAMP, CURRENT TIMESTAMP, 0, $this->category_id, 
(XMLDOCUMENT(XMLELEMENT(NAME \"article\", XMLCONCAT("
                . " XMLELEMENT(NAME \"title\", ".$this->db->safe_no_html
($this->title)."), "
                . " XMLELEMENT(NAME \"content\", ".$this->db->safe_no_html
($this->content)."))))))";
        }
        $result = $this->db->query($sql);
        if($result) return true;
        else return false;
    }
...

    function getArticlesBySearchTerm($search_term) {
        $search_term = strtolower(trim($search_term));
        $sql = 'SELECT a.id, x.title, a.date_created, a.view_count FROM article a, '
            . ' XMLTABLE(\'$d/article\' passing a.data as "d" '
            . ' COLUMNS title VARCHAR(200) PATH \'title\', '
            . ' content VARCHAR(4000) PATH \'content\') as x '
            . ' WHERE LOWER(x.title) LIKE \'%'.$this->db->safe_no_html
($search_term, false).'%\' '
            . ' OR LOWER(x.content) LIKE \'%'.$this->db->safe_no_html
($search_term, false).'%\' '
            . ' ORDER BY x.title';
        $result = $this->db->query($sql);
        $rows = array();
        while($row = $this->db->get_row($result)) {
            $rows[] = array($row[0], $row[1], $row[2], $row[3]);
        }
        return $rows;
    }
...
    function addComment($name, $comments, $ip_address) {
        $sql = 'INSERT INTO comment(date_left, article_id, approved, data) VALUES 
(CURRENT TIMESTAMP, '
            . $this->getId().', 0, (XMLDOCUMENT(XMLELEMENT(NAME "comment", 
XMLCONCAT('
            . ' XMLELEMENT(NAME "name", \''.$this->db->safe_no_html
($name, false).'\'), '
            . ' XMLELEMENT(NAME "ip_address", \''.$ip_address.'\'), '
            . ' XMLELEMENT(NAME "content", \''.$this->db->safe_no_html
($comments,false).'\'))))))';
        $result = $this->db->query($sql);
        if($result) return true;
        else return false;
    }
...

Observe que essa classe é bem maior que a classe Category. Ela contém getters e setters para a classe Article; funções para recuperar, salvar e excluir artigos; funções para recuperar e criar comentários e classificações; e funções para moderar comentários em artigos. Muitas dessas funções são parecidas umas com as outras, então vamos investigar alguns dos conceitos mais importantes introduzidos nessa classe

A primeira função de interesse é a função save. A Listagem 8 mostra as instruções INSERT e UPDATE nessa função sem a formatação da cadeia PHP.

Listagem 8. Instruções INSERT e UPDATE da função save
INSERT INTO article(date_created, date_modified, view_count, 
    category_id, data)
VALUES (CURRENT TIMESTAMP, CURRENT TIMESTAMP, 0, $this->category_id,
    (XMLDOCUMENT(XMLELEMENT(NAME "article", XMLCONCAT(
        XMLELEMENT(NAME "title", $this->title),
        XMLELEMENT(NAME "content", $this->content)
    ))))
)

UPDATE article
SET data = (XMLDOCUMENT(XMLELEMENT(NAME "article", XMLCONCAT(
    XMLELEMENT(NAME "title", $this->title),
    XMLELEMENT(NAME "content", $this->content)
)))), 
category_id = $this->category_id, 
date_modified = CURRENT TIMESTAMP
WHERE id = $this->id

Grande parte dessas duas instruções deve ser familiar. Na instrução INSERT, todos os valores, exceto o que está sendo inserido na coluna data, é SQL relacional padrão. Da mesma maneira, somente a coluna data está sendo atualizada com quaisquer dados incomuns na instrução UPDATE. Em ambos os casos, o valor que está sendo inserido na coluna data é gerado pelo código mostrado na Listagem 9.

Listagem 9. Gerando o valor que está sendo inserido na coluna data
(XMLDOCUMENT(XMLELEMENT(NAME "article", XMLCONCAT(
    XMLELEMENT(NAME "title", $this->title),
    XMLELEMENT(NAME "content", $this->content)
))))

Mapeamento XML explicado

Observe com mais atenção o que o código de mapeamento XML na classe Article faz. Primeiramente, a função XMLDOCUMENT diz ao banco de dados para criar um novo documento XML. A função recebe o próprio documento XML como um argumento. A função XMLELEMENT define um elemento XML que será incluído no documento XML. Ela primeiro recebe o nome do elemento como seu argumento e, em segundo lugar, o valor que será incluído entre as tags do elemento. É algo bem simples e direto para os elementos com nomes, título e conteúdo, mas como fica a primeira utilização da função XMLELEMENT? Isso é um pouco diferente, pois nesse caso estamos definindo um único nó externo (artigo nomeado), que deve conter o título interno e os elementos de conteúdo dentro dele. Nesse caso, a função XMLCONCAT constrói os conteúdos do nó do artigo com o título e os elementos de conteúdo adicionados como valor.

Por exemplo, se o valor de $this->title é "Guia do DB2" e $this->content é "Este é um guia detalhado do IBM DB2", a Listagem 9 geraria o XML mostrado na Listagem 10 para ser salvo na coluna data da tabela article.

Listagem 10. Exemplo de saída XML da Listagem 9
<article>
    <title>Guide to DB2</title>
    <content>This is an in-depth guide to IBM DB2</content>
</article>

A próxima função é a função getArticlesBySearchTerm. Essa função ilustra como os elementos dentro de uma coluna XML que são mapeados como colunas relacionais podem ser usados na cláusula WHERE de uma instrução SQL para restringir os dados retornados pela consulta, como se fossem uma coluna relacional comum. A instrução SELECT (com a formatação da cadeia PHP removida) é mostrada na Listagem 11.

Listagem 11. Instrução SELECT
SELECT a.id, x.title, a.date_created, a.view_count 
FROM article a, XMLTABLE('$d/article' PASSING a.data as "d"
    COLUMNS title VARCHAR(200) PATH 'title',
        content CLOB(16M) PATH 'content') AS x
WHERE LOWER(x.title) LIKE '%$search_term%'
OR LOWER(x.content) LIKE '%$search_term%'
ORDER BY x.title

Observe que os elementos de título e conteúdo na coluna XML data estão mapeados como colunas relacionais sob a tabela hipotética x. Estes são então usados na cláusula WHERE para filtrar os resultados de pesquisa apenas para aquelas linhas nas quais o termo de pesquisa está contido nos elementos de título ou conteúdo.

Outras funções Article importantes

Outra função interessante da classe Article é a getHighestRatedArticles. Essa função retorna um máximo de cinco artigos, ordenados pela classificação média em ordem descendente (com o artigo de classificação mais alta no topo). Na realidade, a instrução SELECT nessa função usa duas funções XMLTABLE para definir duas tabelas hipotéticas. Isso possibilita que ela obtenha informações do elemento de título na coluna XML data da tabela do artigo e o elemento de valor na coluna XML data da tabela de classificações. Ela então agrupa os resultados com base no valor médio de classificação. A versão extraída e formatada da instrução SQL é mostrada na Listagem 12.

Listagem 12. Versão extraída e formatada da instrução SQL
SELECT a.id, ax.title, a.date_created, a.view_count, AVG(rx.value)
FROM article a, rating r, XMLTABLE('$d/article' PASSING a.data AS "d"
    COLUMNS title VARCHAR(200) PATH 'title') AS ax,
XMLTABLE('$d/rating' PASSING r.data as "d"
    COLUMNS value INT PATH 'value') AS rx
WHERE a.id = r.article_id
GROUP BY a.id, ax.title, a.date_created, a.view_count
ORDER BY AVG(rx.value) DESC
FETCH FIRST 5 ROWS ONLY

Observe que é possível mapear mais de um documento XML a uma tabela relacional em uma única instrução SELECT. O exemplo também destaca o fato das colunas serem tratadas exatamente da mesma maneira que as colunas comuns — se você usar uma função de grupo (AVG nesse caso), é necessário incluir todas as outras colunas na cláusula GROUP BY, incluindo qualquer uma que tenha sido mapeada pela função XMLTABLE.

O coração do aplicativo de Base de Conhecimento está contido nas duas classes que você criou. Todas as funções para recuperar, inserir, atualizar e excluir dados estão armazenados nessas duas classes. Mais adiante neste tutorial, você verá como essas classes são usadas pelo aplicativo. Na próxima seção, você criará scripts de cabeçalho e rodapé, que serão usados por cada página no aplicativo para evitar a repetição de código HTML.


Componentes de design comuns

Ao invés de repetir o mesmo código HTML para elementos que são comuns a todas as páginas do aplicativo, é possível colocar os elementos comuns em arquivos que cada página poderá então incluir programaticamente. Nesta seção, você criará os arquivos de inclusão de cabeçalho e rodapé para a interface do aplicativo.

Com a parte mais difícil do aplicativo concluída, você pode se concentrar agora na construção da interface com o usuário do aplicativo de Base de Conhecimento. A Figura 3 mostra a aparência final da interface com o usuário.

Figura 3. O produto final
Screenshot of the Knowledge Base application. The four parts of the user interface are described in the following text.

A interface do aplicativo é dividida em quatro seções. No topo fica o cabeçalho, que contém o nome e o formulário de pesquisa do aplicativo. A área central é dividida em duas colunas. A coluna da esquerda contém os links de navegação do aplicativo para as categorias e páginas de administração disponíveis. A coluna da direita é a área principal de conteúdo onde o conteúdo de cada página individual é exibido. A parte inferior da tela é o rodapé, que neste caso simplesmente contém um aviso de copyright.

Crie o cabeçalho e o rodapé

Nesta seção, você criará dois arquivos: header.php e footer.php. O arquivo header.php desenha o cabeçalho superior e as seções de navegação. O arquivo footer.php desenha o rodapé inferior e fecha a página HTML. Esses arquivos são muito simples e diretos, portanto passaremos rapidamente por eles.

A Listagem 13 contém um extrato do arquivo header.php. O código fonte na seção Downloads contém o arquivo completo. Salve seu arquivo header.php no subdiretório de includes do seu projeto.

Listagem 13. Extrato do arquivo header.php
...
                <!-- Start Sidebar Section -->
                <div class="sidebar">
                    <div class="box">
                        <div class="box_header">
                            Categories
                        </div>
                        <div class="box_content">
                            <ul>
                                <?php
                                if(is_array($cat_list_items) && 
count($cat_list_items) > 0) {
                                    for($i = 0; $i < count($cat_list_items); $i++) {
                                        $cat_id = $cat_list_items[$i][0];
                                        $cat_name = $cat_list_items[$i][1];
                                        echo '<li><a
 href="category_view.php?id='.$cat_id.'">'.$cat_name.'</a>';
                                    }
                                } else {
                                    echo "<li>No categories found</li>";
                                }
                                ?>
                            </ul>
                        </div>
                    </div>
 ...

O único recurso ligeiramente complexo desse arquivo é que ele recupera a lista de categorias para exibição na área de navegação do banco de dados DB2, usando a função getAllCategories da classe Category. Se essa função retorna um resultado adequado como um array, ela mostra cada categoria como um link para a página View Category dessa categoria específica. Além disso, observe que a tag <title> usa a variável $title, apesar da variável não ser declarada no próprio arquivo header.php. Isso possibilita que você defina o título em cada página do aplicativo, declarando a variável $title antes de incluir o arquivo header.php em cada página.

A Listagem 14 mostra os conteúdos do arquivo footer.php, que deve ser salvo no subdiretório de includes do seu projeto.

Listagem 14. footer.php
                </div>
                <!-- End Content Section -->

                <div class="clear">&nbsp;</div>
            </div>
            <!-- End Main Section -->

            <!-- Start Footer -->
            <div class="footer">
                &copy; 2009 Knowledge Base. All rights reserved.
            </div>
            <!-- End Footer -->
        </div>
        <!-- End Page Container -->
    </body>
</html>

Na realidade, esse arquivo não contém nenhum código PHP. Ele simplesmente conclui a página com um aviso de copyright e com os elementos de fechamento de HTML necessários. Na próxima seção, você criará a interface de administração do seu aplicativo.


Criando a interface de administração

Com as classes fundamentais e os componentes comuns concluídos, você pode prosseguir e criar a interface com o usuário para o aplicativo. Antes de começar, observemos as páginas que compõem o aplicativo. No fim deste tutorial, você deve possuir os seguintes arquivos no diretório-raiz do seu projeto:

  • article_comment_process.php
  • article_edit.php
  • article_edit_process.php
  • article_manage.php
  • article_manage_process.php
  • article_rate_process.php
  • article_view.php
  • category_edit.php
  • category_edit_process.php
  • category_manage.php
  • category_manage_process.php
  • category_view.php
  • comment_moderate.php
  • comment_moderate_process.php
  • index.php
  • search_results.php

A maioria desses arquivos é para a interface de administração, que possibilita a adição, a edição e a exclusão de artigos e categorias, assim como a moderação de comentários. Nesta seção, você primeiro criará a interface de administração, começando com o formulário para criar e editar categorias. Quando tiver concluído o desenvolvimento da interface de administração, você construirá as páginas do cliente, que permitirão aos usuários visualizar categorias e artigos, pesquisar artigos, adicionar comentários e enviar classificações.

Nota: Está fora do escopo deste tutorial fornecer detalhes sobre a criação de folhas de estilo; porém, o código fonte na seção Downloads contém um arquivo de folha de estilo chamado main.css no subdiretório css. Não se esqueça de copiar esse arquivo para o subdiretório css do seu projeto para que ele possa ser usado no controle da aparência e da formatação das páginas de interface do aplicativo.

Crie as páginas de administração de categorias

A primeira página de administração a ser criada é a página Create Category/Edit Category. Essa página possibilita tanto a criação de novas categorias quanto a modificação das já existentes. Ela determina se está no modo Create ou Edit com base no envio ou não de um parâmetro de id à página.

Crie um arquivo chamado category_edit.php, copie nele o código da Listagem 15, e salve o arquivo no diretório-raiz do seu projeto.

Listagem 15. category_edit.php
<?php
require_once("classes/category.php");

if(isset($_GET['id'])) {
    $id = $_GET['id'];
    $category = new Category;
    $category->load($id);
}

if(isset($_GET['msg_type']) && isset($_GET['msg'])) {
    if($_GET['msg_type'] == "1") $success_msg = $_GET['msg'];
    else if($_GET['msg_type'] == "2") $error_msg = $_GET['msg'];
}

if(isset($id)) $title = "Edit Category";
else $title = "Create New Category";

include("includes/header.php");
?>
<div class="box">
    <div class="box_header">
        <?php echo $title; ?>
    </div>
    <div class="box_content">
        <?php
        if(isset($success_msg)) echo '<div class="success_msg">'.
$success_msg.'</div>';
        else if(isset($error_msg)) echo '<div class="error_msg">'.
$error_msg.'</div>';
        ?>
        <div class="category_form">
            <form name="category" method="post" 
action="category_edit_process.php">
                <?php
                if(isset($id)) {
                    echo '<div class="category_form_field">';
                    echo '<label for="id">ID:</label>';
                    echo '<input type="text" name="id" id="id" value="'.$id.'" 
readonly="readonly" />';
                    echo '</div>';
                }
                ?>
                <div class="category_form_field">
                    <label for="name">Name:</label>
                    <input type="text" name="name" id="name"
                    <?php if(isset($category)) echo 'value="'.
$category->getName().'"'; ?>
                    />
                </div>
                <?php
                if(isset($id)) echo '<input type="submit" 
value="Edit Category" />';
                else echo '<input type="submit" value="Create Category" />';
                ?>
            </form>
        </div>

        <div class="admin_link">
            <a href="category_manage.php">Manage Existing Categories</a>
        </div>
    </div>
</div>
<?php
include("includes/footer.php");
?>

Essa página verifica primeiro se o parâmetro de id GET foi ou não enviado a ela. Se foi, ela define o título da página como Edit Category e exibe o ID e os valores de nome da categoria (veja a Figura 4). Se o parâmetro não foi enviado, ela define o título da página como Create New Category e permite que o usuário insira um nome e crie uma nova categoria (Figura 5).

Figura 4. Edit Category
Edit Category interface of category_edit.php: Category ID and Name fields with an Edit Category button and Manage Categories link.
Figura 5. Create New Category
Create New Category interface of category_edit.php: Name field with Create Category button and Manage Categories link.

O processamento efetivo de uma categoria nova ou já existente não é conduzido pelo arquivo category_edit.php em si. Um erro comum que os desenvolvedores de aplicativos da Web frequentemente cometem é colocar o código de manipulação no mesmo arquivo que o formulário de envio. Isso pode fazer com que o formulário seja reenviado diversas vezes se o usuário atualizar a página ou pressionar o botão voltar. Para evitar que isso ocorra, deve-se controlar o processamento de transações do banco de dados em um arquivo separado, e então direcionar o usuário de volta ao formulário ou a uma página diferente.

Para este aplicativo, o formulário deve ser enviado a um arquivo chamado category_edit_process.php. Crie um arquivo chamado category_edit_process.php, copie nele o código da Listagem 16, e salve o arquivo no diretório-raiz do seu projeto.

Listagem 16. category_edit_process.php
<?php
require_once("classes/category.php");

$query_string = "";

$category = new Category;
if(isset($_POST['id'])) {
    $category->setId($_POST['id']);
    $query_string = '?id='.$_POST['id'];
}

if(strlen($_POST['name']) > 0) {
    $category->setName($_POST['name']);
    $success = $category->save();
    if($success) {
        $msg = "Category saved successfully.";
        $msg_type = 1;
    } else {
        $msg = "Database error occurred.";
        $msg_type = 2;
    }
} else {
    $msg = "Name is a mandatory field.";
    $msg_type = 2;
}

if(strlen($query_string) > 0) {
    $query_string .= "&msg=$msg&msg_type=$msg_type";
} else {
    $query_string = "?msg=$msg&msg_type=$msg_type";
}

header("Location: category_edit.php".$query_string);
?>

O código nesse arquivo aceita parâmetros que correspondem aos controles de formulário na página category_edit.php. Se a ação que está sendo executada é uma edição, ela define o ID da categoria no objeto e salva. A classe Category reconhecerá que ela é uma categoria existente e a atualizará de forma apropriada. Da mesma maneira, se ela for uma nova categoria, a classe reconhecerá que não há um ID e criará um novo registro. O script category_edit_process.php gera uma mensagem de sucesso ou erro com base no sucesso ou no fracasso de uma transação, e então direciona o usuário de volta à página anterior. A Figura 6 mostra um exemplo de uma mensagem de sucesso. Observe a caixa verde com a mensagem de sucesso acima do campo do ID.

Figure 6. Mensagem de sucesso após Category Edit
Edit Category interface of category_edit.php with message 'Category saved successfully'.

A página que você acabou de criar possibilita aos usuários editar categorias, mas também exige que um ID de categoria seja enviado à página como parâmetro. Nenhum aplicativo decente esperaria que o usuário soubesse como enviar tais parâmetros ou conhecesse o ID de uma categoria. Portanto, vamos criar uma página Manage Categories que relacione as categorias existentes no banco de dados e forneça ao usuário os links para visualizar, editar ou excluir uma categoria específica. Crie um arquivo chamado category_manage.php, copie nele o código da Listagem 17, e salve o arquivo no diretório-raiz do seu projeto.

Listagem 17. category_manage.php
<?php
require_once("classes/category.php");

if(isset($_GET['msg_type']) && isset($_GET['msg'])) {
    if($_GET['msg_type'] == "1") $success_msg = $_GET['msg'];
    else if($_GET['msg_type'] == "2") $error_msg = $_GET['msg'];
}

$title = "Manage Categories";

$category = new Category;
$categories = $category->getAllCategories();

include("includes/header.php");
?>
<div class="box">
    <div class="box_header">
        <?php echo $title; ?>
    </div>
    <div class="box_content">
        <?php
        if(isset($success_msg)) echo '<div class="success_msg">'
.$success_msg.'</div>';
        else if(isset($error_msg)) echo '<div class="error_msg">'
.$error_msg.'</div>';
        ?>
        <div class="category_manage">
            <table cellpadding="6" cellspacing="0" border="0">
                
<thead><tr><th>ID:</th><th>Name:</th><th>
Actions:</th></tr></thead>
                <tbody>
                    <?php
                    if(is_array($categories) && count($categories) > 0) {
                        for($i = 0; $i < count($categories); $i++) {
                            $cat_id = $categories[$i][0];
                            $cat_name = $categories[$i][1];
                            if(($i+1) % 2 == 0) echo '<tr>';
                            else echo '<tr class="odd">';
                            echo '<td>'.$cat_id.'</td>';
                            echo '<td>'.$cat_name.'</td>';
                            echo '<td>';
                            echo '<a href="category_view.php?id='.
$cat_id.'">View</a> | ';
                            echo '<a href="category_edit.php?id='.
$cat_id.'">Edit</a> | ';
                            echo '<a href="category_manage_process.php?id='.
$cat_id.'">Delete</a>';
                            echo '</td></tr>';
                        }
                    } else {
                        echo '<tr><td colspan="3">No categories found.
</td></tr>';
                    }
                    ?>
                </tbody>
            </table>
        </div>
        <div class="admin_link">
            <a href="category_edit.php">Create New Category</a>
        </div>
    </div>
</div>
<?php
include("includes/footer.php");
?>

A principal função dessa página é fazer a chamada para a função getAllCategories na classe Category e emitir o ID e o nome de cada categoria em uma tabela com links para visualizar, editar e excluir. A função getAllCategories retorna um array. Cada item no array é ele próprio um array com dois valores: o ID da categoria e o nome da categoria. A página verifica se o resultado é mesmo um array e se não está vazio. Se forem verificados corretamente, ela efetua um loop pelo array e emite uma linha de tabela para cada categoria. A Figura 7 mostra um exemplo da aparência de uma página de saída.

Figura 7. Manage Categories
Sample Manage Categories page from category_manage.php. 12 categories listed with ID, Name and links for View, Edit, and Delete

Apesar de essa página parecer boa, ela não é 100% funcional neste momento. Você implementará a funcionalidade Visualizar mais tarde, mas agora vamos dar ao aplicativo a habilidade de excluir categorias. Como as páginas Create/Edit, a página Manage também usa um script PHP intermediário para processar exclusões. Crie um arquivo chamado category_manage_process.php, copie nele o código da Listagem 18, e salve o arquivo no diretório-raiz do seu projeto.

Listagem 18. category_manage_process.php
<?php
require_once("classes/category.php");

$query_string = "";

$category = new Category;
if(isset($_GET['id'])) {
    $category->setId($_GET['id']);
}

$success = $category->delete();
if($success) {
    $msg = "Category deleted successfully.";
    $msg_type = 1;
} else {
    $msg = "Database error occurred.";
    $msg_type = 2;
}

$query_string = "?msg=$msg&msg_type=$msg_type";

header("Location: category_manage.php".$query_string);
?>

Esse código funciona de forma parecida com o arquivo category_edit_process.php, exceto pelo fato de ordenar que o banco de dados exclua uma categoria existente ao invés de atualizá-la ou criar uma nova. Quando os usuários excluem uma categoria, eles veem uma mensagem de sucesso na página Manage Categories, similar à mensagem mostrada na página Edit Category na Figura 6.

Crie as páginas de administração do artigo

Em seguida, é necessário permitir a adição, a edição e a exclusão de artigos. Isso funciona quase da mesma maneira que o gerenciamento de categorias. A única dificuldade adicional é que o artigo contém uma chave estrangeira de ID de categoria que é mapeada de volta à tabela category. Por isso, a página Create/Edit Article precisa permitir ao usuário selecionar uma Category armazenada no banco de dados e armazenar o ID dessa categoria na coluna category_id.

A Listagem 19 contém um extrato do arquivo article_edit.php, que contém o código para a página que permite que os usuários criem e editem os artigos da Base de Conhecimento. O código fonte na seção Downloads contém o arquivo completo. Salve seu arquivo article_edit.php no diretório-raiz do seu projeto.

Listagem 19. Extrato do arquivo article_edit.php
...
if(isset($_GET['id'])) {
    $id = $_GET['id'];
    $article = new Article;
    $article->load($id);
}
...
        <div class="article_form">
            <form name="article" method="post" action="article_edit_process.php">
                <?php
                if(isset($id)) {
                    echo '<div class="article_form_field">';
                    echo '<label for="id">ID:</label>';
                    echo '<input type="text" name="id" id="id" value="'.$id.'" 
readonly="readonly" />';
                    echo '</div>';
                }
                ?>
                <div class="article_form_field">
                    <label for="title">Title:</label>
                    <input type="text" name="title" id="title"
                    <?php if(isset($article)) echo 'value="'.$article->getTitle()
.'"'; ?>
                    />
                </div>
                <div class="article_form_field">
                    <label for="category">Category:</label>
                    <select name="category" id="category">

                    </select>
				</div>
...
                <div class="article_form_field">
                    <label for="content">Article Content:</label>
                    <textarea name="content" id="content"><?php 
if(isset($article))echo $article->getContent(); ?></textarea>
                </div>
                <?php
                if(isset($id)) echo '<input type="submit" 
value="Edit Article" />';
                else echo '<input type="submit" value="Create Article" />';
                ?>
            </form>
...

A listagem acima mostra o código que detecta se o formulário deve estar no modo criar ou editar. Ele detecta isso a partir do id da variável GET. O formulário do artigo então usa o resultado dessa detecção para determinar se deve ou não imprimir os valores existentes nos campos do formulário.

Ao criar um novo artigo, a página tem a aparência da captura de tela na Figura 8.

Figura 8. Create New Article
Create New Article interface of article_edit.php: Title, Category, Content fields ; Create Article button; Manage Articles link

Ao editar um artigo existente, a página possui mais campos que são exibidos para fins informativos (somente leitura), como mostrado na Figura 9.

Figura 9. Edit Article
Edit Article interface of article_edit.php: Additional read only fields for article detail ; Edit Article button; Manage Articles link.

Como na página Create/Edit Category, essa página usa um script intermediário para processar a transação de inserção ou atualização antes de dirigir o usuário de volta à página Create/Edit Article. Crie um arquivo chamado article_edit_process.php, copie nele o código da Listagem 20, e salve o arquivo no diretório-raiz do seu projeto.

Listagem 20. article_edit_process.php
<?php
require_once("classes/article.php");

$query_string = "";

$article = new Article;
if(isset($_POST['id'])) {
    $article->setId($_POST['id']);
    $query_string = '?id='.$_POST['id'];
}

if(!(strlen($_POST['title']) > 0)) {
    $msg = "Title is a mandatory field.";
    $msg_type = 2;
} else if(!(strlen($_POST['category']) > 0) || ($_POST['category'] == "0")) {
    $msg = "Category is a mandatory field.";
    $msg_type = 2;
} else if(!(strlen($_POST['content']) > 0)) {
    $msg = "Article Content is a mandatory field.";
    $msg_type = 2;
} else {
    $article->setTitle($_POST['title']);
    $article->setCategoryId($_POST['category']);
    $article->setContent($_POST['content']);
    $success = $article->save();
    if($success) {
        $msg = "Article saved successfully.";
        $msg_type = 1;
    } else {
        $msg = "Database error occurred.";
        $msg_type = 2;
    }
}

if(strlen($query_string) > 0) {
    $query_string .= "&msg=$msg&msg_type=$msg_type";
} else {
    $query_string = "?msg=$msg&msg_type=$msg_type";
}

header("Location: article_edit.php".$query_string);
?>

Para que os usuários sejam capazes de editar e excluir artigos, é necessário fornecer uma lista de artigos existentes a partir da qual o usuário possa escolher editar ou excluir. A listagem 21 contém um extrato do arquivo article_manage.php, que contém o código da página Manage Articles. O código fonte na seção Downloads contém o arquivo completo. Salve seu arquivo article_manage.php no diretório-raiz do seu projeto.

Listagem 21. Extrato do arquivo article_manage.php
...
$article = new Article;
$articles = $article->getAllArticles();
...
            <table cellpadding="6" cellspacing="0" border="0">
                
<thead><tr><th>ID:</th><th>Title:
</th><th>Category:</th>
                    <th>Views:</th><th>Actions:</th>
</tr></thead>
                <tbody>
                    <?php
                    if(is_array($articles) && count($articles) > 0) {
                        for($i = 0; $i < count($articles); $i++) {
                            $art_id = $articles[$i][0];
                            $art_title = $articles[$i][1];
                            $art_cat = $articles[$i][4];
                            $art_vc = $articles[$i][5];
                            if(($i+1) % 2 == 0) echo '<tr>';
                            else echo '<tr class="odd">';
                            echo '<td>'.$art_id.'</td>';
                            echo '<td>'.$art_title.'</td>';
                            echo '<td>'.$art_category->getCategoryNameById
($art_cat).'</td>';
                            echo '<td>'.$art_vc.'</td>';
                            echo '<td>';
                            echo '<a href="article_view.php?id='.$art_id.
'">View</a> | ';
                            echo '<a href="article_edit.php?id='.$art_id.
'">Edit</a> | ';
                            echo '<a href="article_manage_process.php?id='.$art_id.
'">Delete</a>';
                            echo '</td></tr>';
                        }
                    } else {
                        echo '<tr><td colspan="3">No articles 
found.</td></tr>';
                    }
                    ?>
                </tbody>
            </table>
...

Essa página é parecida com a página correspondente para gerenciar categorias. Ela é mostrada na Figura 10.

Figura 10. Manage Articles
Manage Articles page from articles_manage.php. 4 articles listed by ID, Title, Category, Views; Action links for View, Edit, Delete

Em sua forma atual, essa página permite apenas a edição de artigos. A visualização de artigos é abordada mais adiante quando for feita a implementação do lado do cliente do aplicativo, mas agora vamos adicionar a funcionalidade de exclusão. Crie um arquivo chamado article_manage_process.php, copie nele o código da Listagem 22, e salve o arquivo no diretório-raiz do seu projeto.

Listagem 22. article_manage_process.php
<?php
require_once("classes/article.php");

$query_string = "";

$article = new Article;
if(isset($_GET['id'])) {
    $article->setId($_GET['id']);
}

$success = $article->delete();
if($success) {
    $msg = "Article deleted successfully.";
    $msg_type = 1;
} else {
    $msg = "Database error occurred.";
    $msg_type = 2;
}

$query_string = "?msg=$msg&msg_type=$msg_type";

header("Location: article_manage.php".$query_string);
?>

Crie a página de moderação de comentários

A seção de administração da Base de Conhecimento está quase concluída. O último recurso que você precisa implementar é a página Moderate Comments, que permite que o administrador aprove ou rejeite quaisquer comentários enviados para um artigo. Essa página é parecida em estilo com a página Manage Articles. Ela exibe quaisquer comentários que tenham o valor da coluna approved definido como 0 (zero). Ao lado de cada comentário, ela exibe dois links: um para Approve e um para Reject. Clicar em Approve define o valor da coluna approved como 1, o que significa que o comentário é exibido na página View Article ao lado dos outros comentários aprovados. Clicar em Reject define o valor da coluna approved como 2, o que significa que o comentário não será exibido.

A listagem 23 contém um extrato do arquivo comment_moderate.php, que contém o código da página Moderate Comments. O código fonte na seção Downloads contém o arquivo completo. Salve seu arquivo comment_moderate.php no diretório-raiz do seu projeto.

Listagem 23. Extrato do arquivo comment_moderate.php
...
$article = new Article;
$comments = $article->getPendingComments();
...
            <table cellpadding="6" cellspacing="0" border="0">
                <thead><tr><th>Comment:</th><th>Actions:
</th></tr></thead>
                <tbody>
                    <?php
                    if(is_array($comments) && count($comments) > 0) {
                        for($i = 0; $i < count($comments); $i++) {
                            $com_id = $comments[$i][0];
                            $com_name = $comments[$i][1];
                            $com_date = $comments[$i][2];
                            $com_text = $comments[$i][4];
                            $com_title = $article->getArticleTitleById
($comments[$i][3]);
                            if(($i+1) % 2 == 0) echo '<tr>';
                            else echo '<tr class="odd">';
                            echo '<td>'.$com_text.'<br /><em>Left 
By: '.$com_name
                                .' on '.$com_date.'<br />Article: '.$com_title
.'</td>';
                            echo '<td>';
                            echo '<a
 href="comment_moderate_process.php?id='.$com_id.'&action=A">Approve</a> | ';
                            echo '<a
 href="comment_moderate_process.php?id='.$com_id.'&action=R">Reject</a>';
                            echo '</td></tr>';
                        }
                    } else {
                        echo '<tr><td colspan="3">No comments pending 
moderation.</td></tr>';
                    }
                    ?>
                </tbody>
            </table>
...

A Figura 11 mostra um exemplo da página Moderate Comments. Cada comentário pendente é exibido com o texto do comentário, o nome da pessoa que o deixou, quando ele foi deixado, e o título do artigo ao qual ele está relacionado.

Figura 11. Moderate Comments
Moderate Comments page from comment_moderate.php. 3 comments listed with details; Action links for Approve, Reject.

Agora vamos ativar os links Approve e Reject na página Moderate Comments. Crie um arquivo chamado comment_moderate_process.php, copie nele o código da Listagem 24, e salve o arquivo no diretório-raiz do seu projeto.

Listagem 24. comment_moderate_process.php
<?php
require_once("classes/article.php");

$query_string = "";

$article = new Article;
if(isset($_GET['id']) && isset($_GET['action'])) {
    if($_GET['action'] == "A") {
        $success = $article->approveComment($_GET['id']);
        $msg = "Comment approved successfully.";
    } else if($_GET['action'] == "R") {
        $success = $article->rejectComment($_GET['id']);
        $msg = "Comment rejected successfully.";
    }
}

if($success) {
    $msg_type = 1;
} else {
    $msg = "Database error occurred.";
    $msg_type = 2;
}

$query_string = "?msg=$msg&msg_type=$msg_type";

header("Location: comment_moderate.php".$query_string);
?>

Esse script procura dois parâmetros: um id e um indicador de ação, que deve ser ou "A" (Aprovar) ou "R" (Rejeitar). Com base nesse indicador, ele marca os comentários selecionados como aprovados ou rejeitados e redireciona o usuário de volta à página Moderate Comments.

Essa é a última das páginas de administração — a seguir é hora de criar todas as importantíssimas páginas do lado do cliente, que serão vistas pelos usuários finais do aplicativo.


Criando a interface do cliente

Nesta seção, você criará a interface principal que os usuários veem quando usam o aplicativo.

A interface do cliente é composta por estas quatro páginas:

  • Home — exibe a lista dos artigos mais recentes, os artigos com classificação mais alta e os artigos com mais visualizações.
  • Search Results — exibe a lista de artigos encontrados nos quais o título ou o conteúdo corresponde ao termo de pesquisa inserido pelo usuário no formulário de pesquisa que aparece no topo de todas as páginas do aplicativo.
  • View Category — exibe todos os artigos de uma categoria específica.
  • View Article — clicar no título de um artigo, em qualquer uma das três páginas mencionadas acima, leva o usuário à página View Article. Essa página exibe a Meta informação sobre o artigo, assim como o conteúdo principal do próprio artigo. Ela exibe qualquer comentário aprovado deixado no artigo e também sua classificação média atual. Ela também inclui um formulário de classificação e um de comentários que permitem aos usuários classificar o artigo ou deixar um comentário (com aprovação pendente do administrador).

Crie a home page

A Home page é a página que os usuários veem quando acabam de iniciar o aplicativo. Faz sentido que essa página exiba os artigos mais recentes, os artigos com a melhor classificação, e os artigos mais populares.

A listagem 25 contém um extrato do arquivo index.php, que contém o código da home page do aplicativo. O código fonte na seção Downloads contém o arquivo completo. Salve seu arquivo index.php no diretório-raiz do seu projeto.

Listagem 25. Extrato do arquivo index.php
...
$article = new Article;
$newest_articles = $article->getTopArticles("date_created DESC");
$highest_rated_articles = $article->getHighestRatedArticles();
$most_viewed_articles = $article->getTopArticles("view_count DESC");
...
    <div class="box_content">
        <?php
        if(is_array($newest_articles) && count($newest_articles) > 0) {
            for($i = 0; $i < count($newest_articles); $i++) {
                $art_id = $newest_articles[$i][0];
                $art_title = $newest_articles[$i][1];
                $art_dc = $newest_articles[$i][2];
                $art_vc = $newest_articles[$i][3];
                $comments = $article->countCommentsByArticleId($art_id);
                $rating = $article->getRatingByArticleId($art_id);
                $rating_count = $rating['rating_count'];
                $rating_average = number_format($rating['rating_average'], 2, '.', '');
                if($rating_average == 0) $rating_average = "Not yet rated";

                if(($i+1) % 2 == 0) echo '<div class="box_item">';
                else echo '<div class="box_item odd">';
                echo '<div class="box_item_title"><a
 href="article_view.php?id='.$art_id.'">'.$art_title.'</a></div>';
                echo '<div class="box_item_date">'.$art_dc.'</div>';
                echo '<div class="box_item_category">'.$art_vc.' 
view(s)</div>';
                echo '<div class="box_item_comments">'.$comments.' 
Comment(s)</div>';
                echo '<div class="box_item_rating">Rating: '.$rating_average.' 
('.$rating_count.' votes)</div>';
                echo '<div class="clear">&nbsp;</div>';
                echo '</div>';
            }
        } else {
            echo '<div>No articles found.</div>';
        }
        ?>
    </div>
</div>
...

As listagens acima incluem o código que exibe os artigos mais recentes. Conceitualmente, esse código é quase idêntico ao código para os artigos com classificação mais alta e com mais visualizações.

A home page usa duas funções na classe Article: getTopArticles e getHighestRatedArticles. A função getTopArticles aceita um parâmetro que é conectado à cláusula ORDER BY para determinar como os resultados devem ser ordenados. Cada uma dessas funções retorna um array de artigos. O script então efetua um loop pelo array retornado e exibe um link para cada artigo, junto com alguns detalhes adicionais sobre o artigo. A Figura 12 mostra um exemplo da Home page.

Figura 12. Home page
Example of the Home page. Main content area shows Newest, Highest Rated, and Most Viewed articles.

Crie as páginas Search Results e View Category

As páginas Search Results e View Category são similares. A página Search Results retorna os resultados com base em um termo de pesquisa que o usuário inseriu no formulário de pesquisa que aparece no topo de todas as páginas no aplicativo. A página View Category exibe todos os artigos em uma categoria específica. O ID da categoria é passado à página ou pelos links de navegação de categoria no lado esquerdo da tela, ou pela página de administração Manage Categories.

Crie um arquivo chamado search_results.php, copie nele o código da Listagem 26, e salve o arquivo no diretório-raiz do seu projeto.

Listagem 26. search_results.php
<?php
require_once("classes/article.php");

if(isset($_GET['query'])) {
    $search_term = $_GET['query'];
    
    $article = new Article;
    $search_articles = $article->getArticlesBySearchTerm($search_term);
    
    $title = "Search Results for "$search_term"";
}

include("includes/header.php");
?>
<div class="box">
    <div class="box_header">
        <?php echo $title; ?>
    </div>
    <div class="box_content">
        <?php
        if(is_array($search_articles) && count($search_articles) > 0) {
            for($i = 0; $i < count($search_articles); $i++) {
                $art_id = $search_articles[$i][0];
                $art_title = $search_articles[$i][1];
                $art_dc = $search_articles[$i][2];
                $art_vc = $search_articles[$i][3];
                $comments = $article->countCommentsByArticleId($art_id);
                $rating = $article->getRatingByArticleId($art_id);
                $rating_count = $rating['rating_count'];
                $rating_average = number_format($rating['rating_average'], 2, '.', '');
                if($rating_average == 0) $rating_average = "Not yet rated";
                
                if(($i+1) % 2 == 0) echo '<div class="box_item">';
                else echo '<div class="box_item odd">';
                echo '<div class="box_item_title"><a
 href="article_view.php?id='.$art_id.'">'.$art_title.'</a></div>';
                echo '<div class="box_item_date">'.$art_dc.'</div>';
                echo '<div class="box_item_category">'.$art_vc.' 
view(s)</div>';
                echo '<div class="box_item_comments">'.$comments.' 
Comment(s)</div>';
                echo '<div class="box_item_rating">Rating: '.$rating_average.' 
('.$rating_count.' votes)</div>';
                echo '<div class="clear">&nbsp;</div>';
                echo '</div>';
            }
        } else {
            echo '<div>No articles found that match the query
 <strong>'.$search_term.'</strong></div>';
        }
        ?>
    </div>    
</div>        
<?php
include("includes/footer.php");
?>

A Figura 13 mostra um exemplo da página Search Results.

Figura 13. Search Results
Example of the Search Results page. 2 articles are listed with links to article content, number of comments, date and rating.

Em seguida, crie a página View Category. Crie um arquivo chamado category_view.php, copie nele o código da Listagem 27, e salve o arquivo no diretório-raiz do seu projeto.

Listagem 27. category_view.php
<?php
require_once("classes/category.php");
require_once("classes/article.php");

if(isset($_GET['id'])) {
    $category_id = $_GET['id'];
    
    $category = new Category;
    $category->load($category_id);
    $category_name = $category->getName();
    
    $title = $category_name.' Articles';
    
    $article = new Article;
    $category_articles = $article->getArticlesByCategory($category_id);
}

include("includes/header.php");
?>
<div class="box">
    <div class="box_header">
        <?php echo $title; ?>
    </div>
    <div class="box_content">
        <?php
        if(is_array($category_articles) && count($category_articles) > 0) {
            for($i = 0; $i < count($category_articles); $i++) {
                $art_id = $category_articles[$i][0];
                $art_title = $category_articles[$i][1];
                $art_dc = $category_articles[$i][2];
                $art_vc = $category_articles[$i][3];
                $comments = $article->countCommentsByArticleId($art_id);
                $rating = $article->getRatingByArticleId($art_id);
                $rating_count = $rating['rating_count'];
                $rating_average = number_format($rating['rating_average'], 2, '.', '');
                if($rating_average == 0) $rating_average = "Not yet rated";
                
                if(($i+1) % 2 == 0) echo '<div class="box_item">';
                else echo '<div class="box_item odd">';
                echo '<div class="box_item_title"><a
 href="article_view.php?id='.$art_id.'">'.$art_title.'</a></div>';
                echo '<div class="box_item_date">'.$art_dc.'</div>';
                echo '<div class="box_item_category">'.$art_vc.' 
view(s)</div>';
                echo '<div class="box_item_comments">'.$comments.' 
Comment(s)</div>';
                echo '<div class="box_item_rating">Rating: '.$rating_average.' 
('.$rating_count.' votes)</div>';
                echo '<div class="clear">&nbsp;</div>';
                echo '</div>';
            }
        } else {
            echo '<div>No articles to display.</div>';
        }
        ?>
    </div>    
</div>        
<?php
include("includes/footer.php");
?>

A interface resultante para essa página é similar à página Search Results. A Figura 14 mostra um exemplo.

Figura 14. View Category
View Category page lists articles for a single category. article title is link to content.

Crie a página View Article

A última peça do quebra-cabeça é a página View Article. Essa página é a mais complexa da interface do cliente, mas ainda assim é bem simples. Ela usa a função load da classe Article para recuperar todos os detalhes do artigo selecionado. Ela então usa as funções getRatingByArticleId, countCommentsByArticleId e getCommentsByArticleId para receber as informações relacionadas de classificação e comentários. Ela também chama a função incrementViewCount, que aumenta a contagem de visualizações do artigo em um.

A listagem 28 contém um extrato do arquivo article_view.php, que contém o código da página View Article. O código fonte na seção Downloads contém o arquivo completo. Salve seu arquivo article_view.php no diretório-raiz do seu projeto.

Listagem 28. Extrato do arquivo article_view.php
...
    $article->incrementViewCount();

    $rating = $article->getRatingByArticleId($article_id);
...
    $comment_count = $article->countCommentsByArticleId($article_id);
    $comments = $article->getCommentsByArticleId($article_id);

...
    $category_name = $category->getCategoryNameById($article->getCategoryId());
}
...
        <div class="article_meta">
...
        </div>
        <div class="article_body">
            <?php echo $article->getContent(); ?>
        </div>
        <div class="article_rating">
            <div class="article_rating_rate">
                <form name="rating" method="post" action=
"article_rate_process.php">
...
                </form>
            </div>
            <div class="article_rating_votes">
                Rating: <?php echo $rating_average; ?>
                (based on <?php echo $rating_count; ?> votes)
            </div>
            <div class="clear">&nbsp;</div>
        </div>
    </div>
</div>
<div class="spacer">&nbsp;</div>
<div class="box">
    <div class="box_header">
        Comments
    </div>
    <div class="box_content">
        <div class="comment_form">
            <div class="comment_form_title">
                Leave a Comment
            </div>
            <form name="comment" method="post"
...
            </form>
        </div>
        <div class="comment_list">
            <div class="comment_list_title">
                <?php echo $comment_count; ?> Comment(s)
            </div>
            <?php if($comment_count > 0) { ?>
            <div class="comment_list_items">
                <?php
                if(is_array($comments) && count($comments) > 0) {
                    for($i = 0; $i < count($comments); $i++) {
                        $comment_name = $comments[$i][0];
                        $comment_date = $comments[$i][1];
                        $comment_content = $comments[$i][2];
                        if(($i+1) % 2 == 0) echo '<div class=
"comment_list_item">';
                        else echo '<div class="comment_list_item odd">';
                        echo '<div class="comment_list_item_author">'.
$comment_name
                            .' on '.$comment_date.' said:</div>';
                        echo '<div class="comment_list_item_content">'.
$comment_content.'</div>';
                        echo '</div>';
                    }
                }
                ?>
            </div>
            <?php } ?>
        </div>
    </div>
...

Além de exibir todas as informações sobre o artigo e incluir o conteúdo do artigo em si, essa página também inclui dois formulários: um para enviar uma classificação e outro para enviar um comentário. Cada um desses formulários possui um script de processo correspondente.

Processando classificações de artigos

Primeiramente, vamos lidar com a classificação dos artigos. Crie um arquivo chamado article_rate_process.php, copie nele o código da Listagem 29, e salve o arquivo no diretório-raiz do seu projeto.

Listagem 29. article_rate_process.php
<?php
require_once("classes/article.php");

$query_string = "";

$article = new Article;
if(isset($_POST['article_id'])) {
    $article->setId($_POST['article_id']);
    $query_string = '?id='.$_POST['article_id'];
}

if((strlen($_POST['vote']) == 0) || $_POST['vote'] == "0") {
    $msg = "You must select a rating from 1 to 5.";
    $msg_type = 2;
} else if(!isset($_POST['article_id'])) {
    $msg = "You can only rate when viewing an article.";
    $msg_type = 2;
} else {
    $rating_value = $_POST['vote'];
    $ip_address = $_SERVER['REMOTE_ADDR'];
    $success = $article->addRating($rating_value, $ip_address);
    if($success) {
        $msg = "Rating added successfully.";
        $msg_type = 1;
    } else {
        $msg = "Database error occurred.";
        $msg_type = 2;
    }
}

if(strlen($query_string) > 0) {
    $query_string .= "&msg=$msg&msg_type=$msg_type";
} else {
    $query_string = "?msg=$msg&msg_type=$msg_type";
}

header("Location: article_view.php".$query_string);
?>

Processando comentários novos dos artigos

A parte final do código lida com a adição de novos comentários à fila de moderação pendente. Crie um arquivo chamado article_comment_process.php, copie nele o código da Listagem 30, e salve o arquivo no diretório-raiz do seu projeto.

Listagem 30. article_comment_process.php
<?php
require_once("classes/article.php");

$query_string = "";

$article = new Article;
if(isset($_POST['article_id'])) {
    $article->setId($_POST['article_id']);
    $query_string = '?id='.$_POST['article_id'];
}

if(strlen($_POST['name']) == 0) {
    $msg = "Name is a mandatory field.";
    $msg_type = 2;
} else if(strlen($_POST['comments']) == 0) {
    $msg = "Comments is a mandatory field.";
    $msg_type = 2;
} else {
    $name = $_POST['name'];
    $comments = $_POST['comments'];
    $ip_address = $_SERVER['REMOTE_ADDR'];
    $success = $article->addComment($name, $comments, $ip_address);
    if($success) {
        $msg = "Your comment has been submitted and is pending approval.";
        $msg_type = 1;
    } else {
        $msg = "Database error occurred.";
        $msg_type = 2;
    }
}

if(strlen($query_string) > 0) {
    $query_string .= "&msg=$msg&msg_type=$msg_type";
} else {
    $query_string = "?msg=$msg&msg_type=$msg_type";
}

header("Location: article_view.php".$query_string);
?>

A Figura 13 mostra um exemplo da página View Article.

Figura 15. View Article
View Article page has description and article details plus rating and comment forms.

É isso! Você deve ter agora um aplicativo de Base de Conhecimento operacional, que pode ser usado em um departamento de suporte para armazenar a documentação sobre várias questões que são repetidamente levantadas pelos consumidores. Além disso, a Base de Conhecimento é construída de tal maneira que possa ser convertida em um blog, wiki, FAQ, sistema de gerenciamento de documentos ou outro tipo de aplicativo, sem que haja a necessidade do desenvolvimento de muito código adicional.

A próxima seção apresenta um resumo do tutorial, assim como algumas ideias de aprimoramentos em potencial que possam ser desenvolvidos para melhorar o aplicativo.


Resumo

Neste tutorial, você aprendeu a criar um aplicativo de Base de Conhecimento de Suporte em PHP suportado por um banco de dados IBM DB2 pureXML. Primeiramente, você criou tabelas de bancos de dados no DB2 que apresentavam um híbrido de colunas relacionais e XML. Em seguida, você aprendeu como criar uma classe de bancos de dados em PHP que abstraísse os comandos a serem enviados ao banco de dados. Depois você viu como criar as classes primárias do aplicativo de Base de Conhecimento, que lidam com a recuperação e manipulação dos dados de artigos, categorias, comentários e classificações. Mais adiante, você criou os componentes comuns da interface e então criou a interface administrativa, antes de finalmente desenvolver a interface do cliente.

Com as informações deste tutorial, você deve ter uma sólida base para que possa construir um aplicativo da Web híbrido (relacional-XML) usando o DB2 e PHP.

Melhorias sugeridas

Antes de implementar um aplicativo desse tipo, é desejável incorporar a autenticação e a autorização de usuários para proteger as páginas de administração contra o acesso de usuários não autorizados. Também seria preciso avaliar o código sob o ponto de vista da segurança para assegurar a proteção contra ataques de injeção de SQL e Cross Site Scripting.

Além disso, com um pouco mais de código seria bem fácil incorporar os seguintes aprimoramentos:

  • Permitir subcategorias.
  • Incluir tags nos artigos e nuvens de tags.
  • Permitir que os artigos sejam associados a mais de uma categoria.
  • Paginar os resultados de pesquisas e as listagens na página View Category.
  • Permitir que os administradores adicionem, editem e excluam comentários e classificações.
  • Permitir somente uma classificação por endereço IP por artigo.
  • Aprovar automaticamente comentários de usuários cujos comentários anteriores foram aprovados.
  • Distribuir conteúdo usando feeds RSS.
  • Incluir links de mídia social e compartilhamento (por exemplo, "Compartilhar no Twitter" ou "Compartilhar no Facebook")
  • Possibilitar o uso de conteúdo rico em artigos. Isso poderia ser incorporado com o uso de uma estrutura de edição de JavaScript WYSIWYG, como o TinyMCE ou o CKeditor.
  • Exibir artigos relacionados na página View Article.
  • Ativar a anexação de documentos e arquivos aos artigos.

O céu é o limite!


Download

DescriçãoNomeTamanho
Knowledge Base source codekbase.zip18KB

Recursos

Aprender

Obter produtos e tecnologias

  • Faça o download de uma versão de teste gratuita do DB2 para Linux, UNIX e Windows.
  • Agora você pode usar o DB2 gratuitamente. Faça o download do DB2 Express-C, uma versão sem custos do DB2 Express Edition que oferece os mesmos recursos centrais que o DB2 Express Edition e fornece uma base sólida para construir e implementar aplicativos.
  • Obtenha os binários mais recentes do Windows para PHP Point em http://windows.php.net/download/. Este artigo usou o PHP versão 5.2.11. Não utilize o PHP 5.3, pois ele não suporta as extensões PECL neste momento.
  • Faça o download da extensão DB2 para PHP.
  • Inove em seu próximo projeto de desenvolvimento em código aberto com o software de avaliação da IBM, disponível para download ou em DVD.

Discutir

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Information Management
ArticleID=455933
ArticleTitle=Construa uma Base de Conhecimento de Suporte usando DB2 pureXML e PHP
publish-date=12112009