Tasso & As Vozes

Tasso & As Vozes

Um lugar calmo e tranquilo onde dialogo com as vozes que habitam a minha cabeça

Notas sobre "The Great CoffeeScript to Typescript Migration of 2017"

· 5 minutos

Fonte: https://dropbox.tech/frontend/the-great-coffeescript-to-typescript-migration-of-2017

Pré-história: adoção de CoffeeScript

  • Em 2012, o Dropbox era uma startup com 150 funcionários.
  • ES5 e jQuery dominavam o desenvolvimento JavaScript.
  • A codebase consistia de cerca de 100 mil linhas de JavaScript empacotada por simples concatenação de arquivos.
  • Dois engenheiros migraram tudo para CoffeeScript em uma semana.
  • Em 2013, adotaram o sistema de módulos RequireJS (AMD) já que CommonJS era restrita a Node e, portanto, não projetada para uso no browser.

Rumores de uma mudança de linguagem

  • Ao final de 2015, ES6 já tinha mais funcionalidades que CoffeeScript.
  • Alguns times começaram a adotar ES6 em projetos isolados.
  • A codebase era difícil de manter pois as técnicas de codificação defensiva eram insuficientes e prejudicavam a legibilidade do código.
  • Por CoffeeScript ser menos estrita que Python no uso de whitespaces, alguns engenheiros verificavam manualmente o código JavaScript gerado; é citado um bug sério no outono de 2013 causado por um espaço em branco mal-posicionado.
  • Uma pesquisa interna em novembro de 2015 verificou que 62% dos desenvolvedores gostariam de mudar a linguagem utilizada.
  • Problemas com CoffeeScript apontados:
    • Falta de delimitadores
    • Syntactic sugar muito opinativo
    • Falta de suporte da comunidade
    • Leitura difícil pois a sintaxe é muito densa
    • Sujeita a erros por causa de ambiguidades sintáticas
  • O ferramental de TypeScript parecia melhor que vanilla ES6 + Flow no final de 2015.
  • No primeiro semestre de 2016, um engenheiro integrou Babel e TypeScript.
  • Tanto o time quanto a codebase (330 mil linhas) haviam crescido significativamente para uma migração indolor.

Um plano de migração otimista

  • Foram estabelecidas cinco milestones a serem cumpridas até julho de 2017: M1. Suporte básico a TypeScript com interoperabilidade com CoffeeScript e ferramentas de i18n, lint e testes; M2. Documentar boas práticas e guias de migração e migrar as principais bibliotecas e módulos para TypeScript ser a linguagem padrão de desenvolvimento; M3. Consolidar a milestone anterior migrando o restante de módulos e bibliotecas; M4. Migrar os arquivos mais modificados ao longo do tempo no projeto; M5. Remover o compilador de CoffeeScript.
  • M1, M2 e M3 foram executadas no segundo semestre de 2016.
  • M4 e M5 foram mais problemáticas; esperava-se que o código ficaria a cargo dos times que o desenvolveram originalmente.
  • 20% do time de produto foi alocado para trabalhar nas "fundações" da codebase.

Interoperabilidade de CoffeeScript/TypeScript

  • Para cada arquivo de CoffeeScript (*.coffee), foi criado um arquivo de declarações (*.d.ts) exportando os módulos como any.
  • Módulos em TS com export default value; eram importados nos módulos AMD como objetos do tipo { default: value }; named exports foram migrados com poucos problemas.
  • Em módulos cujos exports eram dinamicamente determinados, exportou-se todos os identificadores possíveis mas com valores undefined dinamicamente determinados.

Banindo novos arquivos em CoffeeScript

  • Foi criado um teste automático para barrar a adição de novos arquivos.
  • Este teste quebrou quando uma migração paralela para Bazel como sistema de build foi executada, pois a lista de arquivos *.coffee estava vazia.
  • Como aprendizado, testes passaram a fazer asserções em suposições (neste caso, de que a lista nunca seria vazia).
  • A redução da whitelist de arquivos permitidos gerava pequenos atrasos no code review.

Experiência inicial: não perdemos o syntactic sugar do CoffeeScript

  • A perda de optional chaining e nullish coalescing foi compensada com a adição de tipagem.

Prioridades concorrentes

  • No final de 2016, foi criado um time para redesign e reescrita do website em React ("Maestro").
  • O time do Maestro não conseguiu cumprir o prazo do primeiro trimestre, entregando ao término do segundo o projeto completo em React e TypeScript.
  • A lista de arquivos que deveria ser eliminada na M4 estagnou em 100 arquivos, mas na prática ainda haviam 2000 arquivos em CoffeeScript com manutenção constante.

Adiando a M5

  • Interpretou-se erroneamente que os scripts restantes deveriam ser substituídos pelos arquivos compilados, causando problemas com lint e i18n.
  • Não ficou claro que o objetivo da M5 era reduzir o custo (já pago) de manter o ferramental de CoffeeScript e TypeScript.
  • A migração perdeu seu ETA.
  • A estimativa inicial da migração era de 1000 linhas por dia demandando um ano de trabalho de um único engenheiro; na prática, 100 linhas por dia eram convertidas, significando um tempo de 10 anos ou o trabalho de 10 engenheiros em um ano.
  • Não havia clareza sobre o que era o trabalho "fundamental" que demandaria 20% do time: se infraestrutura ou pagamento de dívida técnica.
  • Uma migração de sistemas em produção para uma nova distribuição do Ubuntu tomou boa parte da mão-de-obra de infraestrutura.

Um novo plano com decaffeinate

  • Em janeiro de 2017, foi sugerido o uso de decaffeinate para as conversões.
  • Uma função de ordenação com i18n não-testada quebrou completamente uma página no browser Safari.
  • A longa lista de bugs semelhantes no decaffeinate obrigou se adotar uma conversão mista (manual e automática) que eventualmente ocasiona erros a nível de semântica do código.
  • Seis meses depois, decaffeinate parecia suficientemente maduro em comparação à conversão manual.
  • O time entrou em acordo: TypeScript apenas com o tipo any era preferível a CoffeeScript não-tipado e habilitaria os times a introduzirem tipos no seu próprio ritmo.

Um plano em duas fases

  • A pipeline de migração de código consistia de múltiplas etapas:
    1. Conversão para JavaScript ES6;
    2. Transformações com codemod, especialmente de funções com binding para arrow functions;
    3. Transformação da API legada de React para JSX;
    4. Conversão de AMD para módulos ES (especial cuidado com exports);
    5. Anotação de parâmetros de função com any;
    6. Adição de declaração de membros de classes;
    7. Anotação de componentes com tipos específicos de React;
    8. Adição de comentário explicado como olhar o código CoffeeScript original através do git;
  • A etapa final de correção dos tipos envolveu escrever scripts para interpretar os erros vindos do type checking e aplicar as correções automaticamente.

Mantendo o foco

  • O compromisso de manter uma ferramenta suficientemente boa tornou trivial resolver manualmente problemas como variáveis mortas.
  • Agrupar a quantidade de erros do tsc por código de erro gerou uma métrica para a migração e confiança de que estavam na direção correta.
  • A taxa de erros por arquivo ficou entre 0,5 e 1.

Ganhando confiança nas suas ferramentas

  • Bugs pré-existentes eram capturados rodando testes antes e após as conversões.
  • Se um erro ocorria em muitos lugares, uma asserção era adicionada à pipeline de migração em vez de realizar correção manual.
  • Um bug notável destes é relacionado a CoffeeScript não ter suporte a variable shadowing.
  • O código foi inteiramente portado para o strict mode sem problemas como atribuição em propriedades read-only.
  • As primeiras conversões em massa foram feitas nos testes automáticos.
  • Uma boa configuração do itest permitia checar rapidamente quais modificações eram a origem dos problemas.
  • Foi importante ter rigor na escrita das traduções de código, cobrindo todos os corner cases e explicitando quais casos eram ignorados.

A última parte

  • Nas últimas semanas as ferramentas eram capazes de converter entre 100 e 200 arquivos numa única passagem.
  • Um dos truques para iterar rapidamente é rodar o typecheck incremental (tsc --noEmit --watch).
  • O número de arquivos em CoffeeScript era mantido atualizado num quadro branco.
  • Apenas dois bugs entraram em produção.
  • Ao time mais resistente à mudança foi prometido que erros ficariam a cargo dos engenheiros envolvidos na migração.
  • Ao se admitir como produto um código TypeScript não-idiomático, foram gastos 2 meses de trabalho de três engenheiros (cerca de 19 engenheiros-semana).
  • "Nós devemos economizar nosso capital político e organizacional com trabalhos que não podemos automatizar para todos"; assim, a conversão manual foi descartada.
  • Atualmente, o time da Dropbox mantém 2kk linhas de TypeScript.

Uma sugestão para simular um e-paper

· 1 minuto

É necessário o alinhamento de software e hardware para ficar perfeito, mas utilizar apenas um deles já confere uma boa aproximação do efeito desejado.

Software

O fundo deve ser um tom de cinza claro como rgb(236, 236, 236) ou mais claro, enquanto a cor do texto deve ser 25% a 30% mais clara que o preto.

A fonte de corpo de texto deve ter uma aparência suave. Há uma preferência para fontes serifadas porém acredito que isto não deveria ser uma regra geral. A tipografia deve acompanhar o significado que o texto transmite onde é possível ser determinado e definida pelo leitor/usuário quando não é inferível o tom de voz do texto. Particularmente acredito que fontes serifadas são ótimas para textos de estrutura mais linear -- como ficções e artigos com poucos recursos gráficos. Uma fonte mono-espaçada seria aconselhável quando o texto deve ser escaneado pelo leitor, e.g., tabelas de dados e índices.

O espaçamento de linha ideal está entre 125% e 150% do tamanho da fonte. Em geral, uma margem mínima de 10pt em todas as direções de qualquer bloco de texto é suficiente para demarcar as divisões semânticas do texto.

Hardware

Uma película protetora anti-reflexiva combinada com a variação de brilho entre 45% para interiores e 75% para iluminação exterior.

Dark mode não-adaptativo: uma escolha puramente estética

· 1 minuto

Advoga-se por aí que todas as interfaces digitais devem ao menos prover um modo escuro, o famigerado dark mode. Não sei ao certo a origem dessa "necessidade" pós-2018, mas não é difícil conjecturar que deva ter surgido no meio do desenvolvimento de software. A justificativa? Descanso visual por uso prolongado de displays.

Acredito que a necessidade real seja outra; me baseio na escolha que muitos conhecidos fotossensíveis têm por trabalhar em ambientes escuros apesar de usar displays com nível de brilho alto, o que, além de destruir o ciclo circadiano, causa cansaço da musculatura complexa dos olhos.

O descanso deve ser para outro órgão: o cérebro do programador, que deve interpretar sintaticamente blocos de texto diferenciando palavras-chaves, identificadores, delimitadores, literais. O modo escuro seria um acessório ao realce de sintaxe, uma vez que o texto colorido é melhor diferenciado contra um fundo preto ou cinza escuro.

Para textos lineares, como os de literatura ou com poucos elementos gráficos e tópicos, tons de sépia e tons simulando o e-ink são muito mais adequados para relaxamento visual. Desta forma, o modo do tema deveria ser adaptativo ao conteúdo, mas ainda prefere-se que ele seja definido pelo contexto, como as cores do sistema ou o modo noturno.

Instalando o Composer... com o Composer

· 1 minuto

Não apenas o Composer, mas uma série de ferramentas para desenvolvimento em PHP pode ser mantida atualizada utilizando o próprio Composer. Uma vez que se assume que essas ferramentas são utilizadas em todos os projetos, muitas vezes a adição delas como dependência de desenvolvimento (e.g composer require --dev phpunit/phunit) torna-se desnecessária projeto a projeto, podendo ser instalada globalmente. Aqui vai a sequência de passos – realizadas numa máquina com Ubuntu – que demonstra como isso é possível:

  1. Faça download do arquivo https://getcomposer.org/composer.phar.

    $ wget https://getcomposer.org/composer.phar
    
  2. Instale o Composer como dependência global com o próprio Composer.

    $ php composer.phar global require --dev composer/composer
    
  3. Adicione o diretório global de binários à variável PATH. No Ubuntu, isso pode ser feito adicionando a seguinte linha ao arquivo ~/.bashrc

    export PATH="$HOME/.composer/vendor/bin:$PATH"
    
  4. Remova o arquivo PHAR – não é mais necessário.

    $ rm composer.phar
    

Pronto. O comando composer já está disponível, mas sem o comando self-update embutido. Para atualizá-lo (junto com outros binários instalados), basta executar:

$ composer global update

Como instalar e usar o Composer?

· 2 minutos

Em projetos PHP atuais, é praticamente impossível um desenvolvedor fazer tudo do zero. Sempre existe um framework, uma library (biblioteca de código) ou até uma "funçãozinha" que outros devs escreveram e que nós utilizamos para entregar tudo dentro de prazos.

Alguns exemplos? Laravel, Zend Framework, Yii, Symfony, Climate etc.

Cada vez que utilizamos algo do tipo, estamos gerando uma dependência no projeto. Para que a nossa aplicação funcione, todas as dependências devem ser resolvidas, isto é, o código delas deve estar acessível.

O que é?

O Composer é um gerenciador de dependências escrito em e escrito para PHP. Através dele, todas as dependências de um projeto são baixadas da Internet automaticamente na forma de pacotes e corretamente disponibilizadas num diretório. Inclusive com direito a autoloader.

Como instalar?

O Composer é disponibilizado como um arquivo PHAR (PHP archive) simples, mas para evitar múltiplas cópias dele espalhadas por aí, o ideal é instalá-lo globalmente.

Debian/Ubuntu/Linux Mint

Se a ferramenta de linha de comando do PHP não está instalada,

$ sudo apt-get install php5-cli

Então instalar é fácil como digitar no terminal isto:

$ php -r "readfile('https://getcomposer.org/installer');" | sudo php -- --install-dir=/usr/bin --filename=composer

Para mais detalhes, consulte: http://getcomposer.org/doc/00-intro.md#installation-nix

Windows

Baixe e execute o instalador oficial.

Para mais detalhes, consulte: http://getcomposer.org/doc/00-intro.md#installation-windows

Como descrevar as dependências?

Para começar, você precisa descrever quais são as dependências do seu projeto em algum lugar. Isso é feito no arquivo composer.json, que agora também deve fazer parte do seu código. Como a extensão sugere, ele deve ter um conteúdo JSON válido. Vamos começar com um arquivo simples:

{
  "name": "minha-empresa/meu-projeto",
  "description": "Meu primeiro projeto com Composer",
  "license": "proprietary"
}

Os itens name e description são obrigatórios e license é recomendado. Você pode verificar se tudo está ok executando o comando:

$ composer validate
./composer.json is valid

Agora podemos adicionar algumas dependências.

{
  "name": "minha-empresa/meu-projeto",
  "description": "Meu primeiro projeto com Composer",
  "license": "proprietary",
  "require": {
    "yiisoft/yii2": "~2.0.0"
  }
}

Dentro da propriedade require, eu defini que meu projeto tem como dependência o framework Yii.

Por que ele está descrito como yiisoft/yii2? Porque foi assim que a Yiisoft o registrou no Packagist, o principal repositório de pacotes do Composer.

O que significa ~2.0.0? Estamos especificando que queremos qualquer pacote cuja versão seja igual ou superior a 2.0.0 e inferior a 2.1.0. Existem várias formas de descrever as versões que precisamos. As recomendações principais são (1) evitar as versões dev-master, que são baseadas no último código escrito nos pacotes e, portanto, mutáveis; e (2) evitar a tentação de utilizar * para ter sempre a última versão disponível. Existe uma diferença enorme, por exemplo, entre a versão 1.1 e a versão 2.0 do Yii. Atualizar dependências assim significa conviver com o fato de ter que mudar todo seu código quando uma delas for atualizada.

Enfim, como utilizar?

Com tudo isso feito, estamos a um mero passo de ter o Yii no nosso projeto:

$ composer install

O Composer vai verificar os repositórios, como o Packagist; verificar se possuem os pacotes necessários; baixá-los; adicioná-los ao diretório vendor do projeto; e criar um arquivo composer.lock, que é uma versão superdetalhada do composer.json, descrevendo até todas as subdepências (i.e., as dependências das dependências do projeto) e versões baixadas.

Pronto.

Para atualizar as dependências ou reinstalá-las, utilize:

$ composer update

Git para times de todos os tamanhos

· 6 minutos

Recentemente, o Tuts+ lançou um artigo sugerindo um workflow baseado no conhecido Git-Flow de Vincent Driessen. Gostei das sugestões dadas e resolvi escrever um pouco sobre esse assunto adicionando uma pitada da minha experiência com o uso de git em pequenas equipes.

Preciso disso?

Uma vez que superamos as primeiras barreiras de aprendizado e dominamos os conceitos que o git nos traz, como submissões (commits), ramos (branches), repositórios etc., já somos capazes de dominar com relativa facilidade a administração dos nossos próprios repositórios. Entretanto, mesmo nessa situação de controle total, em alguns momentos decidir entre se fazer do jeito A ou fazer do jeito B é melhor. Acredito que esse sentimento está presente quando percorremos o log de commits de um repositório e não encontramos em qual o commit uma determinada modificação foi feita. Quando isso ocorre, notamos pela primeira vez que mensagens de commit devem ser bem escritas para nós mesmos, não apenas para escrevermos um change log pomposo que nem precisamos manter em alguns casos. Mas o que seria uma mensagem de commit bem escrita?

Se esse pequeno problema já é bem incômodo quando trabalhamos sozinhos, imagina o quão crítico ele se torna onde estamos trabalhando em equipe, seja num projeto com colegas de trabalho ou num projeto open source hospedado no GitHub ou outro serviço popular. O que toda ferramenta de controle de versão se propõe a fazer é garantir que, quando vários colaboradores fazem modificações sobre os mesmos arquivos, os conflitos gerados sejam detectados e resolvidos antes de que as mudanças sejam aceitas, e o git trata desses conflitos de uma forma muito conveniente. Segundo seus usuários, muito superior à forma como as demais ferramentas (SourceSafe, CVS, SVN) trabalham. O que não significa, todavia, que o git resolva os conflitos sozinho. Há sempre uma etapa no processo de trabalho em equipe em que o código deve ser revisado e aceito por alguém. Nessa situação, gerenciar vários commits em conflito pode ser algo muito desafiador se não houver uma ordem de trabalho, mesmo em projetos pequenos. Código mal revisado significa adição de bugs no seu software.

Agora vou citar uma série de regras que são interessantes para o gerenciamento dos repositórios git. São sugestões: você pode adicionar, remover ou modificar algumas propostas para que se ajustem ao ritmo de trabalho da sua equipe. E o seu também.

Quais são as regras do jogo?

  1. master é a versão estável
  2. develop é a versão instável
  3. feature-<funcionalidade> é uma funcionalidade nova
  4. fix-<problema> é a correção de um problema
  5. hotfix-<problema> é a correção de um problema no código em master
  6. <funcionalidade> e <problema> são códigos simples
  7. Resolva os conflitos com o branch develop antes de solicitar uma mesclagem
  8. Enviou os commits antes de resolver os conflitos? Mescle develop
  9. O revisor de código deve informar as mesclagens em develop e master
  10. Adicione tags aos commits de mesclagem em master
  11. Use verbos imperativos nas mensagens dos commits
  12. Frase curta + descrição longa

Regra #1 - master é a versão estável

Ian Lollar, o autor do artigo do Tuts+, estabelece essa regra como a principal. Eu concordo totalmente com ele.

Quando alguém clona um repositório, sempre se espera que o código do repositório já está "pronto para uso" (deployable). Se for código compilável, ele pode ser compilado em poucos comandos para gerar um programa estável; se for o código de um website, ele já pode ser enviado para o servidor.

Para quem está acostumado com os ambientes de desenvolvimento, de homologação e de produção, o branch master deve conter o código de produção. Quando algum problema sério ocorre em software em produção, nós temos que agir para enviar correções imediatas (hotfixes) sobre esse código. Afinal, como você vai escrever um hotfix em cima do código que está em desenvolvimento e contém modificações que ainda estão incompletas ou não foram aprovadas para produção?

Regra #2 - develop é a versão instável

Entenda-se que a versão instável de um software é aquela que ainda está incompleta ou não foi aprovada para produção. Incompleta, nesse sentido, significa que ainda não contém todas as funcionalidades que estão planejadas para virar a versão estável e não quer dizer é possui funcionalidades incompletas. Para entender o que é isso, suponha que você está desenvolvendo uma aplicação divida em vários módulos. Cada módulo criado representa uma funcionalidade nova adicionada ao sistema. Logo, cada módulo pronto deve ser adiciona ao branch develop, mas isso não significa que o módulo todo seja empacotado num único commit. Se algum módulo é constituído de várias telas, cada tela pode ser enviada ao repositório como um commit diferente, mas não diretamente ao branch develop.

Mantenha isso em mente quando trabalhar com o branch develop: ele só deve aceitar funcionalidades completas.

Regra #3 - feature-<funcionalidade> é uma funcionalidade nova

Em outras palavras, cada coisa nova que você adiciona ao seu software deve ser gradualmente commitada num branch específico. O prefixo feature- no nome do branch indica que ele contém uma funcionalidade (feature). Uma vez que a funcionalidade está completa e nenhum novo commit for adicionado a esse branch, você pode enviar uma solicitação ao revisor de código do projeto pedindo que ele mescle (merge) os commits no branch develop.

Regra #4 - fix-<problema> é a correção de um problema

Quando um problema é conhecido num projeto e ele não é solucionado pela adição de uma funcionalidade nova logo, um bug , uma correção deve ser enviada para o código presente no branch develop, mas nunca diretamente. Novamente, assim que o código estiver completo no branch, você solicita uma mesclagem.

Regra #5 - hotfix-<problema> é a correção de um problema no código em master

Enquanto os dois tipos de branches anteriormente citados são criados a partir de develop,

$ git checkout -b `feature-<funcionalidade>` `develop`

este tipo especial de branch precisa ser criado a partir do código de produção, presente em master:

$ git checkout -b `hotfix-<problema>` `master`

Regra #6 - <funcionalidade> e <problema> são códigos simples

Complementando as três regras anteriores, é preciso deixar claro que descrever o nome de uma funcionalidade ou de um problema pode ser uma tarefa complicada. Quando a equipe tem acesso a algum gerenciador de incidentes (issue tracker), por exemplo, os bugs são numerados, de forma que os branches são melhor descritos na forma fix-issue-X, onde X é o número do bug.

Alguns exemplos adicionais de branches seriam feature-modulo-usuarios, fix-bug-323, feature-pagina-sobre, hotfix-issue-23 etc. Tenha em mente que isso deve ser acordado entre todos os membros da equipe, não apenas de ciência do revisor de código.

Regra #7 - Resolva os conflitos com o branch develop antes de solicitar uma mesclagem

Suponha que você e um colega desejam criar duas funcionalidades diferentes, A e B. Você vai ficar com a funcionalidade A e, portanto, criará um branch chamado feature-A no seu repositório local. Seu colega criará um branch feature-B no repositório dele. Vocês seguem trabalhando, mas seu colega termina o trabalho antes e solicita uma mesclagem em seguida para o revisor de código. O revisor verifica que está tudo certo e mescla os commits do branch feature-B no branch develop do repositório principal. Você termina o seu trabalho e solicita uma mesclagem. O revisor de código então descobre um conflito, pois o seu colega alterou algum ponto no projeto que você também alterou no desenvolvimento das funcionaldades. Assim, você precisa adicionar ao seu próprio branch feature-A os commits de feature-B que foram adicionados em develop. A melhor forma de fazer isso é através de rebasing.

Rebasing é uma técnica do git que consiste em reescrever os commits de um branch, reordenando-os, adicionando novos, removendo alguns e até mesmo reescrevendo o código armazenado em certos commits. No caso apresentado, realizar rebase do branch develop no branch feature-A vai implicar em ter que reescrever algum commit que você adicionou no feature-A.

O ideal é que o branch develop do seu repositório local esteja sempre atualizado com relação ao develop do repositório principal:

$ git checkout develop
$ git pull origin develop

Em seguida, você pode adicionar as mudanças de develop no seu branch:

$ git checkout feature-A
$ git rebase develop

E seguir trabalhando na funcionalidade. Quando tudo estiver pronto:

$ git push origin feature-A

Regra #8 - Enviou os commits antes de resolver os conflitos? Mescle develop

É possível que você acidentalmente envie para o repositório principal o branch de trabalho sem se certificar de que ele não tem conflitos em relação ao develop. Se esses conflitos existem, o revisor de código te informará disso e caberá a você resolver. Como você nunca deve reescrever o que já está no repositório principal, rebasing não poderá ser utilizado aqui. Como Lollar diz, rebasing é uma ação destrutiva. Você precisa criar um commit novo que solucione os conflitos; é exatamente o que o comando git merge te permite fazer:

$ git checkout feature-A
$ git merge develop

Uma forma simplificada de resolver os conflitos é através do comando git mergetool. Com ele, cada conflito existente é listado e as devidas ações de correção são pedidas para você, uma a uma.

Regra #9 - O revisor de código deve informar as mesclagens em develop e master

E como ele pode dizer que os commits adicionados em develop vieram de feature-A? Desabilitando o recurso de fast-forwarding na mesclagem.

$ git checkout develop
$ git merge --no-ff feature-A

Isto adicionará um commit adicional a develop informando a mesclagem, em vez de copiar os commits de feature-A em develop.

E qual a vantagem de se fazer isso?

Regra #10 - Adicione tags aos commits de mesclagem em master

As tags permitem que você marque pontos importantes no histórico do código presente no master, como mudanças de versão.

$ git checkout master
$ git merge --no-ff develop
$ git tag -a v<versão> -m 'Versão <versão>'

Uma dica adicional, também sugerida por Lollar, é a utilização do versionamento semântico para escrever os números de versões.

Regra #11 - Use verbos imperativos nas mensagens dos commits

"Adiciona a tela X" em vez de "Tela X adicionada". "Corrige o bug Y" em vez de "Correção do bug Y". Alguns verbos comuns:

  • adiciona
  • remove
  • corrige
  • transfere
  • atualiza

Pode "colar" alguns verbos de Technologic também.

Regra #12 - Frase curta + descrição longa

Outra recomendação é que a mensagem do commit seja uma frase curta (de até 50 caracteres, com a primeira letra maiúscula), uma linha em branco e um parágrafo que dê uma descrição maior sobre o commit. Essa descrição, entretanto, é opcional.

Adeus shell_exec()

· 6 minutos

Como converter um PDF em imagens PNG ou JPEG, usando PHP? Como converter arquivos de vídeo enviados via upload em formatos para a web, como WebM ou FLV, usando PHP? Como realizar uma tarefa administrativa no servidor, usando PHP?

Muitas vezes nos fazemos essas perguntas e passamos por algumas decepções ao procurar soluções. A primeira é descobrir que nem sempre podemos resolver usando única e exclusivamente a linguagem e/ou tecnologia que usamos em nossos projetos: PHP não foi construída para executar tarefas longas e que demandam alto processamento, como converter formatos de imagem, áudio e vídeo. Até podemos considerar o uso de extensões (como a ImageMagick), mas geralmente o modo rápido de conseguir o que se quer é através de programas externos, como avconv e convert.

A principal dificuldade esperada ao realizar a integração entre scripts PHP e programas externos é a perda de compatibilidade nas diversas plataformas em que PHP é executável. A maioria dos exemplos de uso das funções exec(), shell_exec() e passthru() utiliza programas do ecossistema Unix e, ao menos no Brasil, o número de desenvolvedores PHP que rodam Windows em suas estações de trabalho é grande. Para eles, usar programas externos é adicionar ao seu código algo que só poderá ser testado em servidores de teste ou desenvolvimento uma realidade que vem mudando, a passos de formiga, com a adoção de ferramentas como Vagrant e Docker.

Então é simples usar programas externos? Não. Todo programa é executado como um novo processo do sistema, chamado pelo shell através de uma linha de comando. E neste contexto, existem algumas coisas a se considerar:

  1. Deve ser garantido que nenhum número abusivo de processos seja executado;
  2. Deve ser garantido que nenhum dado enviado execute código arbitrário;
  3. Todo processo possui uma stream de entrada de dados, uma stream de saída normal e uma stream de saída de erros;
  4. Alguns processos exigem interação do usuário através da stream de entrada;
  5. Alguns processos são longos;
  6. Alguns processos escrevem dados em formatos complexos nas streams de saída;
  7. Processos retornam um código de status de fim de execução que indica erros ocorridos durante a execução.

Vamos ver que opções as funções padrão do PHP nos fornecem para trabalhar com processos:

string shell_exec(string $cmd) ou `$cmd`

É a função ideal para processos invocados com uma linha de comando simples e que escrevem apenas uma linha de texto na stream de saída.

  1. Somente um processo pode ser executado por vez, a menos que se utilizem mecanismos de execução paralela presentes no shell (e.g. start convert doc.pdf images.jpg em Windows).
  2. Você depende de escapeshellarg($arg) para escapar argumentos vindos da entrada de usuário. Usar ou não usar é por sua conta e risco. Para o operador de execução (` `) é impossível passar dados de usuário, como se a definição da linha de comando do processo fosse constante.
  3. Tudo o que foi escrito na stream de saída é retornado pela função. Em caso de erro, entretanto, é retornado o valor NULL, mesmo que dados tenham sido escritos.
  4. Não permite escrever dados na stream de entrada.
  5. Você não pode definir um tempo máximo para a execução do processo, embora fique limitado pelo limite de tempo de execução do próprio script PHP.
  6. Você precisa parsear toda a stream de saída obtida através do retorno da função/expressão.
  7. Você não obtém o código de status. Se ele for zero, a função retorna o conteúdo da stream de saída; caso contrário, NULL é retornado.

string exec(string $command[, array &$output[, int &$return_var]])

Esta função adiciona um grau maior de controle sob o que é escrito na stream de saída e sobre os códigos de status.

  1. Somente um processo pode ser executado por vez, a menos que se utilizem mecanismos de execução paralela presentes no shell.
  2. Você depende de escapeshellarg($arg) para escapar argumentos vindos da entrada de usuário. Usar ou não usar é por sua conta e risco.
  3. Você pode ter acesso ao que foi escrito na stream de saída através do array $output ou apenas da última linha escrita através do retorno da função.
  4. Não permite escrever dados na stream de entrada.
  5. Você não pode definir um tempo máximo para a execução do processo, embora fique limitado pelo limite de tempo de execução do próprio script PHP.
  6. Você precisa parsear toda a stream de saída obtida através de $output.
  7. Você obtém o código de status através de $return_var.

void passthru(string $command[, int &$return_var])

O uso mais comum desta função é quando o script PHP funciona como um simples proxy para um programa externo, como um gerador de imagens ou compilador.

  1. Somente um processo pode ser executado por vez, a menos que se utilizem mecanismos de execução paralela presentes no shell.
  2. Você depende de escapeshellarg($arg) para escapar argumentos vindos da entrada de usuário. Usar ou não usar é por sua conta e risco.
  3. A stream de saída do processo é redirecionada para a stream de saída do script PHP. Se você quiser capturar a saída, vai ter que utilizar mecanismos de output buffering (ob_start()).
  4. Não permite escrever dados na stream de entrada.
  5. Você não pode definir um tempo máximo para a execução do processo, embora fique limitado pelo limite de tempo de execução do próprio script PHP.
  6. Você precisa parsear toda a stream de saída obtida através de output buffering.
  7. Você obtém o código de status através de $return_var.

resource proc_open(string $cmd, array $descriptorspec, array &$pipes[, string $cwd[, array $env[, array $other_options]]])

E aqui temos o maior controle possível de processos (inclusive do diretório de trabalho do processo, via $cwd) através de scripts PHP.

  1. Esta função é não-bloqueante, de modo que você pode iniciar quantos processos julgar adequado. Vale o bom senso para garantir que uma quantidade não-abusiva de processos sejam rodados paralelamente.
  2. Você depende de escapeshellarg($arg) para escapar argumentos vindos da entrada de usuário. Usar ou não usar é por sua conta e risco.
  3. Você tem acesso a stdin, stdout e stderr através do array $pipes, como se fossem ponteiros de arquivo tradicionais.
  4. stdin é uma stream de escrita, onde você pode escrever usando funções simples como fwrite().
  5. O controle de timeout dos processos pode ser implementado, já que o processo roda paralelamente ao script.
  6. A leitura dos dados escritos em stdout e stderr não difere da leitura de um arquivo; assim, as mesmas técnicas empregadas para parsear um arquivo podem ser aplicadas, seja de forma integral (ler toda a stream e interpretar o formato), seja de forma contínua (ler a stream linha a linha e interpretar durante a execução).
  7. Você pode ter o código de status a partir do retorno de proc_close($process).

Tudo é complicado

Pelo que se percebe, a complexidade para se executar certos processos via PHP é semelhante à própria complexidade do processo, i.e., um processo que exige interação com todas as streams, timeout e controle do código de status vai demandar o uso de uma função muito complexa. Veja você mesmo:

<?php
$descriptorspec = array(
   0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
   1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
   2 => array("file", "/tmp/error-output.txt", "a") // stderr is a file to write to
);

$cwd = '/tmp';
$env = array('some_option' => 'aeiou');

$process = proc_open('php', $descriptorspec, $pipes, $cwd, $env);

if (is_resource($process)) {
    // $pipes now looks like this:
    // 0 => writeable handle connected to child stdin
    // 1 => readable handle connected to child stdout
    // Any error output will be appended to /tmp/error-output.txt

    fwrite($pipes[0], '<?php print_r($_ENV); ?>');
    fclose($pipes[0]);

    echo stream_get_contents($pipes[1]);
    fclose($pipes[1]);

    // It is important that you close any pipes before calling
    // proc_close in order to avoid a deadlock
    $return_value = proc_close($process);

    echo "command returned $return_value\n";
}

Nesse exemplo, abrimos o processo php, escrevemos na stream de entrada o código <?php print_r($_ENV); ?> e lemos o resultado da execução na stream de saída do processo. Nenhum mecanismo de timeout é implementado, bem como nenhum controle de erros é realizado através da stream de erros e do código de status, e mesmo assim temos um código assustador para programadores novatos.

Symfony Process ao resgate

Streams, código de status, timeout, diretório de trabalho, argumentos escapados... Muitos são os componentes e detalhes presentes na execução de um processo. Para nossa sorte, o componente Symfony Process provê um mecanismo simples para execução e controle de processos (e ao contrário do que muitos pensam não é necessário estar utilizando o framework Symfony para usufruir deste componente). Vamos recriar o exemplo supracitado para demonstrar isso.

Primeiramente, você pode baixar as classes do Symfony Process de modo tradicional, mas isso é desaconselhado; prefira fazer o controle deste e de demais códigos de terceiros através do Composer, um gerenciador de dependências para PHP que se tornou praticamente o padrão da indústria. Caso você ainda não esteja utilizando no seu projeto, execute no terminal

$ composer init

E forneça informações básicas do seu projeto. Assim que o arquivo composer.json estiver disponível, execute

$ composer require symfony/process

Para adicionar ao seu projeto a última versão do componente Symfony Process. Todo o código fica disponível no diretório vendor/symfony/process e o arquivo composer.lock é criado para registrar qual a versão utilizada.

Com a library em mãos, vamos recriar o exemplo passo-a-passo. Escreva, num arquivo chamado exemplo.php, as seguintes linhas:

<?php
require __DIR__.'/vendor/autoload.php';

use Symfony\Component\Process\Process;

A primeira linha vai adicionar o autoloader do Composer, tornando acessíveis todas as classes do Symfony Process. A linha seguinte permite que a classe Symfony\Component\Process\Process possa ser chamada apenas de Process no script. Ainda no mesmo arquivo, escreva:

$process = new Process('php');

Essa linha não executa o processo php de imediato, apenas prepara uma instância da classe Process que representa um processo antes, durante, e após sua execução.

$process->setInput('<?php print_r($_ENV); ?>');

Aqui foi definido qual conteúdo será escrito em stdin que o processo seja executado (esse método lança RuntimeException se é executado depois que o processo é executado).

$process->run(function ($type, $buffer) {
    if (Process::ERR === $type) {
        echo 'ERR > '.$buffer;
    } else {
        echo 'OUT > '.$buffer;
    }
});

Aqui o processo é executado de fato, de forma síncrona (para executar o processo de forma asíncrona, $process->start() deve ser invocado no lugar de $process->run()), mas com um adendo: toda saída gerada pelo processo, em stdout e stderr, é passada imediatamente para o callable passado como parâmetro do método run() (neste caso, é uma closure). O callable deve aceitar dois parâmetros: $type, que indica se a saída foi escrita em stdout ou stderr; $buffer, que contém o texto escrito.

E... Isso é tudo. Você não precisa fechar o processo, muito menos pará-lo. Segue o exemplo completo:

<?php
require __DIR__.'/vendor/autoload.php';

use Symfony\Component\Process\Process;

$process = new Process('php');
$process->setInput('<?php print_r($_ENV); ?>');
$process->run(function ($type, $buffer) {
    if (Process::ERR === $type) {
        echo 'ERR > '.$buffer;
    } else {
        echo 'OUT > '.$buffer;
    }
});

Agora vá!

Espero que este artigo tenha instigado o leitor investir um pouco de atenção ao componente Symfony Process. As possibilidades com ele são interessantes, principalmente se o seu desejo for de adicionar poder de fogo à sua aplicação web. Em breve devo demonstrar mais casos de uso, como invocação de convert, avconv e rsync. Até lá!

Contagem regressiva para a Web.br 2014!

· 1 minuto

Recentemente desenvolvi um trabalho interessante no projeto Atlas Digital da Economia Solidária da Alligo para o Grupo Ecosol (Grupo de Pesquisa em Economia Solidária) como parte do Projeto SIES que envolveu a aplicação de alguns princípios de interoperabilidade, uma vez que o front-end da aplicação é alimento por um web service que mantém uma API RESTful. Eis vou palestrar sobre este e outros assuntos com o líder do projeto, Emerson Rocha Luiz, na Web.br 2014 Conferência Web W3C Brasil nesta quinta-feira, dia 25. Este ano a conferência carrega uma comemoração especial: os 25 anos da Web e os 20 anos do W3C.

Alguns pontos que procurarei abordar incluem REST, HATEOAS, Web Semântica, JSON-LD e linked data em geral. O Emerson vai descrever o que espera-se de atlas temáticos digitais e o que realmente é entregue, bem como o uso de dados abertos em aplicações contruídas sobre a Open Web Platform e alguns tópicos especiais de acessibilidade e usabilidade.

Higiene e segurança em escritórios de TI

· 4 minutos

Introdução

O presente trabalho foi motivado pela promoção de diversas campanhas, como a prompt, sobre o impacto de atividades na indústria de tecnologia da informação tem sobre a saúde mental dos trabalhadores da área, e um relatório recente, financiado pelo governo da Austrália sobre o retorno sobre investimento (ROI) na manutenção de espaços de trabalho que promovem saúde mental.

Descrição da empresa

Para o presente trabalho foi realizada a análise de uma empresa do setor de Tecnologia da Informação registrada na Junta Comercial do Estado do Rio Grande do Sul em abril de 2007 e com sede em Porto Alegre. Inicialmente, a empresa fornecia serviços em plataforma web através de convênio com seccionais da Ordem dos Advogados do Brasil e contato direto com escritórios de advocacia, mas atualmente está em expansão de escopo para atingir mais clientes que precisam de soluções empresariais em TI, ultrapassando a marca atual de 77 mil clientes em todo território brasileiro.

A empresa possui um quadro de 135 colaboradores que se dividem nos seguintes setores:

Setor administrativo. Abrange todas as atividades de controle financeiro e gestão de recursos humanos da empresa.

Setor de Identidade Profissional. Composto de poucos profissionais de relacionamento direto com o cliente e designers, é responsável pela criação de produtos de identidade visual para clientes externos e internos, como logotipos e papelaria.

Central de Relacionamento. O maior setor da empresa. Possui a estrutura de uma central de telemarketing e subdivide-se em núcleos de venda, pós-venda, suporte, cobrança, recuperação de contratos etc.

Setor de Tecnologia da Informação. Composto de coordenadorias de Arquitetura, Infraestrutura, Planejamento de Produto e Desenvolvimento, é estruturado como um departamento de TI típico, excetuando-se que não foi adotado o sistema de acordo de banco de horas para distribuição da jornada de trabalho.

Diagrama de distribuição dos colaboradores entre setores

O foco da análise apresentada será o Setor de Tecnologia da Informação.

Jornada de trabalho

A fim de manter um regimento interno equivalente e com o mínimo de particularidades entre a Central de Relacionamento, que deve manter atividade constante no horário de atendimento, e o setor de Tecnologia da Informação, que não precisa de horários fixos mesmo em atividades de operação, foi estabelecido, mediante acordo, que cada colaborador poderia definir uma distribuição do trabalho ao longo das oito horas diárias que fosse o mais adequado. Assim, o período de atividade é de 7h20 às 19h20, respeitando os intervalos obrigatórios previstos em lei.

Distribuição de colaboradores no setor avaliado

As atividades realizadas pelos colaboradores são dividas com base nas coordenadorias, de modo que existem profissionais nos cargos de:

  • webdesigner: 4 colaboradores;
  • programador de sistemas de informação: 10 colaboradores;
  • administrador de banco de dados: 1 colaborador;
  • suporte técnico: 2 colaboradores;
  • administradores de sistemas: 3 colaboradores.

Cabe destacar que existem apenas duas mulheres no setor, ambas programadoras.

Processo de produção avaliado

Embora quase todos os colaboradores do setor desenvolvam atividades de projeto e planejamento, serão avaliados os riscos para a atividade de operação, desenvolvida majoritariamente pelos webdesigners, programadores e administradores de sistemas. Esta atividade pode ter seu fluxo de trabalho resumido como receber requisições através de um sistema de rastreamento de incidentes (incident ticket system) alimentado pelos profissionais da Central de Relacionamento e atuar em regime de urgência em sistemas que já estão em ambiente de produção, isto é, sistemas informatizados que já estão sendo executados e sendo alimentados com dados de clientes. Incidentes típicos registrados incluem:

  • indisponibilidade de servidores;
  • dados corrompidos ou incorretos;
  • correções na interface visual;
  • dúvidas que o núcleo de suporte não pode esclarecer etc.

Espaço de trabalho

Esquema simplificado do setor

Identificação dos riscos ambientais

A empresa não paga adicionais de insalubridade (NR-15) e periculosidade (NR-16). Há Comissão Interna de Prevenção de Acidentes (CIPA, NR-5), escolhida conjuntamente ao conselho de colaboradores e mantém com rigor o Programa de Controle Médico de Saúde Ocupacional (PCMSO, NR-7) e o Programa de Prevenção de Riscos Ambientais (PPRA, NR-9). E, embora não disponha de Serviços Especializados de Engenharia de Segurança e em Medicina do Trabalho (SESMT), foi recentemente contratada uma massoterapeuta com especialização em Fisioterapia Ocupacional. Entre as métricas de monitoramento de Recursos Humanos, foram adicionados indicadores de acidentes no trabalho e doenças ocupacionais, pois alguns colaboradores apontaram problemas de saúde relacionados ao sedentarismo, como estresse, hipertensão, obesidade e problemas do trato gastroinstestinal (o único registro de afastamento pela Previdência Social foi relacionado a úlcera gástrica).

Seguem os riscos identificados no setor.

1. Risco físico

  • Causa: calor/frio
  • Fonte: sistema de ar condicionado
  • Consequência: doenças respiratórias, doença de Raynaud, síndrome de Sjögren (xerostomia e xeroftalmia)
  • Medidas de controle coletivas: dimensionamento adequado dos ambientes condicionados, distribuição calculada dos condicionadores, limpeza periódica dos filtros, respeito à ABNT NBR 16401-2 (parâmetros de conforto térmico)
  • Medidas de controle individuais: uso de roupas adequadas à estação do ano, distanciamento de regiões de circulação de ar condicionado
  • Risco não controlado.

2. Risco físico

  • Causa: radiação
  • Fonte: monitores dos computadores
  • Consequência: xeroftalmia, aceleração da degeneração macular relacionada à idade (ARMD), alteração no ritmo circadiano, queimadura da retina
  • Medidas de controle coletivas: aferição periódica da radiação, substituição de equipamento defeituoso
  • Medidas de controle individuais: distância mínima de 50cm entre os olhos e a tela, piscar constantemente (dado que o movimento é semi-autônomo)
  • Risco controlado com aferição realizada por empresa terceirizada.

3. Risco biomecânico

  • Causa: postura inadequada
  • Fonte: posto de trabalho sentado
  • Consequência: sedentarismo, lordose, cifose, estase sanguínea dos membros inferiores
  • Medidas de controle coletivas: adoção de assentos com regulagem de altura do assento, altura do apoio de costas e altura dos apoios para os braços, ginástica laboral, massagem preventiva para lombalgia
  • Medidas de controle individuais: postura correta, pausas regulares na jornada de trabalho
  • Risco controlado com cadeiras adequadas e massoterapia.

4. Risco biomecânico

  • Causa: posição inadequada para os punhos
  • Fonte: uso de mouse e teclado
  • Consequência: epicondilite lateral, tendinite, lesões por esforço repetitivo (LER) em geral
  • Medidas de controle coletivas: disponibilização de equipamentos ergonômicos e apoios para os punhos
  • Medidas de controle individuais: pausas regulares na jornada de trabalho, ginástica laboral, automatização de tarefas que exigem grande interação do usuário de computador
  • Risco não controlado.

5. Risco psicossocial

  • Causa: carga de trabalho excessiva
  • Fonte: utilização do sistema de rastreamento de incidentes
  • Consequência: estresse, esgotamento (burnout)
  • Medidas de controle coletivas: treinamento adequado para utilização do sistema de rastreamento de incidentes, alteração do fluxo de trabalho nas atividades de operação
  • Medidas de controle individuais: psicoterapia ocupacional regular
  • Risco não controlado.

Conclusão

Como uma empresa de médio porte, a analisada apresenta, além da conformidade com as Normas Regulamentadoras de Segurança do Trabalho, desejo de aumentar a produtividade garantindo um ambiente de higiene adequada e um ambiente que fornece conforto aos colaboradores. Enquanto a minimização de alguns riscos depende de grande investimento financeiro, boa parte deles exige apenas campanhas internas para conscientização dos colaboradores, que pode ser uma solução de curto-prazo. Para longo prazo, alguns processos burocráticos devem ser revistos.

Referências

  1. MINISTÉRIO DO TRABALHO E EMPREGOS. Site www.mte.gov.br – Inspeção do Trabalho – Legislação – Normas Regulamentadoras.
  2. "Health effects of lighting systems using light-emitting diodes (LEDs)", ATTIA, D., ANSES. Set/2011.

A forma mais *@#$!* para habilitar conexões ao banco de dados Oracle no PHP5 rodando sobre o Ubuntu

· 1 minuto

Este artigo está disponível como um gist. Sinta-se livre para contribuir.

Agradecimentos especiais para:

Primeiro passo: Instalação do cliente Oracle

  1. Faça o download do Instant Client em http://www.oracle.com/technetwork/indexes/downloads/index.html#database (você deve ter um usuário registrado no site da Oracle; o registro é gratuito). Você precisa dos arquivos instantclient-basic-*-*.zip e instantclient-sdk-*-*.zip.

  2. Execute os seguintes comandos no seu terminal:

    $ sudo su -
    $ mkdir -p /opt/oracle/instantclient
    $ cd /opt/oracle/instantclient
    
  3. Copie os arquivos baixados em /opt/oracle/instantclient.

  4. Descompacte os arquivos executando estes comandos:

    $ unzip instantclient-basic-*-*.zip
    $ unzip instantclient-sdk-*-*.zip
    
  5. Mova todos o conteúdo de /opt/oracle/instantclient/instantclient para /opt/oracle/instantclient:

    $ mv instantclient*/* ./
    $ rmdir instantclient*/
    
  6. Durante a compilação do código da extensão, alguns erros podem surgir quando forem resolvidas as dependências de algumas libraries. Para evitá-los, faça:

    $ ln -s libclntsh.so.* libclntsh.so
    $ ln -s libocci.so.* libocci.so
    $ echo /opt/oracle/instantclient >> /etc/ld.so.conf
    $ ldconfig
    
  7. Crie um diretório para seus arquivos de configuração de rede:

    $ mkdir -p network/admin
    
  8. Coloque os arquivos sqlnet.ora e tnsnames.ora em /opt/oracle/instantclient/network/admin.

Agora você tem o kit básico para conexões e acesso à SDK para compilar extensões PHP com Oracle.

Segundo passo: Instalação da extensão PHP OCI8

  1. Pegue todos os pacotes essenciais para baixar e compilar a partir de repositórios PEAR:

    $ apt-get install --yes php5 php5-cli php5-dev php-db php-pear
    $ apt-get install --yes build-essential libaio1
    
  2. Requisite a instalação do OCI8:

    $ pecl install oci8
    

    Digite instantclient,/opt/oracle/instantclient quando perguntado pelo local de instalação do Instant Client.

  3. Salve este texto em /etc/php5/mods-available/oci8.ini:

    extension=oci8.so
    
  4. Ative a extensão:

    $ php5enmod oci8
    

Agora você tem todas as funções PHP oci_* disponíveis tanto via php-cli quanto via Apache.

Terceiro passo: Instalação da extensão PHP PDO/OCI

A biblioteca pdo_oci está desatualizada, então sua instalação é mais "engenhosa".

  1. Corrija alguns caminhos de arquivo:

    $ cd /usr/include/
    $ ln -s php5 php
    $ cd /opt/oracle/instantclient
    $ mkdir -p include/oracle/11.1/
    $ cd include/oracle/11.1/
    $ ln -s ../../../sdk/include client
    $ cd -
    $ mkdir -p lib/oracle/11.1/client
    $ cd lib/oracle/11.1/client
    $ ln -s ../../../../ lib
    $ cd -
    
  2. Baixe a pdo_oci via pecl:

    $ pecl channel-update pear.php.net
    $ mkdir -p /tmp/pear/download/
    $ cd /tmp/pear/download/
    $ pecl download pdo_oci
    
  3. Extraia o código-fonte:

    $ tar xvf PDO_OCI*.tgz
    $ cd PDO_OCI*
    
  4. Crie um arquivo chamado config.m4.patch:

    *** config.m4   2005-09-24 17:23:24.000000000 -0600
    --- /home/myuser/Desktop/PDO_OCI-1.0/config.m4  2009-07-07 17:32:14.000000000 -0600
    ***************
    *** 7,12 ****
    --- 7,14 ----
        if test -s "$PDO_OCI_DIR/orainst/unix.rgs"; then
          PDO_OCI_VERSION=`grep '"ocommon"' $PDO_OCI_DIR/orainst/unix.rgs | sed 's/[ ][ ]*/:/g' | cut -d: -f 6 | cut -c 2-4`
          test -z "$PDO_OCI_VERSION" && PDO_OCI_VERSION=7.3
    +   elif test -f $PDO_OCI_DIR/lib/libclntsh.$SHLIB_SUFFIX_NAME.11.1; then
    +     PDO_OCI_VERSION=11.1
        elif test -f $PDO_OCI_DIR/lib/libclntsh.$SHLIB_SUFFIX_NAME.10.1; then
          PDO_OCI_VERSION=10.1
        elif test -f $PDO_OCI_DIR/lib/libclntsh.$SHLIB_SUFFIX_NAME.9.0; then
    ***************
    *** 119,124 ****
    --- 121,129 ----
          10.2)
            PHP_ADD_LIBRARY(clntsh, 1, PDO_OCI_SHARED_LIBADD)
            ;;
    +     11.1)
    +       PHP_ADD_LIBRARY(clntsh, 1, PDO_OCI_SHARED_LIBADD)
    +       ;;
          *)
            AC_MSG_ERROR(Unsupported Oracle version! $PDO_OCI_VERSION)
            ;;
    #EOF
    
  5. Aplique o patch:

    $ patch --dry-run -i config.m4.patch && patch -i config.m4.patch && phpize
    
  6. Substitua todas as referências de function_entry para zend_function_entry em pdo_oci.c.

  7. Configure, compile e instale:

    $ ORACLE_HOME=/opt/oracle/instantclient ./configure --with-pdo-oci=instantclient,/opt/oracle/instantclient,11.1
    $ make && make test && make install && mv modules/pdo_oci.so /usr/lib/php5/*+lfs/
    
  8. Salve este texto em /etc/php5/mods-available/pdo_oci.ini:

    extension=pdo_oci.so
    
  9. Ative a extensão:

    $ php5enmod pdo_oci
    

E agora você pode pegar uma xícara de café.

Interpretando URLs com DOM

· minutos
function parseURL(url) {
  var a = document.createElement('a');
  a.href = url;
  return {
    href: a.href,
    scheme: a.protocol,
    host: a.host,
    port: a.port,
    path: a.pathname,
    query: a.search.charAt(0) == '?' ? a.search.substring(1) : null,
    hash: a.hash.charAt(0) == '#' ? a.hash.substring(1) : null,
  };
}

Um método mais confiável para serializar closures

· minutos
<?php
/**
* Serializes a closure as string
* @param \Closure $closure
* @return string
*/
function serializeClosure(\Closure $closure) {
$ref = new ReflectionFunction($closure);
$tokens = token_get_all(file_get_contents($ref->getFileName()));

$tokensCount = count($tokens);
$start = false;
$end = $tokensCount;

for ( $i = 0; $i < $tokensCount; ++$i ) {
  if ( is_array($tokens[$i]) && $tokens[$i][0] === T_FUNCTION && $tokens[$i][2] == $ref->getStartLine() ) {
    $start = $i;
    break;
  }
}

for ( $i = $start; $i < $tokensCount; ++$i ) {
  if ( is_array($tokens[$i]) && $tokens[$i][2] > $ref->getEndLine() ) {
    $end = $i - 1;
    break;
  }
}

$tokens = array_slice($tokens, $start, $end);

$replaceToken = function($a) { return is_array($a) ? $a[1] : $a; };

while ( count($tokens) > 0 && $tokens[0][0] === T_FUNCTION ) {

  if ( !getNesting($parameters, $tokens, '(', ')') ) // does not have parameters
    break;

  if ( !getNesting($body, $tokens, '{', '}', $parameters['end']) ) // does not have body
    break;

  if ( ($use_idx = findToken($tokens, T_USE, $parameters['end'] + 2, $body['start'] - 2)) !== false )
    getNesting($use, $tokens, '(', ')', $use_idx);

  if ( findToken($tokens, T_STRING, 0, $parameters['start']) === false ) { // is anonymous function
    while ( getNesting($tmp, $body['tokens'], T_STATIC, ';') ) {
      $tmp['start']--;
      $tmp['end']++;
      array_splice($body['tokens'], $tmp['start'], $tmp['end']);
    }

    if ( !isset($use) )
      $use = array();

    $closure = compact('parameters', 'use', 'body');

    $variables = $ref->getStaticVariables();

    $source = "return function(" . implode('', array_map($replaceToken, $closure['parameters']['tokens'])) .  ") ";
    if ( !empty($closure['use']['tokens']) ) {
      $useParams = array_map('trim', explode(',', implode('', array_map($replaceToken, $closure['use']['tokens']))));

      foreach ( $useParams as $param ) {
        if ( $param[0] == '$')
          $source = "$param = " . var_export($variables[substr($param, 1)], true) . ";\n$source";
      }

      $source .= "use(" . implode(', ', $useParams) .  ") ";
    }

    $source .= "{" .  implode('', array_map($replaceToken, $closure['body']['tokens'])) . "};";

    $test = function($ref) use($source) {
      $newClosure = eval($source);
      $newRef = new ReflectionFunction($newClosure);

      return ( array_map(function($a) { return $a->getName(); }, $newRef->getParameters()) == array_map(function($a) { return $a->getName(); }, $newRef->getParameters()) );
    };

    if ( $test($ref) )
      return $source;
  }

  $function_idx = findToken($tokens, T_FUNCTION, $body['end'] + 2);

  $tokens = $function_idx === false ? array() : array_slice($tokens, $function_idx);
}

return null;
}

function findToken(array $tokens, $needle, $start = 0, $end = PHP_INT_MAX)
{
$idx = false;
for ( $i = $start, $end = min($end, count($tokens)); $i < $end; ++$i ) {
  if ( (is_int($needle) && is_array($tokens[$i]) && $tokens[$i][0] === $needle) ||
    is_string($needle) && is_string($tokens[$i]) && $tokens[$i] === $needle ) {
    $idx = $i;
    break;
  }
}

return $idx;
}

function getNesting(&$matches, array $tokens, $begin = '{', $end = '}', $offset = 0)
{
$level = 0;
$start = false;

for ( $i = $offset, $count = count($tokens); $i < $count; ++$i ) {
  if ( is_array($tokens[$i]) ? ($tokens[$i][0] === $begin) : ($tokens[$i] === $begin) ) {
    ++$level;
    if ( $start === false )
      $start = $i + 1;
  }
  elseif ( is_array($tokens[$i]) ? ($tokens[$i][0] === $end) : ($tokens[$i] === $end) ) {
    if ( $start === false )
      break;

    if ( --$level == 0 ) {
      $matches = array(
        'tokens' => array_slice($tokens, $start, $i - $start),
        'start' => $start,
        'end' => $i - 1
      );
      return true;
    }
  }
}

$matches = null;
return false;
}

Acesso fácil à classe `\Respect\Validation\Validator`

· minutos
<?php
  // Author's recommendation
  use Respect\Validation\Validator as v;

  $number = 123;
  v::numeric()->validate($number); //true

  // The author method can be a little uncomfortable, because
  // the 'v' alias will be present in all source code and it
  // is meaningless according OOP.

  // Here my approach
  $v = 'Respect\Validation\Validator';

  $number = 123;
  $v::numeric()->validate($number); //true

Inicialização básica de plugins jQuery

· minutos
// Basic jQuery plugins initialization
var MyNamespace = MyNamespace || {};

MyNamespace.applyPlugins = function (parent) {
  if (!parent) parent = $('body');

  $('[data-focus=auto]', parent).focus();

  var richEdits = $('textarea[data-rich=true]');
  if (richEdits.size() > 0) {
    richEdits.fadeTo(0, 1e-8);

    // head.js used here for instance
    head.js('/js/vendor/ckeditor/ckeditor.js').ready(function () {
      richEdits.fadeTo(0, 1).each(function () {
        $(this).myRichEditorPlugin();
      });
    });
  }

  // more stuff ...
};

$(function () {
  MyNamespace.applyPlugins();
});

.htaccess snippets

· minutos
# ------------------------------------------------------------------------------
# Redirecionar para mobile a partir do agente de usuário
# ------------------------------------------------------------------------------
<IfModule mod_rewrite.c>
   RewriteEngine On
   RewriteCond %{HTTP_USER_AGENT} "android|blackberry|googlebot-mobile|iemobile|ipad|iphone|ipod|opera mobile|palmos|webos" [NC]
   RewriteRule ^$ http://m.example.com/ [L,R=302]
</IfModule>
<IfModule mod_rewrite.c>
   RewriteEngine On
   RewriteCond %{HTTP_USER_AGENT} "!(android|blackberry|googlebot-mobile|iemobile|ipad|iphone|ipod|opera mobile|palmos|webos)" [NC]
   RewriteRule ^$ http://www.example.com/ [L,R=302]
</IfModule>

# ------------------------------------------------------------------------------
# Ativar Google Chrome Frame
# ------------------------------------------------------------------------------
<IfModule mod_headers.c>
  Header set X-UA-Compatible "IE=Edge,chrome=1"
  <FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webm|webp|woff|xml|xpi)$">
      Header unset X-UA-Compatible
  </FilesMatch>
</IfModule>

# ------------------------------------------------------------------------------
# Permissão de acesso às Webfonts
# ------------------------------------------------------------------------------
# Nota: qualquer domínio pode ser especificado no lugar de "*"
<IfModule mod_headers.c>
  <FilesMatch "\.(eot|font.css|otf|ttc|ttf|woff)$">
      Header set Access-Control-Allow-Origin "*"
  </FilesMatch>
</IfModule>

# ------------------------------------------------------------------------------
# MIME type apropriado para todos os arquivos
# ------------------------------------------------------------------------------
AddType application/javascript         js jsonp
AddType application/json               json
AddType audio/mp4                      m4a f4a f4b
AddType audio/ogg                      oga ogg
AddType video/mp4                      mp4 m4v f4v f4p
AddType video/ogg                      ogv
AddType video/webm                     webm
AddType video/x-flv                    flv
AddType     image/svg+xml              svg svgz
AddEncoding gzip                       svgz
AddType application/vnd.ms-fontobject  eot
AddType application/x-font-ttf         ttf ttc
AddType application/x-font-woff        woff
AddType font/opentype                  otf
AddType application/octet-stream            safariextz
AddType application/x-chrome-extension      crx
AddType application/x-opera-extension       oex
AddType application/x-shockwave-flash       swf
AddType application/x-web-app-manifest+json webapp
AddType application/x-xpinstall             xpi
AddType application/xml                     rss atom xml rdf
AddType image/webp                          webp
AddType image/x-icon                        ico
AddType text/cache-manifest                 appcache manifest
AddType text/vtt                            vtt
AddType text/x-component                    htc
AddType text/x-vcard                        vcf

# ------------------------------------------------------------------------------
# Compressão gzip
# ------------------------------------------------------------------------------
<IfModule mod_deflate.c>
  <IfModule mod_setenvif.c>
      <IfModule mod_headers.c>
          SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
          RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
      </IfModule>
  </IfModule>
  <IfModule mod_filter.c>
      AddOutputFilterByType DEFLATE application/atom+xml \
                                    application/javascript \
                                    application/json \
                                    application/rss+xml \
                                    application/vnd.ms-fontobject \
                                    application/x-font-ttf \
                                    application/xhtml+xml \
                                    application/xml \
                                    font/opentype \
                                    image/svg+xml \
                                    image/x-icon \
                                    text/css \
                                    text/html \
                                    text/plain \
                                    text/x-component \
                                    text/xml
  </IfModule>
</IfModule>

# ------------------------------------------------------------------------------
# Expires headers (para um melhor controle de cache)
# ------------------------------------------------------------------------------
<IfModule mod_expires.c>
  ExpiresActive on
  ExpiresDefault                          "access plus 1 minute"

  ExpiresByType text/cache-manifest       "access plus 0 seconds"
  ExpiresByType text/html                 "access plus 0 seconds"
  ExpiresByType application/json          "access plus 0 seconds"
  ExpiresByType application/xml           "access plus 0 seconds"
  ExpiresByType text/xml                  "access plus 0 seconds"
  ExpiresByType application/atom+xml      "access plus 1 hour"
  ExpiresByType application/rss+xml       "access plus 1 hour"
  ExpiresByType image/x-icon              "access plus 1 minute"
  ExpiresByType audio/ogg                 "access plus 1 month"
  ExpiresByType image/gif                 "access plus 1 month"
  ExpiresByType image/jpeg                "access plus 1 month"
  ExpiresByType image/png                 "access plus 1 month"
  ExpiresByType video/mp4                 "access plus 1 month"
  ExpiresByType video/ogg                 "access plus 1 month"
  ExpiresByType video/webm                "access plus 1 month"
  ExpiresByType text/x-component          "access plus 1 month"
  ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
  ExpiresByType application/x-font-ttf    "access plus 1 month"
  ExpiresByType application/x-font-woff   "access plus 1 month"
  ExpiresByType font/opentype             "access plus 1 month"
  ExpiresByType image/svg+xml             "access plus 1 month"
  ExpiresByType application/javascript    "access plus 1 minute"
  ExpiresByType text/css                  "access plus 1 minute"
</IfModule>

# ------------------------------------------------------------------------------
# Remoção de ETag
# ------------------------------------------------------------------------------
<IfModule mod_headers.c>
  Header unset ETag
</IfModule>
FileETag None

# ------------------------------------------------------------------------------
# Remover o flicker em rollovers CSS no Internet Explorer
# ------------------------------------------------------------------------------
BrowserMatch "MSIE" brokenvary=1
BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
BrowserMatch "Opera" !brokenvary
SetEnvIf brokenvary 1 force-no-vary

# ------------------------------------------------------------------------------
# Requisições AJAX cross-domain
# ------------------------------------------------------------------------------
<IfModule mod_headers.c>
  Header set Access-Control-Allow-Origin "*"
</IfModule>

# ------------------------------------------------------------------------------
# Imagens CORS habilitadas (@crossorigin)
# ------------------------------------------------------------------------------
<IfModule mod_setenvif.c>
  <IfModule mod_headers.c>
      <FilesMatch "\.(gif|ico|jpe?g|png|svg|svgz|webp)$">
          SetEnvIf Origin ":" IS_CORS
          Header set Access-Control-Allow-Origin "*" env=IS_CORS
      </FilesMatch>
  </IfModule>
</IfModule>

# ------------------------------------------------------------------------------
# Concatenação entre arquivos js e css específicos
# ------------------------------------------------------------------------------
# Nota: e.g., em script.combined.js você pode ter
#       <!--#include file="libs/jquery-1.5.0.min.js" -->
#       <!--#include file="plugins/jquery.idletimer.js" -->
<FilesMatch "\.combined\.js$">
  Options +Includes
  AddOutputFilterByType INCLUDES application/javascript application/json
  SetOutputFilter INCLUDES
</FilesMatch>
<FilesMatch "\.combined\.css$">
  Options +Includes
  AddOutputFilterByType INCLUDES text/css
  SetOutputFilter INCLUDES
</FilesMatch>

# ------------------------------------------------------------------------------
# Impedir provedores de redes mobile (3G) de modificar seu site
# ------------------------------------------------------------------------------
<IfModule mod_headers.c>
  Header set Cache-Control "no-transform"
</IfModule>

# ------------------------------------------------------------------------------
# Keep-Alive
# ------------------------------------------------------------------------------
# Nota: existem algumas desvantagens em habilitar esta opção.
#       Ative-a se você serve muito conteúdo estático
<IfModule mod_headers.c>
  Header set Connection Keep-Alive
</IfModule>

# ------------------------------------------------------------------------------
# Permitir a criação de cookies em iframes
# ------------------------------------------------------------------------------
# Nota: apenas Internet Explorer
<IfModule mod_headers.c>
  Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
</IfModule>

# ------------------------------------------------------------------------------
# Iniciar o motor de reescrita de URLs
# ------------------------------------------------------------------------------
<IfModule mod_rewrite.c>
  Options +FollowSymlinks
  RewriteEngine On
#   RewriteBase /
</IfModule>

# ------------------------------------------------------------------------------
# Reescrever "example.com -> www.example.com"
# ------------------------------------------------------------------------------
<IfModule mod_rewrite.c>
  RewriteCond %{HTTPS} !=on
  RewriteCond %{HTTP_HOST} !^www\..+$
  # Exceto para localhost
  RewriteCond %{HTTP_HOST} !^localhost$
  # Exceto para endereço IP
  RewriteCond %{HTTP_HOST} !^(\d{1,3}\.){3}\d{1,3}$ [NC]
  RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>

# ------------------------------------------------------------------------------
# Reescrever "www.example.com -> example.com"
# ------------------------------------------------------------------------------
<IfModule mod_rewrite.c>
  RewriteCond %{HTTPS} !=on
  RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
  RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
</IfModule>

# ------------------------------------------------------------------------------
# Melhor cache baseado em nome de arquivo
# ------------------------------------------------------------------------------
# Nota: link "css/estilos.css" como "css/estilos.123.css"
<IfModule mod_rewrite.c>
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
</IfModule>

# ------------------------------------------------------------------------------
# Impedir alguns alertas SSL
# ------------------------------------------------------------------------------
<IfModule mod_rewrite.c>
  RewriteCond %{SERVER_PORT} !^443
  RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]
</IfModule>

# ------------------------------------------------------------------------------
# Impedir erros 404 para pastas não existentes em URIs reescritas
# ------------------------------------------------------------------------------
Options -MultiViews

# ------------------------------------------------------------------------------
# Página 404 personalizada
# ------------------------------------------------------------------------------
ErrorDocument 404 /404.html

# ------------------------------------------------------------------------------
# Codificação UTF-8
# ------------------------------------------------------------------------------
AddDefaultCharset utf-8
AddCharset utf-8 .atom .css .js .json .rss .vtt .xml

# ------------------------------------------------------------------------------
# Remover assinatura do servidor
# ------------------------------------------------------------------------------
# Nota: apenas para a httpd.conf
ServerSignature Off
ServerTokens Prod

# ------------------------------------------------------------------------------
# Impedir listagem em diretórios sem documento padrão
# ------------------------------------------------------------------------------
<IfModule mod_autoindex.c>
  Options -Indexes
</IfModule>

# ------------------------------------------------------------------------------
# Impedir acesso a diretórios ocultos no Linux
# ------------------------------------------------------------------------------
# Nota: são diretórios cujo nome inicial com ponto, e.g. ".git"
<IfModule mod_rewrite.c>
  RewriteCond %{SCRIPT_FILENAME} -d [OR]
  RewriteCond %{SCRIPT_FILENAME} -f
  RewriteRule "(^|/)\." - [F]
</IfModule>

# ------------------------------------------------------------------------------
# Impedir acesso a arquivos de becape, código-fonte e configuração
# ------------------------------------------------------------------------------
<FilesMatch "(\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|swp)|~)$">
  Order allow,deny
  Deny from all
  Satisfy All
</FilesMatch>

# ------------------------------------------------------------------------------
# Configurações seguras para o PHP
# ------------------------------------------------------------------------------
# Nota: nem todos servidores suportam essas modificações via .htaccess
php_flag  register_globals       Off
php_value session.name           SESSIONID
php_flag  magic_quotes_gpc       Off
php_flag  expose_php             Off
php_value error_reporting        -1
php_flag  log_errors             On
php_flag  display_errors         Off
php_flag  display_startup_errors Off
php_flag  html_errors            Off
php_flag  ignore_repeated_errors Off
php_flag  ignore_repeated_source Off
php_value log_errors_max_len     1024
php_value error_prepend_string   " "
php_value error_append_string    " "
<IfModule mod_php5.c>
  php_value session.cookie_httponly true
</IfModule>

# ------------------------------------------------------------------------------
# Redirecionar acesso para router PHP
# ------------------------------------------------------------------------------
# Nota: "E=app:main" especifica a variável
#       $_SERVER['REDIRECT_app'] = 'main';
<IfModule mod_rewrite.c>
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule ^ index.php [QSA,E=app:main,L]
</IfModule>