terça-feira, 15 de janeiro de 2013

Tutorial CodeIgniter: os helpers


Os helpers do CodeIgniter são uns kits de ferramentas de funções que tornam a vida mais fácil. Estão localizados no diretório system/helpers/. Entre os helpers disponíveis, há para tabelas, captchas, cookies, emails, formulários…

O helper inflector (inflector_helper.php) define por exemplo as funções singular (que retorna a forma singular da palavra passada como parâmetro) e plural (que retorna a forma plural da palavra passada como parâmetro), mas só para o inglês. Veremos neste tutorial como incluir o suporte para outro idioma, aqui o português, à função plural e assim permitir a pluralização do português.

Expandir um helper


Os helpers não são classes, assim que não se pode herdar deles, mas o CodeIgniter oferece uma maneira para os expandir, isto é, para adicionar outras funções a eles.

Para isto, é necessário criar o arquivo equivalente no diretório application/helpers/ usando o nome do arquivo helper prefixado por MY_, por exemplo, MY_inflector_helper.php.

Este prefixo é configurável no arquivo application/config/config.php, através da variável $config['subclass_prefix'].

A chamada ao helper faz-se no controlador:
$this->load->helper('inflector');
$data['man_singular'] = plural('man');
$data['man_plural'] = plural('man');
A visão contém o código seguinte:
1 <?= $man_singular ?>, 2 <?= $man_plural ?>
Que vai mostrar: 1 man, 2 men

Regras de pluralização


Vamos agora criar o arquivo seguinte: application/helpers/MY_inflector_helper.php
function plural_en($str, $force = FALSE)
{
 $result = strval($str);

 $plural_rules = array(
  // always singular
  '/^(benshi|otaku|samurai)$/' => '\1',
  '/^(bison|deer|fish|moose|pike|plankton|salmon|sheep|swine|trout)$/' => '\1',
  '/^(blackfoot|cherokee|chinese|comanchee|cree|delaware|hopi|kiowa|navajo|ojibwa|sioux|swiss|zuni)$/' => '\1',
  // -um => -a (addendum)
  '/^(addend|corrigend|dat|for|medi|millenni|ov|spectr)um$/' => '\1a',
  // -a => -ae (formula)
  '/^(alumn|formul)a$/' => '\1ae',
  // -u => -i (alumnus)
  '/^(alumn|foc|fung|incub|radi|styl|succub)us$/' => '\1i',
  // -on => -a (automaton)
  '/^(automat|criteri|phenomen|polyhedr)on$/' => '\1a',
  // - => -en (ox)
  '/^(ox)$/' => '\1en',
  // -ouse => -ice (mouse)
  '/([m|l])ouse$/' => '\1ice',
  // -ix/-ex => -ices (matrix)
  '/(matr|vert|ind)ix|ex$/' => '\1ices',
  // - => -es (search)
  '/(x|ch|ss|sh)$/' => '\1es',
  // irregulars ending with -y
  '/^penny$/' => 'pence',
  '/^passerby$/' => 'passersby',
  // -y => -ies (query)
  '/([^aeiouy]|qu)y$/' => '\1ies',
  // -hive => -hives (archive)
  '/(hive)$/' => '\1\2s',
  // -f => -ves (half, wife)
  '/(?:([^f])fe|([lr])f)$/' => '\1\2ves',
  // -sis => -ses (basis)
  '/sis$/' => 'ses',
  // -us => -era
  '/^viscus$/' => 'viscera',
  // -o => -oes (tomato)
  '/(buffal|tomat)o$/' => '\1oes',
  // -s => -ses
  '/(bu|campu)s$/' => '\1\2ses', // bus, campus
  '/(alias|census|octopus|platypus|prospectus|status|virus)/' => '\1es', // alias
  // -is => -es (axis)
  '/(ax|cris|test)is$/' => '\1es',
  // -uk => -uit
  '/^(in|inuksh)uk$/' => '\1uit',
  // person => people
  '/(p)erson$/' => '\1eople',
  '/^corpus$/' => 'corpora',
  '/^genus$/' => 'genera',
  '/^foot$/' => 'feet',
  '/^goose$/' => 'geese',
  '/^hoof$/' => 'hooves',
  '/^leaf$/' => 'leaves',
  '/^tooth$/' => 'teeth',
  // compound
  '/^aide-de-camp$/' => 'aides-de-camp',
  '/^director general$/' => 'directors general',
  '/^man-/' => 'men-\2',
  '/^manservant$/' => 'menservants',
  '/^minister-president$/' => 'ministers-president',
  '/^(daughter|father|mother|son)-in-law$/' => '\1s-in-law',
  // man => men
  '/(m)an$/' => '\1en',
  // child => children
  '/(c)hild$/' => '\1hildren',
  // no change (compatibility)
  '/s$/' => 's',
  '/$/' => 's',
 );

 foreach ($plural_rules as $rule => $replacement)
 {
  if (preg_match($rule, $result))
  {
   $result = preg_replace($rule, $replacement, $result);
   break;
  }
 }

 return $result;
}

if ( ! function_exists('plural_pt'))
{
 function plural_pt($str, $force = FALSE)
 {
  $result = strval($str);

  $plural_rules = array(
   // -ão => ães (alemão, cão, capitão, pão)
   '/^(alem|c|capit|p)ão$/u' => '\1ães',
   // -ão => ãos (grão, irmão, mão, cristão, pagão, órfão, órgão, sótão)
   '/^(crist|gr|irm|m|pag|órf|órg|sót)ão$/u' => '\1ãos',
   // -ão => -ões (ação)
   '/ão$/u' => 'ões',
   // avós
   '/^avô$/u' => 'avós',
   // vowel
   '/(a|ã|e|é|i|o|ó|u)$/u' => '\1s',
   // change of tonic accent
   '/^carácter$/u' => 'caracteres', // European Portuguese
   '/^caráter$/u' => 'carateres', // Brazilian Portuguese
   '/^incrível$/u' => 'incríveis',
   '/^réptil$/u' => 'répteis',
   // consonant
   '/(b|c|d|f|g|j|k|n|p|r|s|t|v|w|x|z)$/u' => '\1es',
   // -m => -ns (jardins)
   '/m$/u' => 'ns',
   // change of accent: -el => -éis (hotéis, papéis, pastéis)
   '/el$/u' => 'éis',
   // -il => -is (barris)
   '/^barril$/u' => 'barris',
   // -ol => -óis (caracol)
   '/ol$/u' => '\1óis',
   // -l => -is (jornal)
   '/l$/u' => 'is',
  );

  foreach ($plural_rules as $rule => $replacement)
  {
   if (preg_match($rule, $result))
   {
    $result = preg_replace($rule, $replacement, $result);
    break;
   }
  }

  return $result;
 }
}
Duas observações devem ser feitas. Em primeiro lugar, «sobrecarregamos» a função plural do system/helpers/inflector_helper.php adicionando o código de idioma (ou seja plural_en) para homogeneizar as chamadas. Poderíamos ficar por aqui, mas reescrevemos esta função, a função fornecida por padrão abrange apenas muito poucos casos. Depois, acrescentamos às expressões regulares o parâmetro u para Unicode (e armazenamos o arquivo codificado em UTF-8) quando necessário, isto é, para as regras de pluralização do português neste exemplo.

A chamada é feita da mesma maneira que no controlador: o primeiro arquivo carregado é o helper genérico, em seguida o CodeIgniter carrega automaticamente o helper aplicativo.

Testes de pluralização


Para facilitar a compreensão deste exemplo, não usamos um arquivo de idioma. No entanto, usamos um método de teste chamado inflector no controlador, que chama uma visão especial (application/views/inflector.php). Este método é usado para testar a pluralização: se um caso particular for esquecido, sua inclusão neste método permite validar o funcionamento correto do método plural_pt, bem como uma modificação do mesmo método plural_pt permanece testável com este conjunto de testes.
    public function inflector()
    {
  $test_data = array(
   'en' => array(
    'ability'=>'abilities', 'addendum'=>'addenda', 'agency'=>'agencies', 'aide-de-camp'=>'aides-de-camp', 'alias'=>'aliases', 'alumna'=>'alumnae', 'alumnus'=>'alumni', 'archive'=>'archives', 'automaton'=>'automata', 'axis'=>'axes', 'basis'=>'bases', 'benshi'=>'benshi', 'bison'=>'bison', 'blackfoot'=>'blackfoot', 'buffalo'=>'buffaloes', 'bus'=>'buses', 'calf'=>'calves', 'campus'=>'campuses', 'census'=>'censuses', 'cherokee'=>'cherokee', 'child'=>'children', 'chinese'=>'chinese', 'comanchee'=>'comanchee', 'corpus'=>'corpora', 'corrigendum'=>'corrigenda', 'cree'=>'cree', 'crisis'=>'crises', 'criterion'=>'criteria', 'datum'=>'data', 'deer'=>'deer', 'delaware'=>'delaware', 'diagnosis'=>'diagnoses', 'director general'=>'directors general', 'dwarf'=>'dwarves', 'elf'=>'elves', 'fish'=>'fish', 'focus'=>'foci', 'foot'=>'feet', 'formula'=>'formulae', 'forum'=>'fora', 'fungus'=>'fungi', 'genus'=>'genera', 'goose'=>'geese', 'half'=>'halves', 'hive'=>'hives', 'hoof'=>'hooves', 'hopi'=>'hopi', 'incubus'=>'incubi', 'index'=>'indices', 'inuk'=>'inuit', 'inukshuk'=>'inukshuit', 'iroquois'=>'iroquois', 'kiowa'=>'kiowa', 'knife'=>'knives', 'leaf'=>'leaves', 'life'=>'lives', 'louse'=>'lice', 'man'=>'men', 'man-about-town'=>'men-about-town', 'man-of-war'=>'men-of-war', 'manservant'=>'menservants', 'matrix'=>'matrices', 'medium'=>'media', 'millennium'=>'millennia', 'minister-president'=>'ministers-president', 'moose'=>'moose', 'mouse'=>'mice', 'navajo'=>'navajo', 'octopus'=>'octopuses', 'ojibwa'=>'ojibwa', 'orange'=>'oranges', 'otaku'=>'otaku', 'ox'=>'oxen', 'ovum'=>'ova', 'passerby'=>'passersby', 'penny'=>'pence', 'person'=>'people', 'phenomenon'=>'phenomena', 'pike'=>'pike', 'plankton'=>'plankton', 'platypus'=>'platypuses', 'policeman'=>'policemen', 'policewoman'=>'policewomen', 'polyhedron'=>'polyhedra', 'postman'=>'postmen', 'prospectus'=>'prospectuses', 'québécois'=>'québécois', 'query'=>'queries', 'radius'=>'radii', 'sabertooth'=>'sabertooths', 'safe'=>'saves', 'salesperson'=>'salespeople', 'salmon'=>'salmon', 'samurai'=>'samurai', 'seaman'=>'seamen', 'series'=>'series', 'sheep'=>'sheep', 'sioux'=>'sioux', 'son-in-law'=>'sons-in-law', 'species'=>'species', 'spectrum'=>'spectra', 'spokesman'=>'spokesmen', 'status'=>'statuses', 'succubus'=>'succubi', 'stylus'=>'styli', 'swine'=>'swine', 'swiss'=>'swiss', 'tenderfoot'=>'tenderfoots', 'testis'=>'testes', 'tête-à-tête'=>'tête-à-têtes', 'tomato'=>'tomatoes', 'tooth'=>'teeth', 'trout'=>'trout', 'vertex'=>'vertices', 'virus'=>'viruses', 'viscus'=>'viscera', 'wife'=>'wives', 'woman'=>'women', 'zuni'=>'zuni'
    ),
   'pt' => array('ação'=>'ações', 'açúcar'=>'açúcares', 'álbum'=>'álbuns', 'alemão'=>'alemães', 'animal'=>'animais', 'avô'=>'avós', 'azul'=>'azuis', 'barril'=>'barris', 'café'=>'cafés', 'cão'=>'cães', 'capitão'=>'capitães', 'caracol'=>'caracóis', 'caráter'=>'carateres', 'carácter'=>'caracteres', 'carro'=>'carros', 'céu'=>'céus', 'cristão'=>'cristãos', 'estação'=>'estações', 'estudante'=>'estudantes', 'flor'=>'flores', 'grão'=>'grãos', 'homem'=>'homens', 'hotel'=>'hotéis', 'imagem'=>'imagens', 'incrível'=>'incríveis', 'irmã'=>'irmãs', 'irmão'=>'irmãos', 'jardim'=>'jardins', 'jornal'=>'jornais', 'leão'=>'leões', 'mãe'=>'mães', 'mão'=>'mãos', 'museu'=>'museus', 'nó'=>'nós', 'órfão'=>'órfãos', 'órgão'=>'órgãos', 'pagão'=>'pagãos', 'pão'=>'pães', 'papel'=>'papéis', 'pastel'=>'pastéis', 'paz'=>'pazes', 'praia'=>'praias', 'réptil'=>'répteis', 'restaurante'=>'restaurantes', 'som'=>'sons', 'sótão'=>'sótãos', 'táxi'=>'táxis'
    )
  );

  $this->load->helper('inflector');
  $results  = array();
  $all_passed = array();
  foreach ($test_data as $lang => $test_lang_data)
  {
   $all_passed[$lang] = true;
   foreach ($test_lang_data as $singular => $plural)
   {
    eval('$pluralized = plural_' . $lang . '($singular);');
    $results[$lang][] = array(
     'singular'  => $singular,
     'plural' => $pluralized,
     'expected' => $plural,
     'result' => ($pluralized == $plural)
     );
    $all_passed[$lang] = $all_passed[$lang] && ($pluralized == $plural);
   }
  }

  $data['results']  = $results;
  $data['all_passed'] = $all_passed;
  $data['languages'] = array('en'=>'English', 'pt'=>'Portuguese');
  $this->load->view('inflector', $data);
 }
Com a visão seguinte: application/views/inflector.php
<!DOCTYPE html>
<html>
<head>
    
    Inflector test
</head>
<body>

<?php foreach ($results as $lang => $lang_results) { ?> Test inflector in <?= $languages[$lang] ?>
<?php if ($all_passed[$lang]) { ?> All tests passed. <?php } else { ?> <?php foreach ($lang_results as $k => $result) { ?> <?php if ($result['result'] !== true) { ?> <?php } ?> <?php } ?>
Singular Plural Expected
<?= $result['singular'] ?> <?= $result['plural'] ?> <?= $result['expected'] ?>
<?php } ?>

<?php } ?>
</body> </html>

Em resumo


Assim, estendemos o helper inflector do CodeIgniter para permitir que ele suporta mais idiomas. E mais especificamente aqui, para gerenciar a pluralização do português, e melhorando a do inglês. Outros helpers estendem-se da mesma maneira para acrescentar a estas caixas de ferramentas genéricas as funções específicas úteis para a sua aplicação.


Tutoriel CodeIgniter : étendre les helpers (em francês)
CodeIgniter tutorial: how to extend a helper (em inglês)
Tutorial CodeIgniter: los helpers (em espanhol)

Sem comentários:

Enviar um comentário