Artigo na SQL Magazine

SQL Magazine 84

Já aconteceu a muito tempo, porém eu não havia compartilhado essa conquista aqui no blog. Um artigo meu entitulado “Implementação em MVC utilizando o Zend Framework” foi publicado na SQL Magazine edição 84. Para ver o conteúdo da revista clique aqui. Para acessar o artigo clique aqui.

Espero que nesse ano que virá eu consiga mais publicações :) .

Slides da palestra Desfrutando os Componentes do Zend Framework

Olá pessoal, tudo bem? Hoje às 10 horas apresentei na Latinoware a palestra “Desfrutando os Componentes do Zend Framework”. Apesar do atraso e de ter me decepcionado bastante com a organização do evento, no final tudo deu certo e espero que a palestra tenha sido boa. Críticas e sugestões são sempre bem-vindas, por favor podem enviar nos comentários.

Segue abaixo os slides da palestra:

Zend Framework Certified Engineer

Zend Framework Certified Engineer Olá pessoal, como havia dito em outro post, estava me preparando para a certificação de Zend Framework, e nesta sexta-feira 10 de Setembro de 2010, fiz a prova e obtive a certificação. Gostaria de agradecer a Deus, minha família e a todos que me apoiaram nessa jornada. Eu achei a prova um pouco mais difícil do que a de PHP5, talvez por não haver simulados, ou pelo Zend Framework ser bem extenso, mas no final tudo deu certo.

Para me preparar para a prova utilizei:

Espero que quem esteja interessado na certificação siga em frente e minha sugestão é que realmente estude e conheça os principais componentes exigidos, interfaces, constantes e os padrões de desenvolvimento com o framework. Um abraço e até a próxima.

Autorização com Zend Framework

Veremos aqui uma explicação detalhada dos componentes de Autorização do Zend Framework, demonstrando como restringir acesso a recursos de um sistema, baseando-se nos privilégios que um usuário autenticado possui. Para este artigo, é essencial o estudo do artigo sobre Autenticação com Zend Framework.

Conceitos

Autorização é o ato de verificar as permissões de um usuário já autenticado no sistema e, baseando-se nessas permissões, permitir ou bloquear o acesso deste usuário a determinados recursos da aplicação. Se, por exemplo, em um Sistema de Gestão de Conteúdo (CMS), o usuário logado é um escritor, ele poderia ter acesso à escrita de artigos, porém não poderia ter acesso ao cadastro de novos usuários, pode-se obter este tipo de funcionalidade por meio de um componente de autorização. No Zend Framework este componente é o Zend_Acl, que fornece a funcionalidade de Lista de Controle de Acesso (ACL) e gestão de privilégios. Em geral, uma aplicação pode usar esta funcionalidade para controlar o acesso a certos objetos protegidos, requeridos por outros objetos.

Existem alguns termos utilizados para melhor explanar as entidades envolvidas no controle de acesso, cada um deles é detalhado a seguir.

Papel (role)

Um papel corresponde a uma responsabilidade de um usuário dentro de um sistema como, por exemplo, o papel de “colunista” ou de “membro”. Isto é encapsulado através da classe Zend_Acl_Role, que é uma classe simples que apenas armazena o nome do papel. Existe também herança de papéis, onde ao se herdar de um papel, é possível fazer tudo que o papel pai faz, além das ações específicas do papel filho. Para se criar um papel e adicioná-lo  na lista de controle de acesso, o seguinte código é necessário:

$papelMembro = new Zend_Acl_Role('membro');
$acl = new Zend_Acl();
$acl->addRole($papelMembro);

Para utilizar a herança de papéis basta passar o nome do papel pai como segundo parâmetro do método addRole(). Para exemplificar isso o papel “colunista” irá herdar de “membro”, pois um colunista pode fazer tudo o que um membro pode, além de possuir permissões especiais para criar colunas. O código deste exemplo é apresentado abaixo:

$papelMembro = new Zend_Acl_Role('membro');
$papelColunista = new Zend_Acl_Role('colunista');
$acl = new Zend_Acl();
$acl->addRole($papelMembro);
$acl->addRole($papelColunista, 'membro');

Recurso (resource)

Um recurso é algo a ser protegido, o que pode ser um controlador ou uma ação. Um recurso é encapsulado pela classe Zend_Acl_Resource, que simplesmente armazena o nome do recurso que será protegido. Assim como no caso do Zend_Acl_Role, a classe Zend_Acl_Resource oferece suporte a herança, esta sendo definida no segundo parâmetro do método add() de Zend_Acl. Um exemplo de utilização de recursos é o caso de um sistema de ERP onde usuários do papel “administrador” podem ter acesso ao controlador manutencao, já usuários do papel “operador” podem ter acesso ao controlador produto. Para criar estes recursos o seguinte código é necessário:

$acl = new Zend_Acl();
$acl->addResource(new Zend_Acl_Resource('manutencao'));
$acl->addResource(new Zend_Acl_Resource('produto'));

Privilégio (privilege)

Um dado recurso pode ter diversas permissões a um determinado papel, estas permissões são, tipicamente, baseadas nas operações que serão executadas. Exemplos destas operações podem ser ações de um controlador, como, por exemplo: “adicionar” e “visualizar”. Este tipo de acesso exigido é facilmente configurado com dois métodos do Zend_Acl: allow() e deny(). Um exemplo permitindo e negando ações do controlador artigos ao papel membro é exibido a seguir:

$acl->allow('membro', 'artigos', 'visualizar');
$acl->deny('membro', 'artigos', 'adicionar');

Após permitir ou negar privilégios é possível verificar se um determinado papel tem acesso a um privilégio com o seguinte código:

if ($acl->isAllowed('member', 'artigos', 'visualizar') {
	echo "acesso permitido";
}

Estes são os principais conceitos e métodos relacionados aos componentes de autorização do Zend Framework, agora é hora de implementar um sistema de autorização integrado com o MVC do framework. Existem diversas formas de se implementar esta funcionalidade, a maneira demonstrada neste artigo é apenas uma delas.

Codificando a Autorização

Após uma introdução sobre os componentes de autorização do Zend Framework é hora de integrar as funcionalidades para aplicar a autorização ao MVC do framework. Antes de mais nada estou partindo do pressuposto de que o leitor possui todos os fontes do artigo Autenticação com Zend Framework, para reaproveitar o código desenvolvido neste artigo.

Banco de Dados e configurações iniciais

O banco de dados do artigo anterior sofreu algumas modificações, agora foi criada uma tabela para armazenar os perfis (papéis) suportados por essa aplicação. Dois perfis de exemplo são criados, o primeiro corresponde ao perfil de administrador, que pode ter acesso a todos os recursos protegidos e o segundo ao perfil de escritor, que pode apenas acessar o controlador de notícias. Um usuário para cada perfil também foi criado. O SQL do banco de dados é apresentado abaixo:

CREATE TABLE `perfil`(
 id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
 nome VARCHAR(30)
)ENGINE=InnoDB;
CREATE TABLE `usuario`(
 id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
 login VARCHAR(30) NOT NULL UNIQUE,
 senha VARCHAR(60) NOT NULL,
 nome_completo VARCHAR(100) NOT NULL,
 perfil_id INT UNSIGNED NOT NULL,
 FOREIGN KEY(`perfil_id`) REFERENCES `perfil`(`id`)
 ON UPDATE CASCADE
 ON DELETE CASCADE
)ENGINE=InnoDB;
INSERT INTO `perfil`(nome) VALUES ('admin'), ('writer');
INSERT INTO `usuario`(login, senha, nome_completo, perfil_id) VALUES ('admin', SHA1('admin'), 'Administrador', 1), ('escritor', SHA1('escritor'), 'Escritor', 2);

O arquivo de configurações application/configs/application.ini desenvolvido no artigo anterior não sofrerá alterações impactantes, algumas linhas são adicionadas para configurar as namespaces do autoloader do framework e incluir o plugin de autenticação, que cuidará da parte de verificar o usuário logado e suas permissões, implementado mais adiante. Estas linhas podem ser adicionadas logo acima da seção [staging : production]:

autoloaderNamespaces[] = "Aplicacao"
resources.frontController.plugins.auth = "Aplicacao_Plugin_Auth"

A próxima etapa é adicionar ao Bootstrap a inicialização da classe de ACL que irá popular todas as regras de autorização suportadas pela aplicação. Dentro do arquivo application/Bootstrap.php basta adicionar o seguinte método à classe Bootstrap:

protected function _initAcl()
{
	$aclSetup = new Aplicacao_Acl_Setup();
}

No artigo anterior foram criados os controladores: noticias e auth. Agora basta criar o controlador usuarios, que será acessível pelo papel admin, adicionar novas ações a este novo controlador e ao controlador noticias e, por último, adicionar uma nova ação ao controlador error.

zf create controller usuarios
zf create action adicionar noticias
zf create action adicionar usuarios
zf create action forbidden error

A ação forbidden ficará responsável por informar ao usuário que um determinado acesso não foi autorizado pela aplicação. Para exibir esta mensagem ao usuário basta adicionar o seguinte conteúdo ao arquivo views/scripts/error/forbidden.phtml:

<div style="color: #f00;">Voc&ecirc; n&atilde;o est&aacute; autorizado a ver esta p&aacute;gina</div>

Com isso a parte inicial da aplicação está pronta, agora é hora de implementar a classe responsável por popular o componente Zend_Acl.

Populando a ACL

A classe Bootstrap irá inicializar uma classe chamada Aplicacao_Acl_Setup, que ficará responsável por popular todos os dados relacionados a papéis, recursos e privilégios do componente Zend_Acl. Esta classe é apresentada abaixo e deve ser gravada no arquivo library/Aplicacao/Acl/Setup.php.

<?php
class Aplicacao_Acl_Setup
{
	/**
	 * @var Zend_Acl
	 */
	protected $_acl;

	public function __construct()
	{
		$this->_acl = new Zend_Acl();
		$this->_initialize();
	}

	protected function _initialize()
	{
		$this->_setupRoles();
		$this->_setupResources();
		$this->_setupPrivileges();
		$this->_saveAcl();
	}

	protected function _setupRoles()
	{
		$this->_acl->addRole( new Zend_Acl_Role('guest') );
		$this->_acl->addRole( new Zend_Acl_Role('writer'), 'guest' );
		$this->_acl->addRole( new Zend_Acl_Role('admin'), 'writer' );
	}

	protected function _setupResources()
	{
		$this->_acl->addResource( new Zend_Acl_Resource('auth') );
		$this->_acl->addResource( new Zend_Acl_Resource('error') );
		$this->_acl->addResource( new Zend_Acl_Resource('noticias') );
		$this->_acl->addResource( new Zend_Acl_Resource('usuarios') );
	}

	protected function _setupPrivileges()
	{
		$this->_acl->allow( 'guest', 'auth', array('index', 'login') )
				   ->allow( 'guest', 'error', array('error', 'forbidden') );
		$this->_acl->allow( 'writer', 'noticias', array('index', 'adicionar') )
				   ->allow( 'writer', 'auth', 'logout' );
		$this->_acl->allow( 'admin', 'usuarios', array('index', 'adicionar') );
	}

	protected function _saveAcl()
	{
		$registry = Zend_Registry::getInstance();
		$registry->set('acl', $this->_acl);
	}
}

A primeira etapa desta classe é configurar os papéis. Neste caso foram criados os papéis guest, writer e admin. O papel guest foi criado como um facilitador, já que não será utilizado. Pode ocorrer de a aplicação liberar determinados acessos a usuários não-logados, aí que entra este papel. O papel writer terá os mesmos acessos de guest, além de acessos específicos, assim como o papel admin terá os mesmos acessos que guest e writer, além de outras permissões.

Após ter os papéis definidos, são adicionados os recursos protegidos. Neste caso os recursos são os controladores auth, error, noticias e usuarios. A próxima etapa, é definir os privilégios de um determinado papel a um determinado recurso. O papel guest pode acessar ações de erro e a página de login, já o papel writer pode acessar as ações de guest, as ações do controlador de notícias e a ação de logout do controlador auth. Já o papel admin pode acessar o mesmo que os outros dois papéis, além das ações do controlador de usuários.

Por último esta ACL é adicionada ao Zend_Registry, para que em outras partes da aplicação ela esteja acessível e devidamente populada.

Verificação de permissões

Tendo a ACL devidamente configurada, agora será possível verificar as permissões do usuário. Para isso foi criado um plugin que é adicionado ao Zend_Controller_Front no arquivo de configuração definido na primeira etapa das implementações. Este plugin terá como pai a classe Zend_Controller_Plugin_Abstract que possui diversos métodos referentes a cada etapa do processo de despacho das classes do Zend Framework. O método utilizado no plugin é o preDispatch() que é chamado antes de uma ação de controlador ser despachada, o que permite re-configurar a rota caso o usuário não possa ter acesso à página em questão. Este plugin é exibido abaixo e deve estar gravado em library/Aplicacao/Plugin/Auth.php:

<?php
class Aplicacao_Plugin_Auth extends Zend_Controller_Plugin_Abstract
{
	/**
	 * @var Zend_Auth
	 */
	protected $_auth = null;
	/**
	 * @var Zend_Acl
	 */
	protected $_acl = null;
	/**
	 * @var array
	 */
	protected $_notLoggedRoute = array(
		'controller' => 'auth',
		'action'     => 'login',
		'module'     => 'default'
	);
	/**
	 * @var array
	 */
	protected $_forbiddenRoute = array(
		'controller' => 'error',
		'action'     => 'forbidden',
		'module'     => 'default'
	);

	public function __construct()
	{
		$this->_auth = Zend_Auth::getInstance();
		$this->_acl = Zend_Registry::get('acl');
	}

	public function preDispatch(Zend_Controller_Request_Abstract $request)
	{
		$controller = "";
		$action     = "";
		$module     = "";
		if ( !$this->_auth->hasIdentity() ) {
			$controller = $this->_notLoggedRoute['controller'];
			$action     = $this->_notLoggedRoute['action'];
			$module     = $this->_notLoggedRoute['module'];
		} else if ( !$this->_isAuthorized($request->getControllerName(),
					$request->getActionName()) ) {
			$controller = $this->_forbiddenRoute['controller'];
			$action     = $this->_forbiddenRoute['action'];
			$module     = $this->_forbiddenRoute['module'];
		} else {
			$controller = $request->getControllerName();
			$action     = $request->getActionName();
			$module     = $request->getModuleName();
		}
		$request->setControllerName($controller);
		$request->setActionName($action);
		$request->setModuleName($module);
	}

	protected function _isAuthorized($controller, $action)
	{
		$this->_acl = Zend_Registry::get('acl');
		$user = $this->_auth->getIdentity();
		if ( !$this->_acl->has( $controller ) || !$this->_acl->isAllowed( $user, $controller, $action ) )
			return false;
		return true;
	}
}

Primeiramente são definidas as rotas padrão para o caso do usuário não estar logado ou não estar autorizado a ver uma determinada página. Estas rotas são, consecutivamente: auth/login e error/forbidden. No construtor é recuperada a instância atual de Zend_Auth e Zend_Acl, ambas usadas no processo de verificação do usuário logado.

A primeira etapa do método preDispatch() é verificar se o usuário está logado e, caso não esteja, os parâmetros da requisição são configurados para a rota de login. A segunda etapa é o caso do usuário estar logado, nesta etapa o objeto que representa o usuário logado é recuperado e é feita uma verificação se este usuário possui o privilégio correspondente à ação do controlador requisitado. Caso não possua, ou o recurso que representa o controlador não exista, ele é redirecionado para a rota de acesso proibido. Se nenhuma destas verificações negar o acesso ao usuário significa que ele está apto a acessar a requisição em questão, portanto os parâmetros dessa requisição são mantidos e o fluxo prossegue.

Models

Tendo todo o processo de verificação pronto a próxima etapa é criar as classes com regras de negócio da parte de autenticação. Já que, diferente do artigo anterior, este artigo envolve o MVC do Zend Framework, toda a parte de regra de negócio é implementada na camada referente ao Model. A primeira classe criada é a classe que representa um usuário da aplicação. Ela implementará a interface Zend_Acl_Role_Interface, que permite que ela seja tratada como um papel do componente Zend_Acl. Esta classe exige que o método getRoleId() seja implementado e retorne o identificador correspondente ao papel em questão. Esta classe é bem simples e possui apenas métodos getters e setters. Ela deverá estar no arquivo models/Usuario.php.

<?php
class Model_Usuario implements Zend_Acl_Role_Interface
{
	private $_userName;
	private $_roleId;
	private $_fullName;

	public function getUserName()
	{
		return $this->_userName;
	}

	public function setUserName($userName)
	{
		$this->_userName = (string) $userName;
	}

	public function getFullName()
	{
		return $this->_fullName;
	}

	public function setFullName($fullName)
	{
		$this->_fullName = (string) $fullName;
	}
	/**
	 *
	 */
	public function getRoleId()
	{
		return $this->_roleId;
	}

	public function setRoleId($roleId)
	{
		$this->_roleId = (string) $roleId;
	}
}

A próxima etapa é implementar a classe que cuidará da regra de negócio de login. Esta classe terá apenas um método estático, responsável por fazer o login do usuário na aplicação. A principal diferença deste método de login com o apresentado no artigo anterior é a modificação da consulta ao banco de dados, para adicionar a tabela perfil aos dados retornados pelo Zend_Auth_Adapter_DbTable. O método join() de Zend_Db_Select, adicionará a tabela perfil com o alias “p”, onde a coluna perfil_id da tabela usuario seja igual a coluna id da tabela perfil, retornando a coluna  “nome” da tabela “perfil” com o alias “nome_perfil”. Após validar o login, o objeto retornado pelo adapter é mapeado para um objeto do tipo Model_Usuario e, por último, este é armazenado à sessão do Zend Framework. Este model ficará no arquivo models/Auth.php e deve possuir o seguinte conteúdo:

<?php
class Model_Auth
{
	public static function login($login, $senha)
	{
		$dbAdapter = Zend_Db_Table::getDefaultAdapter();
		//Inicia o adaptador Zend_Auth para banco de dados
		$authAdapter = new Zend_Auth_Adapter_DbTable($dbAdapter);
		$authAdapter->setTableName('usuario')
					->setIdentityColumn('login')
					->setCredentialColumn('senha')
					->setCredentialTreatment('SHA1(?)');
		//Define os dados para processar o login
		$authAdapter->setIdentity($login)
					->setCredential($senha);
		//Faz inner join dos dados do perfil no SELECT do Auth_Adapter
		$select = $authAdapter->getDbSelect();
		$select->join( array('p' => 'perfil'), 'p.id = usuario.perfil_id', array('nome_perfil' => 'nome') );
		//Efetua o login
		$auth = Zend_Auth::getInstance();
		$result = $auth->authenticate($authAdapter);
		//Verifica se o login foi efetuado com sucesso
		if ( $result->isValid() ) {
			//Recupera o objeto do usuário, sem a senha
			$info = $authAdapter->getResultRowObject(null, 'senha');

			$usuario = new Model_Usuario();
			$usuario->setFullName( $info->nome_completo );
			$usuario->setUserName( $info->login );
			$usuario->setRoleId( $info->nome_perfil );

			$storage = $auth->getStorage();
			$storage->write($usuario);

			return true;
		}
		throw new Exception('Nome de usuário ou senha inválida');
	}
}

Controller de Autenticação e demais Views

Agora que toda a regra de negócio foi delegada à camada Model, o controller de autenticação sofre algumas modificações para utilizar esta classe. Neste caso ele chamará o método estático Model_Auth::login(), e, caso este lance alguma exceção, irá armazenar a mensagem ao helper FlashMessenger e exibí-la em cima do formulário de login. A classe modificada é apresentada abaixo e, logo em seguida, a view correspondente a esta ação de login é apresentada. O formulário de login é o mesmo implementado no antigo anterior.

<?php
class AuthController extends Zend_Controller_Action
{

	public function init()
	{
	}

	public function indexAction()
	{
		return $this->_helper->redirector('login');
	}

	public function loginAction()
	{
		$this->_flashMessenger = $this->_helper->getHelper('FlashMessenger');
		$this->view->messages = $this->_flashMessenger->getMessages();
		$form = new Form_Login();
		$this->view->form = $form;
		//Verifica se existem dados de POST
		if ( $this->getRequest()->isPost() ) {
			$data = $this->getRequest()->getPost();
			//Formulário corretamente preenchido?
			if ( $form->isValid($data) ) {
				$login = $form->getValue('login');
				$senha = $form->getValue('senha');

				try {
					Model_Auth::login($login, $senha);
					//Redireciona para o Controller protegido
					return $this->_helper->redirector->goToRoute( array('controller' => 'noticias'), null, true);
				} catch (Exception $e) {
					//Dados inválidos
					$this->_helper->FlashMessenger($e->getMessage());
					$this->_redirect('/auth/login');
				}
			} else {
				//Formulário preenchido de forma incorreta
				$form->populate($data);
			}
		}
	}

	public function logoutAction()
	{
		$auth = Zend_Auth::getInstance();
		$auth->clearIdentity();
		return $this->_helper->redirector('index');
	}
}
<h2>Login</h2>
<?php echo ( sizeof( $this->messages ) > 0 ) ? '<div style="color: #f00;">' . $this->messages[0] . '</div>' : ""; ?>
<?php echo $this->form; ?>

A última etapa é modificar as views dos controladores noticias e usuarios. Estas views são extremamente simples e irão conter apenas uma mensagem de boas-vindas e um link para logout. O arquivo views/scripts/noticias/index.phtml deve conter o seguinte conteúdo:

<h2>Bem-vindo Escritor</h2>
<a href="<?php echo $this->url(array('controller' => 'auth', 'action' => 'logout', 'module' => 'default'), '', true); ?>">
	Logout
</a>

E, por último, o arquivo views/scripts/usuarios/index.phtml possui o seguinte conteúdo:


<h2>Bem-vindo Admin</h2>
<a href="<?php echo $this->url(array('controller' => 'auth', 'action' => 'logout', 'module' => 'default'), '', true); ?>">
	Logout
</a>

Funcionamento

Após todas as etapas estarem concluídas a aplicação está devidamente protegida, tanto na questão de permitir apenas usuários logados, como em autorizar aos usuários apenas alguns privilégios baseados em seu perfil. Para o meu caso a URL da aplicação ficou apontada para: http://localhost/zend_acl, e qualquer tentativa de acessar controllers internos são redirecionadas à pagina de login. Após o login,  a aplicação permite ou proíbe o acesso, de acordo com o perfil logado.  Abaixo são apresentadas algumas screenshots de cada parte funcional desta aplicação:

Erro ao efetuar login

Escritor logado com sucesso, acessando o controlador noticias

Tentativa de acesso ao controlador usuarios, utilizando o perfil escritor

Acesso ao controlador usuarios, usando o perfil admin

Conclusão

Os componentes do Zend Framework relacionados à autorização apresentam uma API consistente e intuitiva de se utilizar e configurar, o que garante uma baixa curva de aprendizagem para se utilizar as funcionalidades básicas do mesmo. Estes componentes podem ser integrados a todo o processo de despacho da parte MVC do framework através de plugins, o que permite manipular as requisições livremente e, baseado em determinados critérios, redirecionar o usuário para outros pontos da aplicação para informar mensagens de erro ou exigir algum formulário para entrada de dados. O objetivo deste artigo foi demonstrar de forma prática uma maneira de implementar autorização e integrá-la ao MVC, porém, vale ressaltar, que existem diversas formas de se chegar a esta funcionalidade, para maiores informações a seção de referências logo abaixo pode ser de grande valia. No próximo artigo os componentes de Internacionalização e Localização serão abordados, até a próxima pessoal!

Referências

Autenticação com Zend Framework

Neste artigo explico detalhadamente os componentes de Autenticação do Zend Framework demonstrando como restringir acesso a uma aplicação, e como, baseado nas credenciais de um usuário, verificar se ele está apto ou não a acessar os recursos protegidos da aplicação.

Conceitos

Autenticação é o ato de determinar se uma entidade é aquilo que ela diz ser, baseado em suas credenciais. Explicando de uma forma mais clara, é o ato de verificar determinados dados de um usuário e, caso ele realmente seja um usuário válido da aplicação, identificá-lo para que a aplicação saiba quem ele é. No Zend Framework esta funcionalidade é fornecida pelo componente Zend_Auth, que além de prover uma API para autenticação, também inclui adaptadores concretos de autenticação para cenários comumente encontrados.

Um adaptador Zend_Auth é utilizado para fazer a autenticação em um serviço de autenticação específico como, por exemplo, LDAP, banco de dados e arquivos físicos. Cada adaptador terá certas particularidades de comportamentos e opções, porém algumas características básicas serão encontradas em todos os adaptadores, por exemplo aceitar credenciais de acesso, efetuar consultas no serviço de autenticação e retornar resultados. Os adaptadores implementam a interface Zend_Auth_Adapter_Interface, esta definindo o método authenticate(), que cuida da consulta utilizada na autenticação, que deverá ser implementado em cada um dos adaptadores. Para a utilização deste método, cada adaptador deverá estar devidamente configurado com os dados necessários na autenticação, como as opções específicas do adaptador e o usuário e senha que deverão ser utilizados.

Uma tentativa de autenticação utilizando o método authenticate() de um adaptador Zend_Auth irá retornar como resultado uma instância da classe Zend_Auth_Result devidamente populada com informações para validar o retorno da autenticação. Esta classe possui os seguintes métodos:

  • isValid() – Retorna true se e somente se o resultado representa uma tentativa de autenticação bem-sucedida;
  • getCode() – Retorna uma constante da classe Zend_Auth_Result para determinar que tipo de falha de autenticação ou sucesso que ocorreu. Pode ser usado em situações onde o desenvolvedor necessita saber o tipo de resultado ocorrido para proceder de alguma forma específica;
  • getIdentity() – Retorna a identidade da tentativa de autenticação, pode ser o nome de usuário ou alguma outra característica definida como identidade;
  • getMessages() – Retorna um array de mensagens sobre uma tentativade autenticação mal-sucedida.

As constantes que podem ser retornadas no método getCode() são listadas abaixo:

Zend_Auth_Result::SUCCESS //Sucesso na  autenticação
Zend_Auth_Result::FAILURE //Falha na autenticação
Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND  //Falha, a identidade é inválida
Zend_Auth_Result::FAILURE_IDENTITY_AMBIGUOUS  //Falha, a identidade é ambígua
Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID  //Falha, a senha é inválida
Zend_Auth_Result::FAILURE_UNCATEGORIZED  //Falha não categorizada

Efetuar corretamente uma autenticação que inclui suas devidas credenciais é útil por si só, mas também é importante manter a autenticação feita para não haver a necessidade de solicitar as credenciais de autenticação a cada requisição. O HTTP é um protocolo stateless, porém técnicas como cookies e sessões foram criadas para facilitar o mantimento de um determinado estado durante diversas requisições às aplicações. Por padrão o componente Zend_Auth armazena uma tentativa bem-sucedida de autenticação na sessão do PHP.  Ele utiliza uma classe de armazenamento chamada Zend_Auth_Storage_Session que utiliza a classe Zend_Session para manipular as sessões. Também pode-se utilizar para armazenamento uma classe customizada, para isso basta prover um objeto que implemente a classe Zend_Auth_Storage_Interface para o método Zend_Auth::setStorage().

Codificando a Autenticação

Após explicar detalhadamente os conceitos que envolvem autenticação é hora de codificar um exemplo simples que fará uso dos componentes do Zend Framework. Este exemplo precisará de uma tabela no banco de dados onde serão armazenados os usuários do sistema. Esta tabela está descrita a seguir:

CREATE TABLE `usuario`(
	id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
	login VARCHAR(30) NOT NULL UNIQUE,
	senha VARCHAR(60) NOT NULL,
	nome_completo VARCHAR(100) NOT NULL
);

É inserido então um usuário de exemplo que estará apto a se autenticar no sistema:

INSERT INTO `usuario` VALUES ('', 'admin', SHA1('admin'), 'Administrador');

É possível notar que é uma tabela extremamente simples, porém essa estrutura já é o suficiente para utilizar no exemplo desenvolvido. Para este exemplo foi criado um projeto padrão do Zend Framework com o seguinte comando:

zf create project zend_auth

Este projeto conterá um controlador de notícias, que seria um recurso apenas disponível para usuários autenticados no sistema, e outro controlador de autenticação, utilizado para fazer login e logout da aplicação. Para criar estes controladores e as ações de autenticação os seguintes comandos são utilizados:

cd zend_auth
zf create controller noticias
zf create controller auth
zf create action login Auth
zf create action logout Auth

O próximo passo é configurar o adaptador do banco de dados MySQL, e configurar o controlador de notícias como o controlador padrão da aplicação. No arquivo application.ini localizado na pasta application/configs basta acrescentar o seguinte conteúdo:

; Faz com que o controlador de notícias seja o controlador padrão
resources.frontController.defaultControllerName = "noticias"
; Banco de dados
resources.db.adapter = "PDO_MYSQL"
resources.db.params.host = "localhost"
resources.db.params.username = "root"
resources.db.params.password = ""
resources.db.params.dbname = "zend_auth"

Agora o último passo é configurar o Autoload de classes do Zend Framework. Para isso basta abrir o arquivo application/Bootstrap.php e adicionar o seguinte método na classe:

protected function _initAutoload()
{
	$autoloader = new Zend_Application_Module_Autoloader(array(
			'basePath' => APPLICATION_PATH,
			'namespace' => ''
	));
	return $autoloader;
}

Formulário de Login

Todo sistema que precisa de autenticação fornece um formulário onde o usuário pode informar suas credenciais para se identificar no sistema. Para criar o formulário de login pode-se utilizar o componente Zend_Form, colocando os campos de login e senha. Para isto, primeiramente basta executar o seguinte comando:

zf create form login

Com isso um arquivo nomeado Login.php poderá ser encontrado dentro de application/forms. Para criar o formulário basta colocar o seguinte conteúdo neste arquivo:

<?php

class Form_Login extends Zend_Form
{
	public function init()
	{
		$this->setName('login');

		$login = new Zend_Form_Element_Text('login');
		$login->setLabel('Login:')
			  ->setRequired(true)
			  ->addFilter('StripTags')
			  ->addFilter('StringTrim')
			  ->addValidator('NotEmpty');

		$senha = new Zend_Form_Element_Password('senha');
		$senha->setLabel('Senha:')
			  ->setRequired(true)
			  ->addFilter('StripTags')
			  ->addFilter('StringTrim')
			  ->addValidator('NotEmpty');

		$submit = new Zend_Form_Element_Submit('submit');
		$submit->setLabel('Logar')
			   ->setAttrib('id', 'submitbutton');

		$this->addElements(array($login, $senha, $submit));
	}
}

Neste código são criados os campos login e senha, onde ambos possuirão filtros que farão uma limpeza nos dados fornecidos pelo usuário, tirando espaços vazios e tags HTML, e uma validação de campos obrigatórios. Após criar estes elementos é criado o botão de envio e, por último, todos os elementos são adicionados ao corpo do formulário. Estudar mais afundo o componente Zend_Form foge do escopo deste artigo, só este componente pode ser assunto de vários outros artigos.

Ação de Login

Após criar o formulário é hora de implementar a ação de login. O seguinte código ficará encarregado de efetuar o login na aplicação:

public function loginAction()
{
	$this->_flashMessenger = $this->_helper->getHelper('FlashMessenger');
	$this->view->messages = $this->_flashMessenger->getMessages();
	$form = new Form_Login();
	$this->view->form = $form;
	//Verifica se existem dados de POST
	if ( $this->getRequest()->isPost() ) {
		$data = $this->getRequest()->getPost();
		//Formulário corretamente preenchido?
		if ( $form->isValid($data) ) {
			$login = $form->getValue('login');
			$senha = $form->getValue('senha');

			$dbAdapter = Zend_Db_Table::getDefaultAdapter();
			//Inicia o adaptador Zend_Auth para banco de dados
			$authAdapter = new Zend_Auth_Adapter_DbTable($dbAdapter);
			$authAdapter->setTableName('usuario')
						->setIdentityColumn('login')
						->setCredentialColumn('senha')
						->setCredentialTreatment('SHA1(?)');
			//Define os dados para processar o login
			$authAdapter->setIdentity($login)
						->setCredential($senha);
			//Efetua o login
			$auth = Zend_Auth::getInstance();
			$result = $auth->authenticate($authAdapter);
			//Verifica se o login foi efetuado com sucesso
			if ( $result->isValid() ) {
				//Armazena os dados do usuário em sessão, apenas desconsiderando
				//a senha do usuário
				$info = $authAdapter->getResultRowObject(null, 'senha');
				$storage = $auth->getStorage();
				$storage->write($info);
				//Redireciona para o Controller protegido
				return $this->_helper->redirector->goToRoute( array('controller' => 'noticias'), null, true);
			} else {
				//Dados inválidos
				$this->_helper->FlashMessenger('Usuário ou senha inválidos!');
				$this->_redirect('/auth/login');
			}
		} else {
			//Formulário preenchido de forma incorreta
			$form->populate($data);
		}
	}
}

Inicialmente é necessário instanciar o formulário de login e passá-lo para a camada View, para exibí-lo ao usuário. Caso este formulário seja enviado para processamento o método $this->getRequest()->isPost() irá retornar true, e então toda a lógica para efetuar o login será executada. Primeiramente os dados do formulário são recuperados através do método $this->getRequest()->getPost(), então é feita uma verificação se os dados foram preenchidos corretamente, caso eles não foram preenchidos, ele não entrará na lógica de login e executará o trecho:

} else {
	//Formulário preenchido de forma incorreta
	$form->populate($data);
}

Que basicamente irá preencher o formulário com os dados já enviados, para o usuário não precisar reinformá-los, permitindo exibí-los na View. No caso do formulário ser preenchido corretamente a lógica de autenticação é executada, onde são recuperados os valores preenchidos do formulário através do método getValue(atributo), e finalmente os componentes do Zend Framework entram em ação, primeiramente é recuperado o adaptador de banco de dados sendo executado pela aplicação e, então ele é passado para a classe Zend_Auth_Adapter_DbTable, que, após ser instanciada, é configurada para utilizar a tabela usuario do banco de dados, utilizando como Identidade a coluna login, e como Credencial a coluna senha, e como Tratamento de senha o formato de criptografia SHA1.

Após configurar o adaptador os dados informados pelo usuário são passados para o mesmo, em seguida o componente Zend_Auth é iniciado, e é efetuada uma tentativa de autenticação. Caso o resultado seja válido, é armazenado um objeto com os dados do usuário em sessão, excluindo apenas sua senha por questões de segurança, e o usuário é redirecionado para o controlador de notícias.  Caso o resultado seja inválido, é criada uma mensagem de erro através do componente FlashMessenger, que é instanciado no início desta action passando as possíveis mensagens armazenadas para a camada View, e então o usuário é redirecionado para o formulário de login, que conterá uma mensagem informando sobre o erro encontrado.

Agora a última etapa é codificar a View login.phtml, localizada na pasta application/views/scripts/auth, para exibir o formulário e as possíveis mensagens de erro. O código deste arquivo é apresentado a seguir:

<h2>Login</h2>
<?php echo ( sizeof( $this->messages ) > 0 ) ? $this->messages[0] : ""; ?>
<?php echo $this->form; ?>

Este arquivo é extremamente simples, ele terá uma verificação se existem mensagens de erro para exibir ao usuário, caso existam ele irá imprimí-las, e irá exibir o formulário de login. Como o componente Zend_Form, encapsula toda a validação, não é necessário mais nenhum código. Agora para testar basta apontar o browser para o projeto, no meu caso estou utilizando a seguinte url: http://zend_auth/auth/login. A seguinte imagem deverá ser exibida:

Formulário de Login

Agora a última etapa é configurar a ação indexAction para redirecionar para a ação de login. Para isso basta deixá-la da seguinte forma:

public function indexAction()
{
	return $this->_helper->redirector('login');
}

Logout

Efetuar logout com os componentes do Zend Framework é uma etapa extremamente simples. Na classe AuthController foi criada a ação logoutAction, basta alterá-la para que ela fique da seguinte forma:

public function logoutAction()
{
	$auth = Zend_Auth::getInstance();
	$auth->clearIdentity();
	return $this->_helper->redirector('index');
}

Basicamente é recuperada a instância do Zend_Auth e é feita uma limpeza dos dados do usuário logado, após isso o usuário é redirecionado ao formulário de login, para que ele realmente saiba que agora ele não está mais autenticado no sistema.

Controlador de notícias

O Controlador de notícias será o ponto de verificação de todo o processo de login. Neste exemplo ele é uma área restrita, portanto é necessário protegê-lo do acesso de usuários não-autenticados. O método init() é acessado antes de qualquer ação de um controlador, portanto ele é o ponto ideal para fazer a proteção do controlador. Para obter esta proteção basta modificar este método, no arquivo NoticiasController.php, deixando-o com o seguinte conteúdo:

public function init()
{
	if ( !Zend_Auth::getInstance()->hasIdentity() ) {
		return $this->_helper->redirector->goToRoute( array('controller' => 'auth'), null, true);
	}
}

Mais um código extremamente simples, ele irá verificar se existe alguma identidade autenticada no sistema, e, caso não exista, o usuário será redirecionado para o formulário de login.

Agora no método indexAction() será feita uma breve codificação para que a sua View possa informar os dados do usuário, junto com um link para efetuar o logout da aplicação. Este código fica da seguinte forma:

public function indexAction()
{
	$usuario = Zend_Auth::getInstance()->getIdentity();
	$this->view->usuario = $usuario;
}

Ele recupera a identidade armazenada na instância do Zend_Auth e passa-a para a camada de apresentação. A View desta ação está localizada em application/views/scripts/noticias/index.phtml e o seguinte conteúdo deve ser colocado nela:

<h2>Usu&aacute;rio logado: <?php echo $this->usuario->nome_completo; ?></h2>
<p><a href="<?php echo $this->url(array('controller' => 'auth', 'action' => 'logout'), null, true); ?>">Efetuar logout</a></p>

Esse código irá mostrar o nome do usuário logado e logo abaixo um link para efetuar logout do sistema. Após clicar neste link, pode-se verificar se o logout foi feito com sucesso tentando acessar novamente o controlador de notícias. O resultado será um redirecionamento para o formulário de login, o que garante que a aplicação está devidamente protegida.

Conclusão

Os componentes do Zend Framework são extremamente simples de se utilizar além de fornecerem uma enorme flexibilidade de extensão. Pode-se criar adaptadores customizados para o Zend_Auth, o que permite estender suas funcionalidades de acordo com cada cenário, tudo isso com um código reaproveitável e bem refinado. Neste artigo procurei abordar os conceitos que envolvem os componentes de autenticação, com um exemplo simples utilizando eles, de acordo com cada situação este exemplo precisará de uma boa refatoração para melhorar o fluxo e o reaproveitamento de código. Para isso recomendo a leitura do manual do Zend Framework, encontrado logo abaixo nas referências do artigo.

Referências

Integrando o Doctrine com o Zend Framework

Neste artigo demonstro uma maneira de integrar o framework de mapeamento objeto relacional Doctrine com o Zend Framework. Esta integração pode ser útil em projetos que necessitam de uma ferramento mais consistente mara fazer o mapeamento objeto relacional, tendo em vista que o Zend_Db ainda não possui boas funcionalidades para este objetivo. O Doctrine também se mostra consistente na gestão de memória, liberando recursos conforme eles não são mais necessários, além de outros recursos interessantes para as aplicações.

Introdução ao Doctrine

O Doctrine é uma ferramenta de mapeamento objeto-relacional (ORM) para PHP 5.2.3+. Um de seus principais recursos é a opção de se escrever consultas de bancos de dados em um dialeto SQL proprietário orientado a objetos chamado Doctrine Query Language (DQL), que foi inspirada na Hibernate Query Language (HQL) do framework Hibernate para Java. Isso fornece aos desenvolvedores uma alternativa poderosa ao SQL que mantém a flexibilidade sem duplicações desnecessárias de código.

Os Models gerados pelo Doctrine extendem da classe Doctrine Record, onde todos eles herdam métodos típicos de um Active Record, como os métodos save, delete, etc, além de permitir que o Doctrine participe e monitore o ciclo de vida dos registros. Outros componentes do Doctrine fazem os trabalhos mais pesados, como por exemplo a classe Doctrine_Table. Esta classe possui os métodos típicos de um Data Mapper, como, por exemplo, createQuery, find, findAll, findBy*, etc.

No Doctrine as consultas são baseadas em classes ao invés de tabelas, para demonstrar um exemplo, considere a seguinte consulta:

FROM Usuario
LEFT JOIN u.Enderecos WHERE u.id = 1

Com ela pode-se notar o seguinte:

  • É feita uma seleção do Model Usuario e não da tabela;
  • É possível referenciar os campos do Model;
  • São feitos joins com associações;
  • Não existe condição de join, as associações entre as classes e como elas estão no banco de dados são conhecidas pelo Doctrine.

Estas informações foram traduzidas do Manual Oficial do Doctrine, com algumas modificações para clarear o entendimento.

Definição do Projeto com o Zend Framework

Agora que já foi feita uma breve explicação sobre o Doctrine é hora de gerar um projeto de exemplo com o Zend Framework. O projeto criado será bem simples, apenas para exemplificar algumas operações básicas com o Doctrine. Neste ponto presumo que você leitor tenha o Zend Framework e sua ferramenta de linha de comando configurados em seu sistema operacional. Caso não tenha feito isso, recomendo o tutorial do Adler Medrado, onde ele ensina como fazer estas configurações.

O primeiro passo é criar a estrutura do projeto com o comando:

zf create project zf_doctrine

Caso nenhum erro ocorra este comando gerará a seguinte estrutura:

Estrutura básica de um projeto Zend Framework

A pasta application será onde o código específico da aplicação será armazenado. Este código inclui as configurações da aplicação para ambiente de desenvolvimento, produção ou demais ambientes configurados; os Controllers responsáveis por intermediar o fluxo entre as Views, que cuidam da apresentação ao usuário, e os Models responsáveis pela lógica de negócio da aplicação.

A pasta docs é utilizada para armazenar as documentações da aplicação e dos componentes. library é onde ficam armazenados os plugins, componentes e demais bibliotecas de terceiros. Ela é utilizada para armazenar o Doctrine e toda estrutura de classes do Zend Framework. A pasta public é a pasta acessível pelo servidor web, onde é feita toda a chamada para inicializar a aplicação, seguindo o padrão Front Controller. E a pasta testes é onde são armazenados os testes unitários.

Integração

A primeira coisa a se fazer é o download do framework Doctrine. Na data em que este artigo foi escrito a última versão estável do framework é a 1.2.1. Você pode baixá-la neste link. Após ter efetuado o download e extraído o pacote, é necessário copiar todo o conteúdo da pasta lib para a pasta library do projeto Zend Framework criado, resultando na seguinte estrutura:

Estrutura com o Doctrine extraído

Após isso é necessário criar a pasta db e dentro dela as seguintes pastas:

  • fixtures – Pasta onde armazenará arquivos de definição de dados para popular o banco de dados;
  • migrations – Arquivos de migração entre diferentes versões de banco de dados;
  • schema – Definição de toda estrutura do banco de dados, seguindo o formato YAML;
  • sql – Armazena a estrutura SQL que define o banco de dados.

Depois de criar estas pastas, também pode ser criada a pasta para armazenar os scripts de linha de comando do Doctrine. Dentro de application é criada uma pasta com o nome de scripts. Após criar todas estas pastas a estrutura será a seguinte:

Estrutura final de diretórios

Após toda a estrutura estar definida é hora de começar a configurar o projeto. A primeira coisa a se fazer é, abrir o arquivo application/configs/application.ini, e acrescentar o seguinte conteúdo nele, na seção [production]:

autoloaderNamespaces[] = "Doctrine"
doctrine.dsn = "mysql://usuario:senha@host/banco"
doctrine.models_path = APPLICATION_PATH "/models"
doctrine.data_fixtures_path = APPLICATION_PATH "/../db/fixtures"
doctrine.migrations_path = APPLICATION_PATH "/../db/migrations"
doctrine.sql_path = APPLICATION_PATH "/../db/sql"
doctrine.yaml_schema_path = APPLICATION_PATH "/../db/schema"
; Conservative Model Loading
doctrine.model_autoloading = 2

Nestas linhas é definido o Namespace utilizado pelo Doctrine para o autoLoader carregar as classes automaticamente, logo em seguida é definido um Nome de Fonte de Dados (Data Source Name – DSN) para a localização do banco de dados, em seguida são definidas as pastas onde são armazenados os models, as fixtures, a sql, e o schema, e, por último é definida a forma como o Doctrine carregará os Models, neste caso é utilizado o Conservative, que carrega os Models conforme eles sejam necessários.

Logo abaixo da linha doctrine.model_autoloading é necessário definir algumas configurações para a ferramenta de linha de comando do Doctrine (Doctrine Cli), basta colocar o seguinte conteúdo:

[doctrineCLI : production]
doctrine.generate_models_options.pearStyle = true
doctrine.generate_models_options.generateTableClasses = false
doctrine.generate_models_options.generateBaseClasses = true
doctrine.generate_models_options.baseClassPrefix = "Base_"
doctrine.generate_models_options.baseClassesDirectory =
doctrine.generate_models_options.classPrefixFiles = false
doctrine.generate_models_options.classPrefix = "Model_"
; Agressive Model Loading
doctrine.model_autoloading = 1

Neste ambiente é necessário configurar as formas como os Models serão gerados. A primeira linha configura a nomenclatura no formato PEAR, onde o nome da classe contém como prefixos a estrutura de diretório onde ela está localizada, por exemplo, Model_Produto, estará na pasta model/Produto.php, no caso da nomenclatura do Zend Framework, a pasta models terá como prefixo Model_, seguindo o padrão do Autoloader do framework. A segunda linha indica ao Doctrine CLI para não gerar as classes Table automaticamente. A terceira linha configura o Doctrine CLI para que gere classes base, onde a quarta linha configura o prefixo das classes Base e a linha seguinte define um diretório vazio, fazendo com que as classes geradas sejam armazenadas no diretório models/base ao invés de models/Model/Base. A próxima linha indica que os nomes de arquivos não devem conter prefixos, a linha seguinte indica ao prefixo das classes geradas, que deverá ser Model_, como explicado. E a última linha define que a forma de carregar os Models será Agressive, ou seja, eles são carregados na inicialização do Doctrine.

Após definir toda essa estrutura é  hora de configurar a inicialização do Zend Framework para que ele carregue e configure corretamente o Doctrine e demais classes necessárias pela aplicação. Para isso, basta abrir o arquivo application/Bootstrap.php, e colocar o seguinte conteúdo nele:

<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
	protected function _initAutoload()
	{
		$autoloader = new Zend_Application_Module_Autoloader(array(
			'basePath' => APPLICATION_PATH,
			'namespace' => ''
		));
		return $autoloader;
	}
	protected function _initDoctrine()
	{
		$this->getApplication()->getAutoloader()
								->pushAutoloader(array('Doctrine', 'autoload'));
		spl_autoload_register(array('Doctrine', 'modelsAutoload'));

		$doctrineConfig = $this->getOption('doctrine');
		$manager = Doctrine_Manager::getInstance();
		$manager->setAttribute(Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE, true);
		$manager->setAttribute(
			Doctrine::ATTR_MODEL_LOADING,
			$doctrineConfig['model_autoloading']
		);
		Doctrine::loadModels($doctrineConfig['models_path']);

		$conn = Doctrine_Manager::connection($doctrineConfig['dsn'], 'doctrine');
		$conn->setAttribute(Doctrine::ATTR_USE_NATIVE_ENUM, true);
		return $conn;
	}
}

O primeiro método configura o Autoloader do Zend Framework, para que ele cuide do carregamento das classes necessárias na aplicação. O segundo método configura o Doctrine. Primeiramente ele adiciona o Autoloader do Doctrine ao Autoloader do Zend Framework, permitindo ao Doctrine carregar as classes específicas de seus componentes. A próxima linha configura o Autoloader de Models do Doctrine, para que o Doctrine também cuide do carregamento de seus Models, conforme for necessário na aplicação. As linhas seguintes cuidam de:

  • Pegar as configurações definidas no arquivo application.ini;
  • Definir os atributos do Doctrine, onde o primeiro atributo definido é a sobrescrita de acessores e o segundo é a forma como os Models são carregados;
  • Carregar os Models, conforme a forma definida na linha anterior;
  • Gerar uma conexão ao banco de dados, baseada na dsn definida no arquivo de configuração;
  • Utilizar a forma nativa de ENUM.

Com isso o arquivo de inicialização do Zend Framework está devidamente configurado. A última etapa é criar a ferramenta de linha de comando do Doctrine. Para fazer isso basta criar o arquivo application/scripts/doctrine.php e colocar o seguinte conteúdo nele:

<?php
//Caminho para a pasta da aplicação
defined('APPLICATION_PATH')
    || define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/..'));
//Ambiente em que a aplicação será executada
defined('APPLICATION_ENV')
    || define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'doctrineCLI'));

//Adiciona a "library" no include_path
set_include_path(implode(PATH_SEPARATOR, array(
    realpath(APPLICATION_PATH . '/../library'),
    get_include_path()
)));

require_once 'Zend/Application.php';
//Cria a aplicação, faz o bootstrap e a executa
$application = new Zend_Application(
    APPLICATION_ENV,
    APPLICATION_PATH . '/configs/application.ini'
);

$application->getBootstrap()->bootstrap('autoload');
$application->getBootstrap()->bootstrap('doctrine');

$config = $application->getOption('doctrine');

$cli = new Doctrine_Cli($config);
$cli->run($_SERVER['argv']);

Este arquivo irá definir a pasta da aplicação, o ambiente doctrineCLI, para pegar as configurações específicas da ferramenta de linha de comando, conforme definido no arquivo application.ini, adicionar a biblioteca de componentes no include_path, inicializar a aplicação, assim como o Autoload do Zend Framework e o Doctrine, pegar as configurações definidas em application.ini, e, finalmente chamar o Doctrine CLI para cuidar de todo o resto. Por último é necessário criar o arquivo executável responsável por chamar esta ferramenta definida. Para isso basta criar o arquivo application/scripts/doctrine e colocar o seguinte conteúdo nele:

#!/usr/bin/env /usr/bin/php
<?php
chdir(dirname(__FILE__));
include('doctrine.php');

O próximo passo é dar permissão de execução a esse arquivo:

$ chmod a+x zf_doctrine/application/scripts/doctrine

Testando

Antes de mais nada, a primeira coisa a se fazer é testar o cliente de linha de comando. Para isso, em um terminal, execute o seguinte comando:

$ zf_doctrine/application/scripts/doctrine

A saída deste comando deverá ser a seguinte:

Doctrine Command Line Interface

doctrine.php build-all
doctrine.php build-all-load
doctrine.php build-all-reload
doctrine.php compile
doctrine.php create-db
doctrine.php create-tables
doctrine.php dql
doctrine.php drop-db
doctrine.php dump-data
doctrine.php generate-migration
doctrine.php generate-migrations-db
doctrine.php generate-migrations-diff
doctrine.php generate-migrations-models
doctrine.php generate-models-db
doctrine.php generate-models-yaml
doctrine.php generate-sql
doctrine.php generate-yaml-db
doctrine.php generate-yaml-models
doctrine.php load-data
doctrine.php migrate
doctrine.php rebuild-db

Se preferir, adicione esta ferramenta no include_path de sua aplicação, seguindo a mesma idéia explanada pelo Adler Medrado. Caso o comando retorne a saída demonstrada acima, a ferramenta da linha de comando deverá estar funcionando corretamente. Agora é hora de criar um banco de dados chamado carros_doctrine. Após criá-lo, crie o arquivo db/schema/schema.yml, e coloque o seguinte conteúdo nele:

Carro:
  connection: 0
  tableName: carro
  columns:
    id:
      type: integer(4)
      fixed: false
      unsigned: true
      primary: true
      autoincrement: true
    nome:
      type: string(150)
      fixed: false
      unsigned: false
      primary: false
      notnull: true
      autoincrement: false
    cor:
      type: string(150)
      fixed: false
      unsigned: false
      primary: false
      notnull: true
      autoincrement: false

Neste arquivo é feita a definição de uma tabela chamada “Carro” para o banco de dados, onde esta tabela terá as colunas:

  • id – Que será um campo do tipo Inteiro, chave primária, não aceitará valores nulos e nem valores negativos (unsigned)  e será um campo auto-incremental;
  • nome – Será um campo do tipo String, com o tamanho de 150 caracteres e  que não pode ser nulo;
  • cor – Será um campo do tipo String, com o tamanho de 150 caracteres e  que não pode ser nulo.

Após criar o banco e definir este arquivo, basta executar a ferramenta de linha de comando para que ela gere o banco de dados e os Models. Para isso basta executar o seguinte comando:

$ zf_doctrine/application/scripts/doctrine build-all-reload
build-all-reload - Are you sure you wish to drop your databases? (y/n)
y
build-all-reload - Successfully dropped database for connection named 'doctrine'

build-all-reload - Generated models successfully from YAML schema
build-all-reload - Successfully created database for connection named 'doctrine'

build-all-reload - Created tables successfully
build-all-reload - Data was successfully loaded

Após executar este comando basta verificar se a pasta models está devidamente preenchida com o arquivo Carro.php e Base/Carro.php.

Agora, caro queira gerar o SQL da aplicação, basta executar o seguinte comando:

$ zf_doctrine/application/scripts/doctrine generate-sql
generate-sql - Generated SQL successfully for models

Após isso, o arquivo db/sql/schema.sql será criado e conterá a SQL do banco de dados, como o trecho a seguir:

CREATE TABLE carro (
	id INT UNSIGNED AUTO_INCREMENT,
	nome VARCHAR(150) NOT NULL,
	cor VARCHAR(150) NOT NULL,
	PRIMARY KEY(id)
) ENGINE = INNODB;

Agora, o último teste para verificar se tudo está corretamente integrado é fazer algumas chamadas no Controller e verificar se tudo é executado corretamente. Dentro do arquivo application/controllers/IndexController.php, existe o método indexAction(), basta definí-lo como a seguir:

public function indexAction()
{
	$carro = new Model_Carro();
	$carro->nome = "Ferrari";
	$carro->cor = "Vermelha";
	$carro->save();

	$query = new Doctrine_Query();
	$query->from('Model_Carro c');
	$query->orderBy('c.id DESC');
	$this->view->carros = $query->execute();
}

Neste método é criado um novo carro com o nome “Ferrari” e a cor “Vermelha”, e então é inserido este carro no banco de dados. Após isso uma consulta de todos os carros do banco de dados é executada e, então, esta consulta é passada para a View, onde é feita uma listagem destes carros e apresentado ao usuário.

Para fazer esta listagem altere o arquivo application/views/scripts/index/index.phtml e deixe-o com o seguinte conteúdo:

<?php foreach ( $this->carros as $carro ): ?>
<h1>Carro #<?php echo $carro->id; ?></h1>
<p>Nome: <?php echo $carro->nome; ?></p>
<p>Cor: <?php echo $carro->cor; ?></p>
<hr />
<?php endforeach; ?>

Nele é feito um laço para percorrer todos os carros imprimindo os dados de cada um ao usuário. Agora basta ir no browser e acessar o projeto, que o seguinte conteúdo será exibido:

Listagem dos Carros

Caso todo este processo ocorra sem erros o Doctrine está devidamente integrado ao Zend Framework.

Conclusão

O Doctrine é uma ferramenta robusta e consistente para mapeamento objeto relacional, e unindo todas as suas funcionalidades com os componentes do Zend Framework é possível obter uma arquitetura altamente consistente e com ferramentas de alto nível. Apesar de ser uma solução minimalista, a integração se mostra devidamente eficaz, provendo todas as funcionalidades das ferramentas de linha de comando e dos componentes de ambos os frameworks. Existem diversas formas de se integrar o Doctrine com o Zend Framework, esta pode não ser a melhor, porém funciona sem maiores problemas. Logo abaixo coloco alguns links com outras formas de integração assim como o screencast que utilizei como base para elaborar este artigo. Até a próxima.

Código-Fonte

O link abaixo é o pacote com todo o código-fonte do projeto desenvolvido neste artigo, junto com a biblioteca Doctrine.

zf_doctrine.tar

Links Úteis

Trabalho de Conclusão de Curso: Proposta de Arquitetura para Desenvolvimento Web Baseada em PHP utilizando Design Patterns, Um Estudo de Caso

Olá a todos, como vão? Algum tempo parado com o blog, apesar de ter dito que agora eu teria mais tempo para me dedicar a ele, não é o que está acontecendo :D . Bom hoje trago para vocês o meu tema de trabalho de conclusão de curso, com o qual conclui minha graduação no Centro de Ensino Superior de Foz do Iguaçu, me tornando Bacharel em Ciência da Computação. Na segunda-feira dia 7 de Dezembro de 2009, apresentei a banca final, defendendo o tema, explanando toda a parte prática (a parte teórica foi apresentada na banca parcial) e ganhando a aprovação dos avaliadores da banca.

Objetivo Geral

Propor uma arquitetura de desenvolvimento de aplicações em PHP contendo design patterns que forneça uma maior estrutura organizacional, padronização de programação, facilidade de manutenção, menos repetição de código e que evite bad smell (algo errado no código que necessita ser refatorado).

Arquitetura Definida

Diagrama da arquitetura definida

Diagrama da arquitetura definida

O fluxo definido por esta arquitetura segue o padrão definido pelo Zend Framework e, também, possui algumas customizações para a comunicação entre cada camada. A estrutura base do framework é baseada no pattern Model-View-Controller (MVC), o que divide a aplicação em Model, View e Controller. Na arquitetura, conforme apresentado na Figura acima ainda existem as camadas: Facade, Data Mapper e Table Data Gateway.

Todo o fluxo inicia-se por uma requisição feita por um usuário, o framework definirá qual o Controller requerido, este então será responsável por tratar a requisição e, utilizando o pattern Factory Method, o Controller obtém a Facade ligada ao caso de uso a que ele corresponde e então delega para esta camada o processamento da lógica referente a regra de negócio.

A Facade poderá utilizar um Data Mapper para obter dados do banco de dados, ou para fazer operações a registros do banco. O Data Mapper irá utilizar o Table Data Gateway para efetuar as operações SQL, que é a linguagem compreendida pelo banco de dados. Ele também poderá mapear os dados vindos do Table Data Gateway para objetos Model, que representam em forma de objetos as entidades do banco de dados.

Existe ainda a implementação do Observer e Observable, que fazem parte do design pattern Observer. Uma classe Observable possuirá métodos para se conectar a Observers e para notificar cada um deles. A classe Observer irá fazer um log das operações notificadas pela Observable, gravando este log em banco de dados, no formato JSON, para permitir uma consulta posterior.

Após todo o processamento das camadas inferiores ser concluído, o Controller irá continuar o fluxo da aplicação, exibindo a View para o usuário, que pode conter os dados pegos pelo Data Mapper, ou os formulários definidos pelos componentes Zend_Form para obter dados para algum registro, ou mensagens relevantes para informar ao usuário.

O design pattern Singleton é implementado por diversos componentes do Zend Framework, como, por exemplo, o Zend_Auth que é utilizado na autenticação de usuários. Com este pattern é possível manter os objetos durante a aplicação e por apenas um ponto de entrada. Isto garante a consistência deste objeto, sabendo sempre o que esperar dele.

Aplicação de Exemplo

Para fazer o estudo de caso foi necessário a elaboração de uma aplicação, esta sendo um sistema simples de controle de bibliotecas. Como não era necessário especificar um cliente real, foram criadas algumas regras de negócio que o sistema devia atender e então construí todo o sistema utilizando a arquitetura. A observação que deixo para quem for analisar a aplicação é que a regra de negócio é sim extremamente simples, e acredito que se fosse aplicar a aplicação a uma biblioteca real existiria muita coisa a se trabalhar, porém com ela foi possível demonstrar o uso da arquitetura e o resultado foi bem satisfatório. Os casos de uso criados são demonstrados no diagrama de casos de uso apresentado na Figura abaixo:

Diagrama de Casos de Uso

Diagrama de Casos de Uso

Conclusões

A principal conclusão apresentada na minha monografia é com relação a manutenibilidade dos sistemas e ciclo de vida. Com uma arquitetura padronizada e altamente reutilizável graças a um ótimo framework e a utilização de design patterns pode-se aumentar bastante o ciclo de vida, principalmente porque desenvolvedores que forem alterar as aplicações já prontas, deverão apenas se familiarizar com os patterns já especificados e reconhecidos mundialmente, e, também, ler a documentação da arquitetura, para saberem onde exatamente alterar as partes necessárias. Os trabalhos futuros são:

  • Tornar a arquitetura independente de framework;
  • Basear a arquitetura em plugins para facilitar a criação e atualização de funcionalidades de sistemas;
  • Escolher entre os design patterns existentes para adicionar ou remover patterns de acordo com a necessidade das aplicações que forem desenvolvidas.

A Monografia

Código-Fonte

O código-fonte com a arquitetura e a aplicação de exemplo poderão ser baixados no link abaixo. Se você tiver melhorias e dicas por favor envie um comentário que ficarei bem agradecido, principalmente se forem coisas construtivas. Bom, é isso, gostaria só de anunciar as boas novas e, também, divulgar esta arquitetura, que ao meu ver ficou muito boa para se trabalhar, e que já estou utilizando em um sistema real… :)

Código-Fonte da Arquitetura

Reapresentação da palestra Webservices REST com Zend Framework

Ontem eu e o Jurmir apresentamos na Semana Acadêmica Integrada 2009 do CESUFOZ a palestra Webservices REST com Zend Framework. Quem ainda não viu os slides, confira-os no post original, escrito após a apresentação na Latinoware. Agradeço a todos que nos prestigiaram e espero que tenham gostado da palestra. Esta foi uma reapresentação da palestra da Latinoware, o bom desta palestra é que tínhamos internet, então foi possível demonstrar o cliente Twitter, apesar de não ter sido possível inserir novos status, mas tudo bem, já foi muito mais completa do que a da Latinoware.

Estou em processo de término da monografia, além de estar atuando em alguns projetos, em breve espero colocar mais artigos aqui no blog. Até mais.

Cliente Twitter com Zend Framework

Bom, como disse no outro post fiz um cliente com o Zend Framework para consumir o serviço REST disponibilizado pelo Twitter. Este cliente era para ser apresentado na palestra da Latinoware, infelizmente não foi possível o acesso à internet, o que faria com que o cliente não funcionasse, então acabei nem mostrando ele. Disponibilizo aqui então este cliente, para utilizá-lo é necessário ativar o mod_rewrite no Apache e, provavelmente, ativar a extensão curl do PHP. Tendo estes requisitos, descompacte o zip na pasta acessível pelo Apache, altere o arquivo application/configs/application.ini para que ele contenha o usuário e senha utilizados no seu banco de dados MySQL, como no exemplo abaixo:
resources.db.params.username = "root"
resources.db.params.password = ""

Então, crie o banco de dados utilizando o arquivo encontrado em db/banco.sql. Após certificar-se de tudo estar pronto basta ir no browser e acessar o cliente na sua URL local, para quem não conhece o Zend Framework é necessário acessar a pasta public para que ele funcione… por exemplo: http://localhost/zf_twitter/public. Então é só cadastrar seu usuário e senha do twitter, logar-se e postar seus twitts, visualizar os últimos twitts de seus amigos, seu perfil e fazer buscas por twitts. Espero que gostem e lembro que é um aplicativo simples, mas quem estiver afim pode extendê-lo sem nenhum problema.

Para baixar o cliente, clique aqui

Até o próximo post.

Palestra Webservices REST com Zend Framework

Ontem foi um dia bem diferente para mim, apresentei na Latinoware uma palestra sobre Webservices REST com Zend Framework, junto com meu amigo Jurmir. Fiquei bem nervoso, mas depois que comecei a falar consegui me acalmar e apresentar tranquilamente. Agradeço a todos que estiveram lá e espero que tenham gostado da palestra. Infelizmente graças a ótima rede wireless disponibilizada lá, não pude mostrar o aplicativo que fiz para consumir o webservice do Twitter, mas daqui a pouco disponibilizarei ele aqui no site. Bom, segue abaixo os slides da palestra (via slideshare).