| 1 |
Estendendo a Barra de Ferramenta para Debug Web |
|---|
| 2 |
=============================================== |
|---|
| 3 |
|
|---|
| 4 |
*por Ryan Weaver* |
|---|
| 5 |
|
|---|
| 6 |
Por padrão, a barra de ferramentas para debug web do symfony contém uma variedade de ferramentas que auxiliam |
|---|
| 7 |
na depuração, melhoria de desempenho e muito mais. A *Barra de Ferramenta para Debug Web* |
|---|
| 8 |
consiste de várias ferramentas, chamada *painéis de debug web*, que está relacionada com a memória cache, |
|---|
| 9 |
config, logs, uso de memória, versão do symfony e tempo de processamento. Além disso, o |
|---|
| 10 |
symfony 1.3 introduz dois novos *painéis de debug web* para exibir informações da `visão` (*view*) |
|---|
| 11 |
e depuração de `e-mail` (*mail*). |
|---|
| 12 |
|
|---|
| 13 |
 |
|---|
| 14 |
|
|---|
| 15 |
Desde a versão 1.2 do symfony, os desenvolvedores podem criar facilmente seus próprios *painéis de debug web* e |
|---|
| 16 |
adicioná-los à *Barra de Ferramenta para Debug Web*. Neste capítulo, configuraremos um novo *painel de debug web* |
|---|
| 17 |
e, depois, utilizaremos todas as diferentes ferramentas e personalizações disponíveis. |
|---|
| 18 |
Além disso, o [ac2009WebDebugPlugin](http://www.symfony-project.org/plugins/ac2009WebDebugPlugin) |
|---|
| 19 |
contém vários painéis úteis e interessantes que empregam algumas das |
|---|
| 20 |
técnicas utilizadas neste capítulo. |
|---|
| 21 |
|
|---|
| 22 |
Criando um novo *Painel de Debug Web* |
|---|
| 23 |
------------------------------------- |
|---|
| 24 |
|
|---|
| 25 |
Os componentes individuais da *Barra de Ferramentas para Debug Web* são conhecidos como *painéis de debug web* |
|---|
| 26 |
e são classes especiais que estendem a classe ~`sfWebDebugPanel`~. Criar um novo |
|---|
| 27 |
painel é realmente muito fácil. Crie um arquivo chamado `sfWebDebugPanelDocumentation.class.php` |
|---|
| 28 |
em seu diretorio `lib/debug/` (você precisa criar este diretório): |
|---|
| 29 |
|
|---|
| 30 |
[php] |
|---|
| 31 |
// lib/debug/sfWebDebugPanelDocumentation.class.php |
|---|
| 32 |
class acWebDebugPanelDocumentation extends sfWebDebugPanel |
|---|
| 33 |
{ |
|---|
| 34 |
public function getTitle() |
|---|
| 35 |
{ |
|---|
| 36 |
return '<img src="/images/documentation.png" alt="Documentation Shortcuts" height="16" width="16" /> docs'; |
|---|
| 37 |
} |
|---|
| 38 |
|
|---|
| 39 |
public function getPanelTitle() |
|---|
| 40 |
{ |
|---|
| 41 |
return 'Documentation'; |
|---|
| 42 |
} |
|---|
| 43 |
|
|---|
| 44 |
public function getPanelContent() |
|---|
| 45 |
{ |
|---|
| 46 |
$content = 'Placeholder Panel Content'; |
|---|
| 47 |
|
|---|
| 48 |
return $content; |
|---|
| 49 |
} |
|---|
| 50 |
} |
|---|
| 51 |
|
|---|
| 52 |
No mínimo, todos os painéis de debug devem implementar os métodos `getTitle()`, `getPanelTitle()` |
|---|
| 53 |
e `getPanelContent()`. |
|---|
| 54 |
|
|---|
| 55 |
* ~`SfWebDebugPanel::getTitle()`~: Determina como o painel irá aparecer na |
|---|
| 56 |
barra de ferramenta. Como a maioria dos painéis, o nosso painel personalizado inclui um pequeno ícone |
|---|
| 57 |
e um nome curto no painel. |
|---|
| 58 |
|
|---|
| 59 |
* ~`SfWebDebugPanel::getPanelTitle()`~: Usado como o texto para a *tag* `h1` |
|---|
| 60 |
que aparecerá no topo do conteúdo do painel. Também é usado como atributo `title` |
|---|
| 61 |
da *tag link* que envolve o ícone na barra de ferramentas e, como tal, |
|---|
| 62 |
*não* deve incluir qualquer código HTML. |
|---|
| 63 |
|
|---|
| 64 |
* ~`SfWebDebugPanel::getPanelContent()`~: gera o conteúdo HTML que |
|---|
| 65 |
será exibido quando você clicar no ícone do painel. |
|---|
| 66 |
|
|---|
| 67 |
A única etapa restante é para notificar a aplicação que você deseja incluir |
|---|
| 68 |
o novo painel na sua barra de ferramentas. Para fazer isso, adicione um *listener* ao |
|---|
| 69 |
evento `debug.web.load_panels`, que é notificado quando a *Barra de Ferramenta para Debug Web* |
|---|
| 70 |
está coletando os potenciais painéis. Primeiramente, modificar o arquivvo |
|---|
| 71 |
`config/ProjectConfiguration.class.php` para ouvir (*listen*) o evento: |
|---|
| 72 |
|
|---|
| 73 |
[php] |
|---|
| 74 |
// config/ProjectConfiguration.class.php |
|---|
| 75 |
public function initialize() |
|---|
| 76 |
{ |
|---|
| 77 |
// ... |
|---|
| 78 |
|
|---|
| 79 |
$this->dispatcher->connect('debug.web.load_panels', array( |
|---|
| 80 |
'acWebDebugPanelDocumentation', |
|---|
| 81 |
'listenToLoadDebugWebPanelEvent' |
|---|
| 82 |
)); |
|---|
| 83 |
} |
|---|
| 84 |
|
|---|
| 85 |
Agora, vamos adicionar a função *listener* `listenToLoadDebugWebPanelEvent()` ao |
|---|
| 86 |
`acWebDebugPanelDocumentation.class.php` para adicionar o painel na barra de ferramentas: |
|---|
| 87 |
|
|---|
| 88 |
[php] |
|---|
| 89 |
// lib/debug/sfWebDebugPanelDocumentation.class.php |
|---|
| 90 |
public static function listenToLoadDebugWebPanelEvent(sfEvent $event) |
|---|
| 91 |
{ |
|---|
| 92 |
$event->getSubject()->setPanel( |
|---|
| 93 |
'documentation', |
|---|
| 94 |
new self($event->getSubject()) |
|---|
| 95 |
); |
|---|
| 96 |
} |
|---|
| 97 |
|
|---|
| 98 |
É isto! Atualize seu navegador e verá imediatamente o resultado. |
|---|
| 99 |
|
|---|
| 100 |
 |
|---|
| 101 |
|
|---|
| 102 |
>**TIP** |
|---|
| 103 |
>A partir do symfony 1.3, um parâmetro url `sfWebDebugPanel pode ser usado para automaticamente |
|---|
| 104 |
>abrir um determinado painel de debug web no carregamento da página. Por exemplo, adicionando |
|---|
| 105 |
>`?sfWebDebugPanel=documentation` ao final da URL será automaticamente |
|---|
| 106 |
>aberto o painel de documentação que acabamos de adicionar. Isto pode ser muito útil |
|---|
| 107 |
>ao construir painéis personalizados. |
|---|
| 108 |
|
|---|
| 109 |
Os Três Tipos de *Painéis de Debug Web* |
|---|
| 110 |
--------------------------------------- |
|---|
| 111 |
|
|---|
| 112 |
Nos bastidores, existem apenas três tipos diferentes de *Painéis de Debug Web*. |
|---|
| 113 |
|
|---|
| 114 |
### O painel do tipo *Icon-Only* |
|---|
| 115 |
|
|---|
| 116 |
O tipo mais básico de painel é o que mostra um ícone e texto na barra de ferramentas |
|---|
| 117 |
e nada mais. O exemplo clássico é o painel `memory`, que exibe |
|---|
| 118 |
o uso de memória, mas não faz nada quando clicado. Para criar um painel *icon-only*, |
|---|
| 119 |
basta definir o `getPanelContent()` para retornar uma string vazia. A única saída |
|---|
| 120 |
do painel vem do método `getTitle()`: |
|---|
| 121 |
|
|---|
| 122 |
[php] |
|---|
| 123 |
public function getTitle() |
|---|
| 124 |
{ |
|---|
| 125 |
$totalMemory = sprintf('%.1f', (memory_get_peak_usage(true) / 1024)); |
|---|
| 126 |
|
|---|
| 127 |
return '<img src="'.$this->webDebug->getOption('image_root_path').'/memory.png" alt="Memory" /> '.$totalMemory.' KB'; |
|---|
| 128 |
} |
|---|
| 129 |
|
|---|
| 130 |
public function getPanelContent() |
|---|
| 131 |
{ |
|---|
| 132 |
return; |
|---|
| 133 |
} |
|---|
| 134 |
|
|---|
| 135 |
### O painel do tipo *Link* |
|---|
| 136 |
|
|---|
| 137 |
Como o painel *icon-only*, um painel *link* consiste de um painel sem conteúdo. No entanto, |
|---|
| 138 |
ao contrário do painel *only-icon*, ao clicar em um painel *link* na barra de ferramentas |
|---|
| 139 |
você será direcionado à URL especificada através do método `getTitleUrl()` do painel. Para criar |
|---|
| 140 |
um painel *link*, configure o `getPanelContent()` para retornar uma seqüência vazia e adicione |
|---|
| 141 |
um método `getTitleUrl()` na classe. |
|---|
| 142 |
|
|---|
| 143 |
[php] |
|---|
| 144 |
public function getTitleUrl() |
|---|
| 145 |
{ |
|---|
| 146 |
// link to an external uri |
|---|
| 147 |
return 'http://www.symfony-project.org/api/1_3/'; |
|---|
| 148 |
|
|---|
| 149 |
// or link to a route in your application |
|---|
| 150 |
return url_for('homepage'); |
|---|
| 151 |
} |
|---|
| 152 |
|
|---|
| 153 |
public function getPanelContent() |
|---|
| 154 |
{ |
|---|
| 155 |
return; |
|---|
| 156 |
} |
|---|
| 157 |
|
|---|
| 158 |
### O painel do tipo *Content* |
|---|
| 159 |
|
|---|
| 160 |
De longe, o tipo mais comum de painel é um painel *content*. Estes painéis têm |
|---|
| 161 |
um corpo cheio de conteúdo HTML que é exibido quando você clica no painel |
|---|
| 162 |
na *Barra de Ferramentas para Debug Web*. Para criar esse tipo de painel, simplesmente |
|---|
| 163 |
certifique-se que o `getPanelContent()` retorna mais do que uma string vazia. |
|---|
| 164 |
|
|---|
| 165 |
Personalizando o Painel *Content* |
|---|
| 166 |
------------------------- |
|---|
| 167 |
|
|---|
| 168 |
Agora que você criou e adicionou seu painel personalizado de debug à barra de ferramentas, |
|---|
| 169 |
a adição de conteúdo poderá ser realizada facilmente através do método `getPanelContent()`. |
|---|
| 170 |
O symfony fornece vários métodos para ajudá-lo a tornar este conteúdo rico |
|---|
| 171 |
e utilizável. |
|---|
| 172 |
|
|---|
| 173 |
### ~`SfWebDebugPanel::setStatus()`~ |
|---|
| 174 |
|
|---|
| 175 |
Por padrão, cada painel é exibido na *Barra de Ferramentas para Debug Web* usando um |
|---|
| 176 |
fundo padrão cinza. Mas você pode alterar para um fundo laranja ou vermelho quando se |
|---|
| 177 |
requer atenção especial algum conteúdo dentro do painel. |
|---|
| 178 |
|
|---|
| 179 |
 |
|---|
| 180 |
|
|---|
| 181 |
Para alterar a cor de fundo do painel, basta utilizar o método `setStatus()`. |
|---|
| 182 |
Este método aceita qualquer constante `priority` da classe |
|---|
| 183 |
[sfLogger](http://www.symfony-project.org/api/1_3/sfLogger). |
|---|
| 184 |
Em particular, há três níveis de status diferentes, que correspondem |
|---|
| 185 |
as três diferentes cores de fundo de um painel (cinza, laranja e vermelho). |
|---|
| 186 |
Mais comumente, o método `setStatus()` será chamado de dentro do |
|---|
| 187 |
método `getPanelContent()`, quando ocorreu alguma condição que precisa |
|---|
| 188 |
de atenção especial. |
|---|
| 189 |
|
|---|
| 190 |
[php] |
|---|
| 191 |
public function getPanelContent() |
|---|
| 192 |
{ |
|---|
| 193 |
// ... |
|---|
| 194 |
|
|---|
| 195 |
// set the background to gray (the default) |
|---|
| 196 |
$this->setStatus(sfLogger::INFO); |
|---|
| 197 |
|
|---|
| 198 |
// set the background to orange |
|---|
| 199 |
$this->setStatus(sfLogger::WARNING); |
|---|
| 200 |
|
|---|
| 201 |
// set the background to red |
|---|
| 202 |
$this->setStatus(sfLogger::ERR); |
|---|
| 203 |
} |
|---|
| 204 |
|
|---|
| 205 |
### ~`SfWebDebugPanel::getToggler()`~ |
|---|
| 206 |
|
|---|
| 207 |
Uma das características mais comuns em todas as *Barra de Ferramentas para Debug Web* existentes é o *toggler*: |
|---|
| 208 |
um elemento visual em forma de uma seta que esconde/exibe conteúdo quando clicado. |
|---|
| 209 |
|
|---|
| 210 |
 |
|---|
| 211 |
|
|---|
| 212 |
Esta função pode ser facilmente usada no painel personalizado de debug com a função |
|---|
| 213 |
`getToggler()`. Por exemplo, suponha que queremos mudar uma lista de |
|---|
| 214 |
conteúdo em um painel: |
|---|
| 215 |
|
|---|
| 216 |
[php] |
|---|
| 217 |
public function getPanelContent() |
|---|
| 218 |
{ |
|---|
| 219 |
$listContent = '<ul id="debug_documentation_list" style="display: none;"> |
|---|
| 220 |
<li>List Item 1</li> |
|---|
| 221 |
<li>List Item 2</li> |
|---|
| 222 |
</ul>'; |
|---|
| 223 |
|
|---|
| 224 |
$toggler = $this->getToggler('debug_documentation_list', 'Toggle list'); |
|---|
| 225 |
|
|---|
| 226 |
return sprintf('<h3>List Items %s</h3>%s', $toggler, $listContent); |
|---|
| 227 |
} |
|---|
| 228 |
|
|---|
| 229 |
O `getToggler` possui dois argumentos: o `id` do elemento e |
|---|
| 230 |
um `título` para definir como o atributo `title` do link *toggler*. Você é que deverá |
|---|
| 231 |
criar o elemento DOM com o atributo `id`, bem como qualquer *label* descritiva |
|---|
| 232 |
(por exemplo "Os itens da lista") para o *toggler*. |
|---|
| 233 |
|
|---|
| 234 |
### ~`SfWebDebugPanel::getToggleableDebugStack()`~ |
|---|
| 235 |
|
|---|
| 236 |
Similar ao `getToggler()`, o `getToggleableDebugStack()` processa uma seta clicável |
|---|
| 237 |
que alterna a exibição de um conjunto de conteúdos. Neste caso, o conjunto de conteúdo é |
|---|
| 238 |
um *debug stack trace*. Esta função é útil se você precisar exibir resultados de log |
|---|
| 239 |
para uma classe personalizada. Por exemplo, suponha que realizamos alguns logs personalizado em |
|---|
| 240 |
uma classe chamada `myCustomClass`: |
|---|
| 241 |
|
|---|
| 242 |
[php] |
|---|
| 243 |
class myCustomClass |
|---|
| 244 |
{ |
|---|
| 245 |
public function doSomething() |
|---|
| 246 |
{ |
|---|
| 247 |
$dispatcher = sfApplicationConfiguration::getActive() |
|---|
| 248 |
->getEventDispatcher(); |
|---|
| 249 |
|
|---|
| 250 |
$dispatcher->notify(new sfEvent($this, 'application.log', array( |
|---|
| 251 |
'priority' => sfLogger::INFO, |
|---|
| 252 |
'Beginning execution of myCustomClass::doSomething()', |
|---|
| 253 |
))); |
|---|
| 254 |
} |
|---|
| 255 |
} |
|---|
| 256 |
|
|---|
| 257 |
Como exemplo, vamos exibir uma lista das mensagens de log relacionados à |
|---|
| 258 |
`MyCustomClass` completo com *debug stack trace* para cada um. |
|---|
| 259 |
|
|---|
| 260 |
[php] |
|---|
| 261 |
public function getPanelContent() |
|---|
| 262 |
{ |
|---|
| 263 |
// retrieves all of the log messages for the current request |
|---|
| 264 |
$logs = $this->webDebug->getLogger()->getLogs(); |
|---|
| 265 |
|
|---|
| 266 |
$logList = ''; |
|---|
| 267 |
foreach ($logs as $log) |
|---|
| 268 |
{ |
|---|
| 269 |
if ($log['type'] == 'myCustomClass') |
|---|
| 270 |
{ |
|---|
| 271 |
$logList .= sprintf('<li>%s %s</li>', |
|---|
| 272 |
$log['message'], |
|---|
| 273 |
$this->getToggleableDebugStack($log['debug_backtrace']) |
|---|
| 274 |
); |
|---|
| 275 |
} |
|---|
| 276 |
} |
|---|
| 277 |
|
|---|
| 278 |
return sprintf('<ul>%s</ul>', $logList); |
|---|
| 279 |
} |
|---|
| 280 |
|
|---|
| 281 |
 |
|---|
| 282 |
|
|---|
| 283 |
>**NOTE** |
|---|
| 284 |
>Mesmo sem a criação de um painel personalizado, as mensagens de log para `myCustomClass` |
|---|
| 285 |
>seriam exibidas no painel de logs. A vantagem aqui é simplesmente |
|---|
| 286 |
>reunir este subconjunto de mensagens de log em um local e controlar a sua saída. |
|---|
| 287 |
|
|---|
| 288 |
### ~`SfWebDebugPanel::formatFileLink()`~ |
|---|
| 289 |
|
|---|
| 290 |
A possibilidade de clicar em arquivos na *Barra de Ferramentas para Debug Web* e |
|---|
| 291 |
abrir no seu editor de texto preferido é outra novidade no symfony 1.3. Para obter mais informações, consulte o |
|---|
| 292 |
artigo ["What's new"](http://www.symfony-project.org/tutorial/1_3/en/whats-new) |
|---|
| 293 |
para o symfony 1.3. |
|---|
| 294 |
|
|---|
| 295 |
Para ativar esse recurso para qualquer caminho de arquivo em particular, o `formatFileLink()` deve |
|---|
| 296 |
ser utilizado. Além do arquivo em si, poderá ser, opcionalmente, direcionado para uma linha exata. |
|---|
| 297 |
Por exemplo, o seguinte código deverá linkar para a linha 15 do `config/ProjectConfiguration.class.php`: |
|---|
| 298 |
|
|---|
| 299 |
[php] |
|---|
| 300 |
public function getPanelContent() |
|---|
| 301 |
{ |
|---|
| 302 |
$content=''; |
|---|
| 303 |
|
|---|
| 304 |
//... |
|---|
| 305 |
|
|---|
| 306 |
$path = sfConfig::get('sf_config_dir') . '/ ProjectConfiguration.class.php'; |
|---|
| 307 |
$content .= $this->formatFileLink($path, 15, 'Project Configuration'); |
|---|
| 308 |
|
|---|
| 309 |
return $content; |
|---|
| 310 |
} |
|---|
| 311 |
|
|---|
| 312 |
Tanto o segundo argumento (número da linha) quanto o terceiro argumento (o link de texto) são |
|---|
| 313 |
opcionais. Se nenhum argumento de "texto do link" for especificado, o caminho do arquivo será mostrado |
|---|
| 314 |
como o texto do link. |
|---|
| 315 |
|
|---|
| 316 |
>**NOTE** |
|---|
| 317 |
>Antes de testar, verifique se você configurou o novo recurso de link de arquivo. Este |
|---|
| 318 |
>recurso pode ser configurado através da chave `sf_file_link_format` no settings.yml ou |
|---|
| 319 |
>através da configuração `file_link_format` no |
|---|
| 320 |
>[xdebug](http://xdebug.org/docs/stack_trace#file_link_format). O último |
|---|
| 321 |
>método garante que o projeto não está vinculado à uma IDE específica. |
|---|
| 322 |
|
|---|
| 323 |
Outros truques com a *Barra de Ferramentas para Debug Web* |
|---|
| 324 |
--------------------------------------- |
|---|
| 325 |
|
|---|
| 326 |
Em grande parte, a magia de seu painel de debug web personalizado será formada pelo |
|---|
| 327 |
conteúdo e informações que você decidir mostrar. Há, no entanto, |
|---|
| 328 |
alguns truques mais a explorar. |
|---|
| 329 |
|
|---|
| 330 |
### Removendo os Painéis Padrão |
|---|
| 331 |
|
|---|
| 332 |
Por padrão, o symfony automaticamente carrega vários painéis de debug web em sua |
|---|
| 333 |
*Barra de ferramentas para Debug Web*. Ao utilizar o evento `debug.web.load_panels`, estes painéis padrões |
|---|
| 334 |
também podem ser facilmente removidos. Use a mesma função *listener* declarada |
|---|
| 335 |
anteriormente, mas substitua o corpo com a função `removePanel()`. O seguinte |
|---|
| 336 |
código irá remover o painel `memory` da barra de ferramentas: |
|---|
| 337 |
|
|---|
| 338 |
[php] |
|---|
| 339 |
public static function listenToLoadDebugWebPanelEvent(sfEvent $event) |
|---|
| 340 |
{ |
|---|
| 341 |
$event->getSubject()->removePanel('memory'); |
|---|
| 342 |
} |
|---|
| 343 |
|
|---|
| 344 |
### Acessando os Parâmetros do Pedido (*Request*) a partir de um Painel |
|---|
| 345 |
|
|---|
| 346 |
Uma das coisas mais comumente necessárias dentro de um painel de debug são os parâmetros |
|---|
| 347 |
do pedido. Digamos, por exemplo, que você deseja exibir informações de |
|---|
| 348 |
um banco de dados sobre um objeto `Event` no banco de dados com base no parâmetro |
|---|
| 349 |
do pedido `event_id`: |
|---|
| 350 |
|
|---|
| 351 |
[php] |
|---|
| 352 |
$parameters = $this->webDebug->getOption('request_parameters'); |
|---|
| 353 |
if(isset($parameters['event_id'])) |
|---|
| 354 |
{ |
|---|
| 355 |
$event = Doctrine::getTable('Event')->find($parameters['event_id']); |
|---|
| 356 |
} |
|---|
| 357 |
|
|---|
| 358 |
### Ocultar um Painel Condicionalmente |
|---|
| 359 |
|
|---|
| 360 |
Às vezes, o painel pode não possuir informação útil para mostrar para a |
|---|
| 361 |
solicitação atual. Nessas situações, você pode optar por esconder o seu painel |
|---|
| 362 |
completamente. Vamos supor que, no exemplo anterior, que o painel personalizado |
|---|
| 363 |
não apresente nenhuma informação a não ser o parâmetro `event_id` do pedito. |
|---|
| 364 |
Para ocultar o painel, basta não retornar conteúdo no método `getTitle()`: |
|---|
| 365 |
|
|---|
| 366 |
[php] |
|---|
| 367 |
public function getTitle() |
|---|
| 368 |
{ |
|---|
| 369 |
$parameters = $this->webDebug->getOption('request_parameters'); |
|---|
| 370 |
if(!isset($parameters[´event_id´])) |
|---|
| 371 |
{ |
|---|
| 372 |
return; |
|---|
| 373 |
} |
|---|
| 374 |
|
|---|
| 375 |
return '<img src="/acWebDebugPlugin/images/documentation.png" alt="Documentation Shortcuts" height="16" width="16" /> docs'; |
|---|
| 376 |
} |
|---|
| 377 |
|
|---|
| 378 |
Reflexões finais |
|---|
| 379 |
-------------- |
|---|
| 380 |
|
|---|
| 381 |
A *Barra de ferramentas para Debug Web* existe para tornar a vida do desenvolvedor mais simples, porém é mais |
|---|
| 382 |
do que uma exposição passiva da informação. Ao adicionar painéis personalizados de debug, o |
|---|
| 383 |
potencial da *Barra de ferramentas para Debug Web* é limitado apenas pela imaginação do |
|---|
| 384 |
desenvolvedor. O [ac2009WebDebugPlugin](http://www.symfony-project.org/plugins/ac2009WebDebugPlugin) |
|---|
| 385 |
inclui apenas alguns dos painéis que poderiam ser criados. Sinta-se livre para criar |
|---|
| 386 |
os seus próprios. |
|---|