Jump to content

Archived

This topic is now archived and is closed to further replies.

Henrique Barcelos

View / Template engine

Recommended Posts

Saudações, galera...

Vou aproveitando minhas curtas férias para dar prosseguimento ao meu framework caseiro, no bom sentido...

 

Estou querendo estruturar a parte que diz respeito à View. Dei uma olhada nos frameworks mais famosos, tem bastante coisa, se torna até meio complexo quando eu parto pro ZF.

 

Enfim, o que eu pensei até agora sobre isso:

  • Terei que fazer a manipulação do OutputBuffer (ob_*).
  • Usar variáveis do escopo da view no template, da forma: $this->var
  • Não irei enviar nada para a saída do navegador, isso é responsabilidade da Response.
  • Seria interessante permitir o parse de partes da view (partial views).

Preciso bolar um jeito também de implementar um CompositeView, para aplicações multiconteúdo (99,99% do que eu já desenvolvi). Como estruturar cada parte do layout e como unir essas partes para formar o todo?

 

Começando do simples, sem helpers, sem aquela c***lhada de coisa que vemos por aí e a partir daí ir incrementando a coisa. Não planejo chegar a algo tão complexo quando o Zend_View, muito pelo contrário, quero manter as coisas simples.

 

Estou com essas ideias meio jogadas, preciso de uma força pra encontrar o ponto de início.

 

Agradeço a atenção desde já.

Share this post


Link to post
Share on other sites
Terei que fazer a manipulação do OutputBuffer (ob_*).

Talvez.

 

Dependendo de como você desenvolver a View pode ser necessário ou não. OU, se você quiser fazer algum tipo de parsing ou "correção" no template.

 

Como na maioria das vezes os (ou as?) Template Views são um espaguete de abertura e fechamento de tags PHP, o source gerado acaba com diversas linhas em branco duplicadas.

 

Eu acho feio isso, então UMA das coisas que eu faço é removê-las do buffer antes de ecoá-lo.

 

Usar variáveis do escopo da view no template, da forma: $this->var

Eu acho impossível não ser dessa forma.

 

Se fosse possível definir no objeto as variáveis que seriam utilizadas, com o PHP 5.4, traria um belo ganho de performance. Pelo menos foi o que eu li da transcrição do último WebCast do Manuel Lemos, no PHPClasses.

 

Não irei enviar nada para a saída do navegador, isso é responsabilidade da Response.

É isso aí. :thumbsup:

 

Mas não ésó isso que ela fará. Também responsabilidade da Response enviar Headers e Cookies, além de verificar se os Headers podem ou não ser enviados.

 

Seria interessante permitir o parse de partes da view (partial views).

Vai ter que me explicar melhor o que seria esse negócio.

 

Seriam includes de templates? :huh:

 

Preciso bolar um jeito também de implementar um CompositeView, para aplicações multiconteúdo (99,99% do que eu já desenvolvi). Como estruturar cada parte do layout e como unir essas partes para formar o todo?

De novo, em linhas gerais pra burro entender (:rolleyes:). O que seria um CompositeView?

 

Começando do simples, sem helpers, sem aquela c***lhada de coisa que vemos por aí e a partir daí ir incrementando a coisa. Não planejo chegar a algo tão complexo quando o Zend_View, muito pelo contrário, quero manter as coisas simples.

Também não tenho View Helpers... Ainda :hehehe:

Share this post


Link to post
Share on other sites
Vai ter que me explicar melhor o que seria esse negócio.

 

Seriam includes de templates?

Isso... No caso, meu primeiro objetivo é reformular esse site aqui: http://mshoje.com/

Observe que ele tem uma quantidade GRANDE de conteúdos distintos.

 

Observe também essa URL: http://mshoje.com/anuncios/

Compare com a primeira. Do lado direito, não exibo mais os anúncios, pois eles estarão no centro da página, então não faz sentido exibi-los.

Esse tipo de lógica atualmente é um monte de if-else jogado no HTML. Eu queria jogar isso para uma camada acima, no "ViewAssembler" (não pensei no nome ainda), que dadas as regras, irá montar as páginas da maneira desejada.

 

Aqui tem uma exemplificação do que é o padrão Composite View.

:seta: http://www.devshed.com/c/a/PHP/PHP-Rendering-Web-Pages-Using-the-Composite-View-Design-Pattern/

 

O uso de partial views me permite fugir da preocupação com a colisão de nomes de variáveis entre as diversas views do sistema. Assim que for possível, eu já renderizo o HTML de parte da página e o retorno (daí a necessidade do output buffer, pois o rodapé pode ficar pronto antes do conteúdo, por exemplo).

Share this post


Link to post
Share on other sites

Já experimentou fazer esses Partial Views invocando o Response Body de outro Controller dentro do Template principal?

 

Algo assim:

 

class AdvertisementController extends AbstractControler {

   /**
    * Esse init() é particular ao meu sistema. Serve para complementar
    * o construtor da classe-pai sem sobrescrever o construtor
    */
   public function init() {

       /**
        * Esse método, da minha View, seta como TRUE uma flag
        * que diz ao renderizador se haverá uma renderização de template.
        */
       $this -> view -> disableRender();
   }

   public function sidebar() {

       /**
        * Monta a lógica de exibição dos anúncios
        * e adiciona ao Response Body o conteúdo do template
        * do anúncio
        *
        * No meu caso faço isso através de
        * $this -> getResponse() -> appendBody()
        */
   }
}

class HomeController extends AbstractController {

   public function index() {

       // ...

       $advertisement = new AdvertisementController;

       $advertisement -> sidebar();

       $this -> view -> assign(

           'sidebarAdvertisemets', $dvertisement -> getResponseBody()
       );
   }
}

E no template você só ecoa essa variável. A lógica toda estará no Controller de anúncios, separada por "tipo", um em cada método.

 

Só uma idéia que me ocorreu aqui rápido, não testei nem nada, pode até ser que não dê certo. Pura especulação. u.u

Share this post


Link to post
Share on other sites

Isso aí tá fugindo um pouco do princípio do Controller Magro...

Eu estava pensando em implementar um "LayoutManager" abstrato e ir estendendo conforme a necessidade.

HomeLayoutManager
FaqLayoutManager
ContactLayoutManager

 

Tá faltando mesmo é eu dar o "pequeno passo para iniciar a longa caminhada".

Share this post


Link to post
Share on other sites

Invariavelmente, nas nem todos os Controllers serão magros.

 

Uma aplicação de médio / grande porte terá, por exemplo, na página inicial muitos módulos distintos.

 

Uma arquitetura HMVC que, pelo que eu li, nada mais é do que, a grosso modo, "MVC dentro de MVC", sendo que a maioria dos Controllers não teria output (apenas popularia seus respectivos Response Bodies), proporcionaria sim, magreza em muitos Controllers, pois cada um manipularia um pequena parte da aplicação, mas não em todos, afinal, como no exemplo, o Controller da página principal, terá de agregar todos os recursos distribuindo-os para a View final.

 

Ou não?

Share this post


Link to post
Share on other sites

É que aí já não sei se o 'juntador' de respostas é um controller mais...

Bom, ele teria no mínimo que instanciar os outros controllers e disparar os métodos para a requisição.

 

O que não tá muito certo é o seguinte:

Cada controller vai devolver a resposta já processada? O controller master pode ter a permissão de alterar o que já foi processado?

 

Se for dessa forma, não faz sentido haver um composite view no sistema, vou ter que apenas montar as respostas já preprocessadas, fazendo uma concatenação ou outra...

Share this post


Link to post
Share on other sites

Eu acho que tanto faz.

 

A idéia do HMVC é que cada componente funcione isoladamente ou em conjunto da mesma forma. Por esse ângulo, justifica enviar a Resposta em cada action, de cada Controller.

 

Particularmente não gosto disso.

 

Só não estou bem certo se ao enviar a primeira resposta, a segunda já não vai ser barrada devido ao envio de header já ter ocorrido.

 

É uma coisa que não testei no meu sistema.

Share this post


Link to post
Share on other sites

Outra coisa, como fazer a relação View-Templates? 1:1??? 1:N...

 

To naquela época em que meu cérebro não funciona direito, não consigo ter ideias por mais que eu tente... --'

Share this post


Link to post
Share on other sites

Eu acho que depende. Haverão Controllers de páginas simples onde um único template bastará.

 

Já outros poderão exigir mais. Agora, no caso das divagações acima, acredito que será sempre 1:1, pois cada Controller preencherá a Response com os dados de um único template.

 

A menos que o template seja quebrado em header, content e footer...

Share this post


Link to post
Share on other sites

Eu gosto demais quando tem discussões relacionadas a Design Patterns e OO!

Qual a diferença entre dar um include nos templates e usar buffers (ob_*) ?

 

Tem que haver um motivo MUITO forte,por que eu simplesmente faço um include e tipo "f***-se", então quero aproveitar essa oportunidade e esclarecer minhas dúvidas, valeu.

Share this post


Link to post
Share on other sites

Se você já fornecer a saída ao navegador, primeiro que não pode alterar a ordem de exibição e segundo que não poderá mais enviar headers.

Share this post


Link to post
Share on other sites

Se você já fornecer a saída ao navegador, primeiro que não pode alterar a ordem de exibição e segundo que não poderá mais enviar headers.

Então é por isso que eu não consigo header em alguns casos.. Hhmmm!

Share this post


Link to post
Share on other sites

Bom, acabei chegando meio sem querer numa solução... Codifiquei e o resultado saiu melhor do que eu tava esperando...

 

View_Abstract.php

 

<?php
/**
* @author henrique
* @version 0.1 beta
* 
* Implementação da base de uma View Engine
*/
abstract class View_Abstract {
/**
	* Armazena as variáveis que serão passadas ao template.
	* 
	* @var array
	*/
protected $_vars = array();

/**
	* Armazena o nome do template utilizado na view.
	* 
	* @var string
	*/
protected $_template;

/**
	* Armazena o caminho real para o template.
	* 
	* @var string
	*/
protected $_templatePath;

/**
	* Armazena o caminho base onde se encontram os templates.
	* 
	* @var string
	*/
protected $_path;

/**
	* Armazena um valor padrão par ao caminho base dos templates.
	* 
	* @var string
	*/
protected static $_defaultPath;

/**
	* Armazena a extensão dos arquivos de template.
	* 
	* @var string
	*/
protected $_templateExtension = 'phtml';

/**
	* Armazena a extensão padrão dos arquivos de template.
	* 
	* @var string
	*/
protected static $_defaultTemplateExtension = 'phtml';

/**
	* Se TRUE, uma exceção será disparada ao tentar 
	* acessar uma variável inexistente.
	* 
	* @var boolean
	*/
protected $_strictVars = true;

/**
	* @param string $spec : o nome do template (sem extensão)
	* @param array $vars : as variáveis a serem passadas ao template
	* @param array $opt : um array associativo. Chaves:
	* 		path => o caminho para o diretório do template. Se não informado, o padrão será usado
	* 		templateExtension => a extensão dos arquivos de template. O padrão é 'phtml'
	*/
public function __construct($spec, array $vars = array(), array $opt = array()) {
	if(isset($opt['path'])) {
		$this->setPath($opt['path']);
	} else {
		$this->_path = self::$_defaultPath;
	}

	if(isset($opt['templateExtension'])) {
		$this->setTemplateExtension($opt['templateExtension']);
	} else {
		$this->_templateExtension = self::$_defaultTemplateExtension;
	}

	$this->assign($vars);
	$this->setTemplate($spec);
}

/**
	* @param string $var
	* @param mixed $value
	*/
public function __set($var, $value) {
	$this->assign($var, $value);
}

/**
	* @param string $var
	* @return mixed
	*/
public function __get($var) {
	return $this->getVar($var);
}

/**
	* Seta o arquivo de template para a view.
	* 
	* Para templates dentro de módulos, utilize a notação:
	* 		<code>module.templateName</code>
	* Exemplo:
	* 		<code>$view->setTemplate('user.list')</code>
	* 
	* @param string $spec : o nome do template.
	* @return Abstract_View : fluent interface
	*/
public function setTemplate($spec) {
	$this->_template = (string) $spec;
	$tplPath = $this->_path . $this->_normalizeTemplateName($spec) . '.' . $this->_templateExtension;
	if(!FileSystem_File::isFile($tplPath)) {
		throw new View_Exception(sprintf('O template %s não é um arquivo válido!', $tplPath));
	}

	$this->_templatePath = $tplPath;
	return $this;
}

/**
	* Substitui o . por / ou \ para encontrar o caminho do template.
	* 
	* @param string $spec
	* @return string
	*/
protected function _normalizeTemplateName($spec) {
	return str_replace('.', FileSystem::SEPARATOR, $spec);
}

/**
	* Retorna o nome do template.
	* 
	* @return string
	*/
public function getTemplate() {
	return $this->_template;
}

/**
	* Seta o caminho para o template.
	* 
	* @param string $path
	* @return View_Abstract : fluent interface
	* @throw View_Exception : caso $path não aponte para um diretório
	*/
public function setPath($path) {
	self::_verifyPath($path);
	$this->_path = (string) new FileSystem_Directory($path);
	return $this;
}

/**
	* Retorna o caminho para o template.
	* 
	* @param string $path
	*/
public function getPath($path) {
	return $this->_path;
}

/**
	* Seta a extensão para o template.
	* 
	* @param unknown_type $ext
	* @return View_Abstract
	*/
public function setTemplateExtension($ext) {
	$this->_templateExtension = (string) $ext;
	return $this;
}

/**
	* Retorna a extensão do template.
	* 
	* @return string
	*/
public function getTemplateExtension() {
	return $this->_templateExtension;
}

/**
	* Atribui uma variável de template.
	* 
	* @param string|array $var
	* @param mixed $value
	*/
public function assign($var, $value = null) {
	if(is_array($var)) {
		foreach($var as $key => $value) {
			$this->_vars[(string) $key] = $value;
		}
	} else {
		$this->_vars[(string) $var] = $value;
	}
	return $this;
}

/**
	* Retorna uma variável.
	* 
	* @param string $var
	* @return mixed
	* @throws View_Exception : caso a variável não exista e strictVars seja TRUE
	*/
public function getVar($var) {
	$var = (string) $var;
	if(isset($this->_vars[$var])) {
		return $this->_vars[$var];
	} else if($this->_strictVars === true) {
		throw new View_Exception(sprintf('A variável "%s" 
				não existe para este template', $var));
	} else {
		return null;
	}
}

public function hasVar($var) {
	return isset($this->_vars[(string) $var]);
}

/**
	* Retorna as variáveis de template.
	* 
	* @return string
	*/
public function getVars() {
	return $this->_vars;
}

/**
	* Limpa as variáveis de template.
	* 
	* @return View_Abstract : fluent interface
	*/
public function clearVars() {
	unsert($this->_vars);
	$this->_vars = array();
	return $this;
}

/**
	* Renderiza o template.
	* 
	* @return string
	*/
public function render() {
	ob_start();
	$this->_run();
	return ob_get_clean();
}

/**
	* Executa a View.
	* 
	* @return void
	*/
abstract protected function _run();

/**
	* Seta o diterório padrão para os templates.
	*
	* @param string $path
	* @return void
	* @throws View_Exception
	*/
public static function setDefaultPath($path) {
	self::_verifyPath($path);
	self::$_defaultPath = (string) new FileSystem_Directory($path);
}

/**
	* Verifica o caminho para os templates, lançando uma exceção se não for válido.
	*
	* @param string $path
	* @throws View_Exception
	*/
protected static function _verifyPath($path) {
	if(!FileSystem_Directory::isDir($path)) {
		throw new View_Exception('O diretório para de templates não é válido');
	}
}

/**
	* Seta a extensão padrão para os templates.
	* 
	* @param string $ext
	* @return void
	*/
public static function setDefaultTemplateExtension($ext) {
	self::$_defaultTemplateExtension = (string) $ext;
}

/**
	* Retorna a extensão padrão para os templates.
	* 
	* @return string
	*/
public static function getDefaultTemplateExtension() {
	return self::$_defaultTemplateExtension;
}

/**
	* Seta a flag que indica se deve ser lançada uma exceção
	* ao tentar pegar o valor de uma variável inexistente.
	* 
	* @param boolean $opt
	*/
public function strictVars($opt) {
	$this->_strictVars = (bool) $opt;
}
}

 

 

 

View.php

class View extends View_Abstract {
/**
	* @see View_Abstract::_run()
	*/
protected function _run() {
	foreach($this->_vars as $key => &$each) {
		if($each instanceof View_Abstract) {
			$each = $each->render();
		}
	}
	include $this->_templatePath;
}
}

 

index.php

<?php
require_once 'config/bootstrap.php';
View::setDefaultPath(Core::getInstance()->getAppRoot() . 'view/');

$homeView = new View('home');
$headView = new View('head');
$contentView = new View('content');

$homeView->head = $headView;
$homeView->content = $contentView;

$headView->title = 'Teste CompositeView';
$headView->charset = 'iso-8859-1';

$contentView->header = '<header><h1>Isto é um HEADER!</h1></header>';
$contentView->aside = '<aside>Isto é um conteúdo lateral!</aside>';
$contentView->main = '<section id="main">Este é o conteúdo principal</section>';
$contentView->footer = '<footer>Este é o rodapé</footer>';

echo $homeView->render();

 

home.phtml

<!DOCTYPE html>
<html>
<?php echo $this->head ?>
<?php echo $this->content ?>
</html>

 

head.phtml

<head>
<title><?php echo $this->title ?></title>
<meta charset="<?php echo $this->charset ?>"/>
<meta http-equiv="content-type" content="text/html;<?php echo $this->charset ?>"/>
</head>

 

content.phtml

<body>
<?php echo $this->header ?>
<?php echo $this->aside ?>
<?php echo $this->main ?>
<?php echo $this->footer ?>
</body>

Percebam que o Composite ficou meio implícito ali. Diferente do ZF, eu não restinjo o tipo das variáveis de template. Se for uma View, eu simplesmente a renderizo antes de dar o include.

 

 

O que já pensei em alterar: transformar View_Abstract numa interface e mover o código todo par View.

 

Ainda preciso arrumar um jeito de deixar o HTML de saída bonitinho, por enquanto ele fica assim:

<!DOCTYPE html>
<html>
<head>
<title>Teste CompositeView</title>
<meta charset="iso-8859-1"/>
<meta http-equiv="content-type" content="text/html;iso-8859-1"/>
</head>	<body>
<header><h1>Isto é um HEADER!</h1></header>	<aside>Isto é um conteúdo lateral!</aside>	<section id="main">Este é o conteúdo principal</section>	<footer>Este é o rodapé</footer></body></html>

 

Sugestões?

Share this post


Link to post
Share on other sites

Eu também esses acessos de frescura com a beleza do source, então eu adiciono uma nova linha em branco no template logo após cada umas tags de abertura do PHP.

 

Não sei porque motivo isso conserta a maioria das indentações que você define no próprio template (até agora só menus dropdown me pegaram de calça curta) ao custo de duplicar as linhas em branco no source.

 

Como eu, assim como você, capturo o buffer de saída, eu aplico um limpeza com a ER abaixo nele, antes de ecoar:

 

(^[\\r\\n]*|[\\r\\n]{2,})[\\s\\t]*[\\r\\n]+

E fica tudo bunitu ^_^

 

No mais... Interessante esse esquema, de repente eu até implemente no meu. Fazer include de template dentro do próprio template é horrível :P

Share this post


Link to post
Share on other sites

Então, esse negócio de include dentro do template te mata pois você precisa setar todas as variáveis numa única view e aí uma hora você vai ter uma colisão de nomes, ou vai ficar com nomes prefixados com um 'namespace' que também não é legal...

 

Sobre essa ER aí, você substitui isso por nada?

 

preg_replace('^[\\r\\n]*|[\\r\\n]{2,})[\\s\\t]*[\\r\\n]+', '', $output)

Share this post


Link to post
Share on other sites

Achei legal a implementação do Henrique, código bem maneiro, mas tipo..

Pra que fazer essa codificação? Basta criar um index com o HTML, e faz uma função pra chamar as páginas de forma dinâmica dentro de uma div.

 

Desculpe a minha ignorancia, mas EU não vejo a necessidade AINDA de fazer código para isso. Eu simplesmente deixo HTML da página na index, e faço o esquema que disse logo acima.

 

No mais... Interessante esse esquema, de repente eu até implemente no meu. Fazer include de template dentro do próprio template é horrível

Concordo, mas funciona!

Share this post


Link to post
Share on other sites

Sobre essa ER aí, você substitui isso por nada?

Esqueci de mencionar. Não, substituo por dois \n

 

Eu sei parece estranho uma ER que limpe linhas em branco duplicadas fazê-lo por outras duas linhas duplicadas.

 

Mas eu sou paranóico :yay:

Share this post


Link to post
Share on other sites
Pra que fazer essa codificação? Basta criar um index com o HTML, e faz uma função pra chamar as páginas de forma dinâmica dentro de uma div.

 

Desculpe a minha ignorancia, mas EU não vejo a necessidade AINDA de fazer código para isso. Eu simplesmente deixo HTML da página na index, e faço o esquema que disse logo acima.

 

Isso funciona bem quando a estrutura das páginas é a mesma para todas as páginas do sistema.

Mas como eu falei no post #3, muitas vezes, a estrutura das páginas varia conforme o conteúdo. Aí eu acho deselegante (pra não dizer outra coisa) aquele monte de condicional na view.

 

<?php if(FrontController::getRequestData('get')->get('page') != 'anuncios') : ?>
<aside id="aside-ads">
   	<?php echo FrontController::getResponse('ads'); ?>
</aside>
<?php endif; ?>

 

Primeiro: estou quebrando de certa forma o conceito de MVC, no qual a View não deve conhecer o Controller.

Segundo: apesar de eu não concordar com a expressão 'View burra', esse tipo de lógica além de ser deselegante, dependendo da complexidade, se torna difícil de manter.

 

Além disso, ao efetuar uma requisição ajax, eu tenho que perpetrar o horror que é:

<?php if(FrontController::getRequest()->isAjax()): ?>
...
<body>
<section id="main">
   	...
   	<div class="ajax-content">
   	<?php endif; ?>
   	<!-- Conteúdo da requisição Ajax -->
   	<?php if(FrontController::getRequest()->isAjax()): ?>
   	</div>
   	...
</section>
</body>
...
<?php endif; ?>

 

Pense no facebook: é possível você abrir uma foto de um post como uma requisição Ajax ou se você tiver o link, abrir como conteúdo central da página.

Com ajax, a resposta deve ser somente a marcação que diz respeito à foto. Sem, a resposta deve trazer a página completa, doctype, html, head, body, etc...

Share this post


Link to post
Share on other sites

Minha view, alias meu Layout estende a View principal, o Layout por sua vez, sobrescreve o método render, fazendo com que o conteúdo renderizado seja atribuído a variável content, nisso, eu posso ter a página principal (index.phtml), e lá ter:

<?php echo $this->content ?>

Daí todas as outras Views, ao invés de estender a View, estende a AppLayout, que vai ter o método render modificado.

<?php

      abstract class Layout extends View {

             /**
              * Reenderiza o layout
              * @param string $script
              * @return mixed 
              */
             public function renderLayout ( $script ) {
                    echo parent::render ( $script ) ;  
             }

             /**
              * Reenderiza o conteúdo
              * @param string $script 
              */
             public function render ( $script ) {
                    $this->assign ( 'content' , parent::render ( $script ) ) ;
             }

      }


      class AppLayout extends Layout { 

             public function display ( ) {
                    $this->renderLayout ( '/Templates/Layouts/AppLayout.phtml' ) ;
                    // ...
             }

      }

      class UserView extends AppLayout {

             public function showUserPanel ( ) {
                    $this->render ( '/Templates/Panels/Panel.phtml' ) ;

                    // ...
                    parent::display ( ) ;
             }

      }

 

Algo mais ou menos isso aí.

Share this post


Link to post
Share on other sites

×

Important Information

Ao usar o fórum, você concorda com nossos Terms of Use.