Programando para programadores: Desafios na evolução de um Framework

1.804 visualizações

Publicada em

Nesta palestra são abordados alguns desafios a serem enfrentados na criação de um Framework PHP open-source e também os obstáculos a serem superados para que o mesmo evolua tecnologicamente, mantendo a retrocompatibilidade e uma base de usuários ativa, fazendo o uso correto de seus conceitos.

Publicada em: Tecnologia

Programando para programadores: Desafios na evolução de um Framework

  1. 1. Programando para programadores Desafios na evolução de um Framework Pablo Dall'Oglio @pablodalloglio fb/pablodalloglio
  2. 2. Meu caminho ● Clipper (1994-1998): comercial, bibliotecas, funções; ● Delphi (1998-1999): automação, componentes; ● PHP (2000): SAGU (php+html+sql); ● PHP-GTK(2001): PHP só com classes; ● Agata Report (2001-2006); ● Gnuteca (2002): PHP Web com classes; ● PHP-GTK: Criando Aplicações Gráficas com PHP (2004); ● Design Patterns (2004): Unisinos; ● Core (2006): Primeira experiência com Framework MVC; ● PHP: Programando com Orientação a Objetos (2007); ● Mestrado em Engenharia de Software (2008, 2009); ● Criando Relatórios com PHP (2011); ● Adianti Framework para PHP (2012). Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #2
  3. 3. Introdução ● Software como aplicação (final): – Atende os requisitos? Funciona bem? – Como é a usabilidade? Como é o desempenho? ● Software como framework (meio): – Segue o padrão de código X? O de arquitetura Y? – Como é a interoperabilidade? E o aprendizado? ● Desafios: – Atender padrões globais; – Facilidade no aprendizado; – Evolução tecnológica. Vamos compartilhar algumas experiências Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #3
  4. 4. Framework Experiências com o Adianti Framework: ● Primeira geração em 2006; ● Versão atual iniciou ~2008; ● Lançado oficialmente em 2012; ● Focado em Business apps. Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #4
  5. 5. #Go Horse 1 <?php // configuração $conn = pg_connect("host=localhost port=5432 dbname=exemplos..."); // query $query = 'SELECT id, nome, endereco FROM cliente WHERE id not in (...)'; // resultados $result = pg_query($conn, $query); if ($result) { Apresentação // apresentação echo '<table border="1">'; while ($row = pg_fetch_assoc($result)) { echo '<tr>'; echo '<td>' . $row['id'] . '</td>'; echo '<td>' . $row['nome'] . '</td>'; echo '<td>' . $row['endereco'] . '</td>'; echo '</tr>'; } echo '</table>'; } pg_close($conn); Configuração Business rule? Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #5
  6. 6. Componentes Componentes de interface. $this->datagrid = new TDataGrid; $code = new TDataGridColumn('code', ...); $name = new TDataGridColumn('name', ...); $this->datagrid->addColumn($code); $this->datagrid->addColumn($name); $act1 = new TDataGridAction(array($this, 'onView')); $act1->setLabel('View name'); $act1->setImage('bs:search blue'); $act1->setField('name'); $act_group = new TDataGridActionGroup('Actions', 'bs:th'); $act_group->addHeader('Available Options'); $act_group->addAction($act1); $act_group->addAction($act2); $this->datagrid->addActionGroup($act_group); Abstrair a tecnologia Solução em alto nível Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #6
  7. 7. Componentes Seleção de valores class FormDBAutoSelectionView ... { private $form; function __construct() { parent::__construct(); $this->form = new TForm; $notebook->appendPage('Automatic selection', $this->form); $radio = new TDBRadioGroup('radio','samples','Category', ...); $check = new TDBCheckGroup('check','samples','Category',...); $combo = new TDBCombo('combo', 'samples', 'Category', ...); $select= new TDBSelect('select', 'samples', 'Category', ...); $search= new TDBMultiSearch('search','samples','Category', ...); $search->setMinLength(3); Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #7
  8. 8. Melhorias estruturais 1.0.0 própria 1.0.1 jQuery 1.0.3 bootstrap 2 Geração 1: Bootstrap 2 Geração 2: Bootstrap 3 class TMessage { private $id; private $action; public function __construct($type, ...) { $this->id = uniqid(); $modal_wrapper = new TElement('div'); $modal_wrapper->{'class'} = 'modal'; $modal_wrapper->{'id'} = $this->id; $modal_dialog = new TElement('div'); $modal_dialog->{'class'} = 'modal-dialog'; $modal_content = new TElement('div'); $modal_content->{'class'} = 'modal-content'; Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #8
  9. 9. Componentes Estrutura de um componente class TAccordion extends TElement { public function __construct() { parent::__construct('div'); $this->id = 'taccordion_' . uniqid(); } public function appendPage($title, $object) public function outroMetodo($title, $object) public function show() { $title = new TElement('h3'); $content = new TElement('div'); parent::add($title); parent::add($content); parent::show(); } Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #9 } Estrutura para Extensibilidade app/lib
  10. 10. Estilização Customização de estilo: $this->datagrid = new TQuickGrid; $this->datagrid->class = 'customized-table'; parent::include_css('/.../include/custom-table.css'); $this->form = new TQuickForm; $this->form->style = 'width: 500px'; $this->form->class = 'tform'; $bt = new TButton('bt'); $bt->setLabel('Warning'); $bt->class = 'btn btn-warning btn-sm'; Descolamento entre dados, estrutura e estilização Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #10
  11. 11. Templates Interfaces muito específicas (menor reuso): Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #11
  12. 12. Templates Templates próprios class ProductCatalogView extends TPage { public function __construct() { Estrutura parent::__construct(); $this->html = new THtmlRenderer('app/.../catalog.html'); TPage::include_css('app/resources/catalog.css'); Estilo $replace = array(); $this->html->enableSection('main', $replace); $replace_detail[] = $product->toArray(); $replace_detail[] = $product->toArray(); $this->html->enableSection('products', $replace_detail, TRUE); parent::add($this->html); } Conteúdo Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #12
  13. 13. Desafios da evolução ● Aderência à padrões da comunidade (Namespaces); ● Acrescentar novos componentes de Interface; ● Melhorar componentes existentes (+features); ● Melhorar visual padrão, permitir customização; ● Melhorar a estrutura de diretórios; ● Substituir tecnologia inerente (jQuery, Bootstrap); ● Agregar tecnologia (Font Awesome); ● Tudo continuar funcionando; ● Projetos grandes sendo desenvolvidos. Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #13
  14. 14. Estrutura de diretórios Isolamento entre app (user) e lib (framework, third parts). A carga das classes do FW é com ele mesmo. Onde geralmente ocorrem as mudanças globais em migrações Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #14
  15. 15. Aderência a padrões Namespaces Utilização de class_alias() para compatibilidade Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #15
  16. 16. Novos componentes ● Release 1.0.1 – TSpinner, TSlider ● Release 1.0.2 – TTreeview ● Release 1.0.3 – TSortList – TSelect ● Release 2.0 Sem Traumas! – TMultiSearch, T...ActionGroup – TExpander, TInputDialog, TColor Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #16
  17. 17. Novas features ● Release 1.0.1 – Edição inline (jquery.editinplace.js) – setExitAction (+ método) ● Release 1.0.2 Migration: Acréscimos, substituições em /lib – TEntry::setNumericMask (jquery.maskedinput.js) ● Release 1.0.3 – Nova TFile (TFile.php, tfile.js, tfile.css, same interface); ● Release 2.0 – Datagrid popover (jquery → bootstrap, libraries.html) Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #17
  18. 18. Bibliotecas utilizadas <!-- Third part libraries required by Adianti Framework --> <script src="lib/jquery/jquery.min.js"></script> <script src="lib/bootstrap/js/bootstrap.min.js"></script> <script src="lib/bootstrap/js/bootstrap-colorpicker.min.js"></script> <script src="lib/jquery/jquery-ui.min.js"></script> <script src="lib/jquery/jquery.blockUI.min.js"></script> <!-- Adianti Framework core and components --> <script src="lib/adianti/include/adianti.min.js"></script> <script src="lib/adianti/include/components.min.js"></script> <!-- Application custom Javascript (Optional) --> <script src="app/lib/include/application.js"></script> <!-- Third part CSS required by Adianti Framework --> <link href="lib/jquery/jquery-ui.min.css"/> <link href="lib/bootstrap/css/bootstrap.min.css"/> <link href="lib/font-awesome/css/font-awesome.min.css"/> <!-- Adianti Framework Components CSS --> <link href="lib/adianti/include/adianti.min.css"/> <link href="lib/adianti/include/components.css"/> <!-- Application custom CSS --> <link href="app/templates/{template}/application.css"/> Bootstrap Dialogs, buttons, dropdown, menubar, tooltip, color picker, date picker, multisearch libraries.html Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #18
  19. 19. #Go Horse 2 class ContaReceber { function inserir($id, $a, $b, $c) { $sql = "INSERT INTO conta_receber..."; // exec $sql; } function listar() { $sql = "SELECT * FROM conta_receber"; // exec $sql } function getContasEmAberto() { $sql = "SELECT id, sum(valor) Começou a separar FROM contas_receber cr, lancamentos l WHERE l.conta_id = cr.id GROUP BY 1 HAVING sum(valor) >1”; } function getContasEmAtraso() { $sql = "SELECT ... ... WHERE dt_vencimento <= date(now())"; } Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #19 } Lógicas complexas extensas em SQL Código reflexo do BD
  20. 20. #Go Horse SP DROP PROCEDURE IF EXISTS `VALIDAVENDA`; CREATE PROCEDURE `VALIDAVENDA`(IN ICODVENDA int(11), OUT CERRO VARCHAR(60)) BEGIN SET @CCREDITO = (select vendas.cod_venda from vendas where vendas.cod_venda = ICODVENDA and vendas.status = 1); SET @PRODUTO = (select vendas.cod_prod from vendas where vendas.cod_venda = ICODVENDA and vendas.status = 0); SET @COD_TITULO = (select contas_receber.cod_titulo from contas_receber where contas_receber.cod_venda = ICODVENDA); SELECT IF(@CCREDITO IS NULL,'OK','Já existe credito para a venda referente a devolução!') INTO CERRO; IF CERRO = 'OK' THEN UPDATE PRODUTO SET PRODUTO.FLAG_VENDA = "N" WHERE PRODUTO.COD_PROD = @PRODUTO; UPDATE CONTAS_RECEBER SET CONTAS_RECEBER.STATUS = "Cancelado" WHERE CONTAS_RECEBER.COD_VENDA = ICODVENDA; WHILE @COD_TITULO IS NOT NULL DO DELETE FROM CR_CAIXA WHERE CR_CAIXA.COD_TITULO = @COD_TITULO; END WHILE; END IF; Como chamo aquela SP do formulário? END; Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #20
  21. 21. Orientação a modelos Aplicação orientada à SQL (BAD) ● Inserir autor em livro INSERT INTO autor_livro (autor_id, livro_id) VALUES ('$autor_id', '$livro_id'); ● Retornar os itens da Nota Fiscal SELECT * FROM nf, itens WHERE nf.id = itens.nf_id AND nf.id = '$nf_id' ● Percorrer dados relacionados SELECT * FROM turma, matricula, aluno WHERE turma.id=matricula.turma_id AND matricula.id_aluno = aluno.id AND turma.id = '$turma_id' Cérebro orientado à relações simples chaves entre tabelas Ver 10 piores SQL Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #21
  22. 22. Orientação a modelos Aplicação orientada ao domínio (GOOD) XMI → {PHP, SQL} Utilização de relações mais complexas. Modelo relacional é consequência Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #22
  23. 23. Orientação a modelos Exemplo de implementação class Filme extends TRecord { public function get_genero() { if (empty($this->genero)) { Lazy Load $this->genero = new Genero( $this->genero_id ); } return $this->genero; } public function addAtor( Ator $object ) { $this->atores[] = $object; } public function getAtores() { return $this->atores; } public function load($id) { $this->criticas = parent::loadComposite('Critica', ...); $this->atores = parent::loadAggregate('Ator', ...); return parent::load($id); } Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #23 } Relações mais complexas, é preciso treinar... Métodos auxiliares
  24. 24. Orientação a modelos Exemplo de utilização // carrega o filme $filme = new Filme(5); print $filme->distribuidor->nome; print $filme->genero->nome; // carrega o filme $filme = new Filme(5); foreach ($filme->getAtores() as $ator) { print $ator->nome; Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #24 } // adiciona o ator $ator = new Ator(10); $filme->addAtor( $ator ); $filme->store(); LEGIBILIDADE Lazy Load MAIOR Agregação
  25. 25. Orientação a modelos Aplicação orientada ao domínio (GOOD) ● Inserir autor em livro $livro = new Livro( 10 ); $livro->addAutor( new Autor(8) ); $livro->store(); ● Retornar os itens da Nota Fiscal $nf = new NotaFiscal(10); foreach ($nf->getItems() as $item) { print $item->produto->descricao; } ● Percorrer dados relacionados NAVEGABILIDADE entre relações $turma = new Turma(10); foreach ($turma->getMatriculas() as $matricula) { print $matricula->aluno->nome; } Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #25
  26. 26. Persistência Armazenar um novo objeto (INSERT). //... TTransaction::open('samples'); // abre uma transação $object = new Pessoa; $object->name = 'Maria da Silva'; $object->address = 'Rua da Conceicao'; $object->phone = '(51) 8111-2222'; $object->birthdate = '2013-02-15'; $object->status = 'S'; $object->email = 'maria@email.com'; $object->gender = 'M'; $object->store(); // armazena o objeto TTransaction::close(); // fecha a transação. //... Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #26
  27. 27. Persistência Alterar um objeto já existente (UPDATE). //... TTransaction::open('samples'); // abre uma transação $objeto = new Customer; // instancia o cliente $customer= $objeto->load(31); // carrega o cliente 31 if ($customer) // se existe { $customer->phone = '51 8111-3333'; // muda o fone $customer->store(); // armazena o objeto } new TMessage('info', 'Objeto atualizado'); TTransaction::close(); // fecha a transação. //... Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #27
  28. 28. Persistência Manipular um conjunto de objetos conforme um filtro. //... TTransaction::open('samples'); // abre uma transação $repository = new TRepository('Customer'); $customers = $repository->where('gender', '=', 'M') ->where('name', 'like', 'A%') ->load(); foreach ($customers as $customer) { $customer->phone = '84 '.substr($customer->phone, 3); $customer->store(); } Active Record TTransaction::close(); // fecha a transação. //... Repository Pattern Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #28
  29. 29. Object caching Melhorias para a versão 2.0: ● Cache Service class Customer extends TRecord { 1/3 tempo na matrícula const TABLENAME = 'customer'; const PRIMARYKEY = 'id'; const IDPOLICY = 'max'; // {max, serial} const CACHECONTROL = 'Cache-Control-Class'; // ... } ● Utilizado em: Grandes ganhos de Performance Deve implementar classe específica – Carregar objeto, Salvar objeto, Excluir objeto; – Carregar coleções, Excluir coleções; Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #29
  30. 30. Object caching Implementação de cache usando APC: class TAPCache implements AdiantiRegistryInterface { public static function enabled() { return extension_loaded('apc'); } public static function setValue($key, $value) { return apc_store($key, serialize($value)); } public static function getValue($key) { return unserialize(apc_fetch($key)); } public static function delValue($key) { return apc_delete($key); } } Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #30
  31. 31. Prepared Statements Melhorias para a versão 2.0: ● Prepared Statements transparente: – Habilitar/desabilitar por transação (performance); $data = $this->form->getData(); Query Object pattern TTransaction::open('samples'); $criteria = new TCriteria; $criteria->add(new TFilter('gender', '=', $data->gender)); $criteria->add(new TFilter('age', '>', $data->age)); $criteria->add(new TFilter('haircolor', '>', $data->haircolor)); $repository = new TRepository('Customer'); $customers = $repository->load($criteria); TTransaction::close(); Prepared Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #31
  32. 32. Prepared Statements Como o Prepared é processado: TTransaction::log($sql->getInstruction()); $dbinfo = TTransaction::getDatabaseInfo(); if (isset($dbinfo['prep']) AND $dbinfo['prep'] == '1') { Com prepared $result = $conn->prepare($sql->getInstruction(TRUE), ...); $result-> execute( $sql->getPreparedVars() ); } else { Sem prepared $result = $conn-> query($sql->getInstruction()); } if ( $cache = $this->getCacheControl() ) { Atualiza cache $record_key = $class . '['. $this->$pk . ']'; if ($cache::setValue( $record_key, $this->toArray() )) { TTransaction::log($record_key . ' stored in cache'); } } return $result; Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #32
  33. 33. Novas restrições ● Release 1.0.2 – TForm::addField() 2 campos mesmo nome; – TForm::addField() só aceita AdiantiWidgetInterface. class TForm { public function addField( AdiantiWidgetInterface $field ) { $name = $field->getName(); if (isset($this->fields[$name])) { throw new Exception(TAdiantiCoreTranslator::translate('You have already added a field called "^1" inside the form', $name)); } $this->fields[$name] = $field; $field->setFormName($this->name); } Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #33 } Resiliência 1o programador = instrução 2o programador = exceção
  34. 34. Novas restrições ● Release 1.0.3 – TRecord::addAttribute() não pode ter atributo data; – setLogger() sem transação ativa lança exceção; final class TTransaction { public static function setLogger( TLogger $logger) { if (isset(self::$conn[self::$counter])) { self::$logger[self::$counter] = $logger; } else { Não esperar que as coisas estejam certas throw new Exception(TAdiantiCoreTranslator::translate('No active transactions') . ': ' . __METHOD__); } } Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #34 } Controle
  35. 35. Novas restrições ● Release 2.0 (PSR-3) – TTransaction implements LoggerAwareInterface; – TLogger's implements LoggerInterface. final class TTransaction implements LoggerAwareInterface { Ciência public static function setLogger( LoggerInterface $logger) { if (isset(self::$conn[self::$counter])) { self::$logger[self::$counter] = $logger; } else { throw new Exception(TAdiantiCoreTranslator::translate('No active transactions') . ': ' . __METHOD__); } } Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #35 } Antes, TLogger
  36. 36. E o que mais? ● Práticas! – Onde colocar tal método? M,V, ou C? Qual das M? – Nunca a Model deve dar TMessage; – Model não deve dar return em falha, só throw; – Nunca abrir transaction na model (Model agnóstica); – Catch sempre na controller; – Transactions somente na controller; – Usar getByAlgumaCoisa(). Ex: Turma::getByCodigo(); – Objetos de negócio sempre em Português (Aluno); – Métodos-ação imprimeAluno(), geraCertificado(). Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #36
  37. 37. Ferramentas profissionais
  38. 38. Studio Pro <?php /** * Customer Active Record * @author <your-name-here> */ class Customer extends TRecord { public function get_city() public function addSkill(Skill $skill) public function getSkills() public function load($id) public function store() public function delete($id = NULL) Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #38 } ?> XMI SQL PHP Modelo Astah StarUML
  39. 39. Studio Pro ● Se o modelo for feito antes, facilita tudo; ● Relações podem ser definidas por wizards também. <?php // load customer $obj = new Customer(5); print $obj->city->name; print $obj->category->name; // percorre contatos foreach ($obj->getContacts() as $contact) { $contact->value = '9'.$contact->value; $contact->store(); print $contact->type . '-' . $contact->value; } foreach ($obj->getSkills() as $skill) { print $skill->name; } Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #39
  40. 40. Studio Form Designer Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #40
  41. 41. Studio Form Designer class TestView extends TPage { private $form; function __construct() { parent::__construct(); $this->form = new TForm; try { Wrapper $ui = new TUIBuilder(500,300); $ui->setController($this); $ui->setForm($this->form); $ui->parseFile('app/forms/sample.form.xml'); $this->form->add($ui); $this->form->setFields($ui->getFields()); } catch (Exception $e) { new TMessage('error', $e->getMessage()); } parent::add($this->form); Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #41
  42. 42. Studio PDF Designer Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #42
  43. 43. Studio PDF Designer class PDFDesignNFEView extends TPage { function onGenerate() { Wrapper try { $designer = new TPDFDesigner; $designer->fromXml('app/reports/nfe.pdf.xml'); $designer->generate(); $designer->SetFont('Arial', 'B', 8); $designer->setFontColorRGB( '#4C4491' ); $designer->writeAtAnchor('bairro', 'Centro'); $designer->writeAtAnchor('municipio', 'Cidade teste'); $designer->writeAtAnchor('fone', '(11) 1234-5678'); $designer->gotoAnchorXY('details'); $designer->SetFont('Arial', '', 8); $designer->Cell( 62, 10, '12121212', 1, 0, 'C'); $designer->Cell(140, 10, utf8_decode('Guaraná'), 1, 0, 'L'); } //... Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #43
  44. 44. Obrigado ● Adianti Framework: – www.adianti.com.br/framework ● Contato: – www.dalloglio.net – www.adianti.com.br – @pablodalloglio – @adiantisolution ● Não esquecer de falar do Sorteio! Adianti Solutions Ltda © Pablo Dall'Oglio Programando para Programadores #44

×