Save 37% off PRO during our Black Friday Sale! »

O Que Há de Novo no PHP 8.0 e 8.1?

O Que Há de Novo no PHP 8.0 e 8.1?

O PHP tem evoluído constantemente nos últimos anos e hoje em dia conta com um robusto ecossistema de ferramentas e funcionalidades. E, em breve, será lançado o PHP 8.1. Ela será uma das releases com um grande número de funcionalidades. Nesta apresentação falaremos sobre as novidades do PHP 8.0 e 8.1 como named arguments, attributes, constructor property promotion, match expressions, enums, array unpacking com chaves em string, readonly properties entre outras novidades.

52711e2157a6fed933b0361cc06a6953?s=128

Marcel dos Santos

November 04, 2021
Tweet

Transcript

  1. Marcel Gonçalves dos Santos @marcelgsantos php 8.0 o que há

    de novo ? 8.1 no e
  2. pensandonaweb.com.br desenvolvedor web full-stack Marcel Gonçalves dos Santos @marcelgsantos

  3. @phpsp phpsp.org.br

  4. Interaja nas mídias sociais! 
 
 - fale sobre o

    evento, palestrantes e conteúdo - tire fotos do evento e publique 
 - interaja com outros participantes do evento - tire dúvidas ou dê feedbacks para os palestrantes 
 - utilize as hashtags#php80 e #php81
  5. O início!

  6. o PHP existe há mais de 20 anos e passou

    por várias "revoluções"
  7. na versão 5.3 o PHP incluiu o suporte para namespaces,

    funções lambda e closures
  8. a criação do PHP-FIG permitiu a de fi nição de

    padrões pela comunidade
  9. o Composer causou uma transformação na forma como bibliotecas são

    distribuídas e como aplicações PHP são desenvolvidas
  10. na versão 7.0 fi cou de 2 a 3 vezes

    mais rápido e inúmeras funcionalidades foram adicionadas ao core da linguagem
  11. na versão 7.4 foram introduzidas funcionali- dades muito solicitadas como

    propriedades tipadas, arrow functions e spread operator
  12. o PHP como plataforma encontra-se mais maduro e robusto

  13. C om pos e , L ar avel, Symf on

    y, Laminas, Slim, Sil er , CakePHP , Yii, CodeIgnit e , Pr oo ph, Doc tr ine, Guzzle, Respect, PHP League, M on ol og, PHP-DI, FastR ou te, Psl, PHP-DS, Symf on y C om p on ents, Ramsey Uuid, Email Validat o , C ar b on , Flysystem, CLImate, Psysh, Twig, Fak e , Int er venti on Image, Ass er t, Depl oyer , PHPMail e , Magento, W or dPress, Drupal, J oo mla, Sylius, OpenC ar t, W oo C om m er ce, PhpSt or m, Sw oo le, ReactPHP , Amp, Rev ol t PHP , Hyp er f, Octane, Bref, Vap o , XDebug, phpdbg, PHPStan, Psalm, Phan, PHPUnit, Codecepti on , Pest, Infecti on PHP , PHPSpec, Pr op hecy, Mock er y, Behat, PHP CodeSniff e , PHP CS F ixer , PHPMD, PHPCPD, GrumPHP , CaptainH oo k, Dep tr ac, Rect or , phpDocument o , Safe-PHP
  14. mas sempre existe espaço para melhorar!

  15. a próxima grande novidade do PHP é o lançamento da

    versão 8.1, uma release com muitas novidades
  16. Uma breve revisão sobre PHP 7.4!

  17. Typed properties ou propriedades tipadas

  18. as typed properties ou propriedades tipadas era uma das funcionalidades

    mais esperadas do PHP 7.4
  19. o PHP teve uma enorme evolução no seu sistema de

    tipos com o passar do tempo
  20. utilizavam-se docblocks e métodos getters e setters para a garantia

    de tipos
  21. isso fazia com que, para ter essa garantia, houvesse um

    boilerplate desnecessário de código
  22. / / code with unnecessary boilerplate to enforce type contracts

    class User { /** @var int $id * / private $id; /** @var string $name * / private $name; public function _ _ construct(int $id, string $name) { $this - > id = $id; $this - > name = $name; } public function getId() : int { return $this - > id; } / / setId, getName and setName implementation . . . }
  23. / / more concise code with same type contracts class

    User { public int $id; public string $name; public function _ _ construct(int $id, string $name) { $this - > id = $id; $this - > name = $name; } }
  24. as propriedades tipadas permitem a garantia de tipos em tempo

    de execução
  25. se uma propriedade tipada não tiver um valor padrão ela

    será considerada não inicializada
  26. / / uninitialized properties and default null class User {

    public $id; public $name; } $user = new User; var_dump($user); / / class User#1 (2) { / / public $id = > NULL / / public $name = > NULL / / }
  27. / / uninitialized properties and no null default class User

    { public int $id; / / no null default public ?string $name; / / also no null default } $user = new User; var_dump($user); / / object(User)#1 (0) { / / ["id"] = > uninitialized(int) / / ["name"] = > uninitialized(?string) / / }
  28. ao tentar fazer a leitura de uma propriedade não inicializada

    será lançado um erro do tipo TypeError
  29. / / try to access a uninitialized property class User

    { public int $id; public string $name; } $user = new User; echo $user - > id; / / Uncaught Error: Typed property User : : $id must 
 / / not be accessed before initialization
  30. Arrow functions

  31. as funções anônimas no PHP são bastante verbosas, principalmente quando

    vamos realizar uma operação simples
  32. isso ocorre por causa do boilerplate sintático 
 e a

    necessidade de importar as variáveis que serão utilizadas no escopo interno da função
  33. / / anonymous function (more verbose) $add = function ($x,

    $y) { return $x + $y; };
  34. a funcionalidade de arrow functions do PHP 7.4 torna mais

    concisa a sintaxe para esse padrão
  35. / / anonymous function (less verbose) $add = fn ($x,

    $y) = > $x + $y;
  36. a sintaxe de uma arrow function é a seguinte: fn(parameter_list)

    => expression
  37. ao utilizar uma variável de fi nida no escopo externo

    ela será passada de forma implícita para a expressão
  38. / / variable in parent scope is captured - by

    - value 
 $y = 1; $inc1 = fn($x) = > $x + $y; $inc2 = function ($x) use ($y) { return $x + $y; };
  39. as arrow function podem ser utilizadas inúmeros casos do dia-a-dia

  40. / / arrow functions help reducing code boilerplate 
 /

    / (without arrow functions) $result = Collection : : from([1, 2]) - > map(function ($v) { return $v * 2; }) - > reduce(function ($tmp, $v) { return $tmp + $v; }, 0); echo $result; / / 6
  41. / / arrow functions help reducing code boilerplate / /

    (with arrow functions) 
 $result = Collection : : from([1, 2]) - > map(fn($v) = > $v * 2) - > reduce(fn($tmp, $v) = > $tmp + $v, 0); echo $result; / / 6
  42. Spread operator em arrays

  43. o PHP possui o suporte ao argument unpacking desde a

    versão 5.6 da linguagem
  44. a funcionalidade permite o "desempacota- mento" de um array (ou

    um Traversable) em uma lista de argumentos utilizando o spread operator
  45. function sum($a, $b) { return $a + $b; } /

    / using spread operator ( . . . ) to unpacking / / an array as an argument list $numbers = [3, 5]; echo sum( . . . $numbers); / / 8
  46. a utilização do spread operator não era permitido na de

    fi nição de arrays a partir de outro array
  47. / / using spread operator to def i ne a

    new array / / from another is not supported $someNumbers = [2, 3, 4]; $numbers = [1, . . . $someNumbers, 5]; print_r($numbers); 
 / / PHP Parse error: syntax error, unexpected ' . . . ' 
 / / (T_ELLIPSIS), expecting ']'
  48. a nova funcionalidade permite a utilização do spread operator (...)

    na de fi nição de um array através do unpacking dos valores de outro array ou Traversable
  49. / / using spread operator to def i ne an

    array from another $someNames = ['Bob', 'Carol']; $names = ['Alice', . . . $someNames, 'Daniel', 'Elisa']; print_r($names); / / ['Alice', 'Bob', 'Carol', 'Daniel', 'Elisa'];
  50. essa funcionalidade só está disponível para arrays com chaves numéricas

    (para manter consistência com o argument unpacking)
  51. Numeric literal 
 separator

  52. ei, você consegue identi fi car que número é esse?

  53. 1000000000;

  54. é um bilhão, 100 milhões ou 10 bilhões?

  55. Difícil, né?!

  56. o olho de um ser humano não é otimizado para

    olhar e decodi fi car rapidamente uma sequências de dígitos
  57. e esse valor?

  58. $discount = 13500;

  59. é 13.500 ou 135 porque está em centavos?

  60. a nova funcionalidade numeric literal separator permite usar um separador

    visual para ajudar a melhorar a legibilidade do código e transmitir informações adicionais
  61. / / using numeric literal separator $threshold = 1_000_000_000; /

    / a billion $testValue = 100_925_284.88; / / scale is hundreds of millions $discount = 135_00; / / $135, stored as cents
  62. adicionar o underscore entre dois dígitos não alterará o seu

    valor
  63. / / adding undescore doesn't change the value var_dump(1_000_000); /

    / int(1000000)
  64. PHP 8.0

  65. Named arguments

  66. até o PHP 7.4 os argumentos só podiam ser passados

    de forma posicional
  67. a partir do PHP 8.0 é possível passar os argumentos

    de forma nomeada
  68. os argumentos nomeados permitem passar os argumentos para uma função

    com base no nome do parâmetro em vez da posição do parâmetro
  69. os benefícios de usar argumentos nomeados são: -o argumento é

    auto-explicativo -pode ser usado em qualquer posição -pode omitir valores padrões
  70. function greet(string $gretting, string $name, int $number = 1) :

    void { echo "$gretting, $name" . str_repeat('!', $number) . PHP_EOL; } / / using positional and named arguments greet("Hello", "Alice"); / / Hello Alice! greet(gretting: "Hello", name: "Bob"); / / Hello Bob! / / using of out - of - order named arguments greet("Hello", name: "Carol"); / / Hello Carol! greet(name: "Dave", gretting: "Hello", number: 5); / / Hello, Dave!!!!!
  71. Attributes

  72. os attributes permitem adicionar meta- informações a elementos do código

    como classes, propriedades, métodos, funções, parâmetros e constantes
  73. eles permitem de fi nir diretivas de con fi guração

    diretamente no código
  74. conceitos similares existem em outras linguagens como annotations em Java,

    attributes em C#, Rust e Hack e decorators em Python e JavaScript
  75. os attributes ou annotations eram feitos com docblock e utilizam

    bibliotecas como Doctrine Annotations
  76. / / using docblock annotations in order to mapping /

    / an entity to database table class User { /** @Column(type="int") * / private int $id; /** @Column(type="string", length=100) * / private int $name; }
  77. agora, como parte da linguagem, os attributes podem ter a

    sintaxe veri fi cada 
 por linters, ferramentas de análise 
 estática e IDEs
  78. outra vantagem é não precisar de uma biblioteca externa para

    realizar o parsing de docblocks para obter as annotations
  79. / / using annotations with built - in attributes class

    User { # [ Column(type: 'integer')] private int $id; # [ Column(type: 'string', length: 100)] private int $name; }
  80. o Doctrine ORM 2.9 passou a suportar attributes e typed

    properties
  81. None
  82. o Symfony 5.2 também passou a suportar attributes

  83. None
  84. / / route def i nition in Symfony using attributes

    class SomeController { # [ Route('/path', name: 'action')] public function someAction() { / / . . . } }
  85. Constructor 
 Property Promotion

  86. durante a modelagem de objetos se faz necessário a utilização

    de muito boilerplate
  87. cada propriedade é repetida 4 vezes e seu tipo é

    repetido 2 vezes
  88. / / user implementation with excessive boilerplate class User {

    public int $id; public string $name; public string $email; public function _ _ construct( int $id, string $name, string $email ) { $this - > id = $id; $this - > name = $name; $this - > email = $email; } }
  89. $user1 = new User(1, "Alice", "alice@example.com"); print_r($user1); / / User

    Object / / ( / / [id] = > 1 / / [name] = > Alice / / [email] = > alice@example.com / / )
  90. a partir do PHP 8.0 pode-se utilizar a funcio- nalidade

    constructor property promotion
  91. ela permite a redução do boilerplate para a criação de

    um objeto
  92. / / using constructor property promotion / / and with

    no boilerplate class User { public function _ _ construct( public int $id, public string $name, public string $email ) {} }
  93. $user2 = new User(2, "Bob", "bob@example.com"); print_r($user2); / / User

    Object / / ( / / [id] = > 2 / / [name] = > Bob / / [email] = > bob@example.com / / )
  94. essa funcionalidade pode ser combinada com outras funcionalidades novas e

    reduzir a quantidade de código e torná-lo mais expressivo
  95. a constructor property promotion está disponível em diversas linguagens como

    Kotlin e TypeScript
  96. Union Types

  97. um union type permite fazer uma anotação de tipos com

    diferentes tipos
  98. o PHP já possui dois tipos especiais de union types:

    nullable types e iterable
  99. o tipo nullable possui a sintaxe ?Type e pode ser

    do tipo Type|null, isto é, Type ou null
  100. o tipo iterable pode ser do tipo array| Traversable, isto

    é, array ou Traversable
  101. declare(strict_types=1); / / using union type to annotate a function

    parameter function power(float|int $number, int $exponent) : int|float { return $number * * $exponent; } echo power(3, 2); / / 9 echo power(3.5, 2); / / 12.25 echo power('3', 2.5); / / Uncaught TypeError: power() : Argument #1 ($number) must be of / / type int|float, string given
  102. os union types são muito utilizado em projetos open-source e

    funções internas do PHP
  103. o suporte nativo permite a garantia de tipos pelo interpretador

    PHP e reduz a necessida- de de docblocks
  104. Throw Expression

  105. no PHP throw é uma instrução e não era possível

    lançar exceções em locais onde apenas expressões são permitidas…
  106. …como arrow functions, null coalesce operator e operador ternário

  107. / / trying to throw an exception in an expression

    shows a / / syntax error in PHP 7.4 $function = fn() = > throw new Exception('an exception was thrown'); $function(); / / Parse error: syntax error, unexpected 'throw' (T_THROW) $nullableValue = null; $value = $nullableValue ? ? throw new Exception('null is not allowed'); / / Parse error: syntax error, unexpected 'throw' (T_THROW)
  108. a partir do PHP 8.0 passou a ser possível utilizar

    throw como uma expressão
  109. essa funcionalidade é bastante útil e torna o código bastante

    expressivo
  110. / throwing an exception in an expression $function = fn()

    = > throw new Exception('an exception was thrown'); $function(); / / PHP 8.0 - Fatal error: Uncaught Exception: an exception was thrown $nullableValue = null; $value = $nullableValue ? ? throw new Exception('null is not allowed'); / / PHP 8.0 - Fatal error: Uncaught Exception: null is not allowed
  111. Match Expressions

  112. costuma-se utilizar o switch para criar valores que serão usados

    no futuro
  113. porém, o switch tem os seguintes problemas: - esquecer a

    atribuição da variável em algum dos casos 
 - possuir um boilerplate execessivo
  114. / / switch has an excessive boilerplate and is error

    prone switch ($statusCode) { case 200 : $reasonPhrase = 'Ok'; break; case 201 : $reasonPhrase = 'Created'; break; case 400 : $reasonPhrase = 'Bad Request'; break; } echo $result; / / Created
  115. o match não necessita da palavra-chave break após cada branch

    e possui um boilerplate mais enxuto
  116. / / match is less verbose and more expressive echo

    match ($statusCode) { 200 = > 'Ok', 201 = > 'Created', 400 = > 'Bad Request', }; / / Created
  117. o switch utiliza a comparação fraca (==) e pode levar

    a erros
  118. o match utiliza a comparação estrita (===) e leva a

    uma avaliação mais previsível independente do uso de strict_types
  119. utilização do break é uma das maiores fontes de erros

    ao utilizar o switch case
  120. o match resolve este problema por ter um break implícito

    em cada branch
  121. o match pode ter múltiplas condições para uma mesma branch

  122. / / match with multiple conditions for the same branch

    echo match($country) { 'brazil', 'portugal' = > 'portuguese', 'argentina', 'spain', 'mexico' = > 'spanish', default = > 'another language' }; / / portuguese
  123. a match expression deve contemplar todas as opções possíveis

  124. caso não exista uma branch correspondente ao resultado da expressão

    a exceção UnhandledMatchError será lançada
  125. PHP 8.1

  126. Enums

  127. uma enum de fi ne um novo tipo que possui

    número fi xo e limitado de valores possíveis
  128. / / creates an enum for the suits of a

    deck enum Suit { case Hearts; case Diamonds; case Clubs; case Spades; } $value = Suit : : Hearts; var_dump($value); / / enum(Suit : : Hearts)
  129. por padrão, os casos enumerados não possuem equivalentes escalares, isto

    é, eles são objetos singleton
  130. esse tipo de valor é chamado de pure case e

    uma enum que contém apenas pure cases é chamada de pure enum
  131. uma função ou método podem ser tipados com o tipo

    enum
  132. / / annotate a function parameter with an enum type

    function pick_a_card(Suit $suit) {} pick_a_card($value); / / ok pick_a_card(Suit : : Clubs); / / ok pick_a_card('Spades'); / / Fatal error: Uncaught TypeError: pick_a_card() : Argument #1 ($suit) must be of type Suit, string given
  133. contudo, existem casos de usos em que é necessário ter

    equivalentes escalares para, por exemplo, persistir em uma base de dados
  134. / / create an enum backed with scalar values enum

    Suit: string { case Hearts = 'H'; case Diamonds = 'D'; case Clubs = 'C'; case Spades = 'S'; } var_dump(Suit : : Hearts); / / enum(Suit : : Hearts) var_dump(Suit : : Hearts - > name); / / string(6) "Hearts" var_dump(Suit : : Hearts - > value); / / string(1) "H"
  135. um case que possui um equivalente escalar é chamado de

    backed case pois é "suportado" por um valor mais simples
  136. uma enum que contém backed cases é chamado de backed

    enum
  137. um backed enum implementa a interface interna BackedEnum que expõe

    dois méto- dos adicionais que são from e tryFrom
  138. o método from() recebe um tipo escalar e retorna o

    case correspondente e, caso o valor não seja encontrado, a exceção ValueError é lançada
  139. o método tryFrom() possui comportamento similar mas, caso o valor

    não seja encontra- do, retorna nulo
  140. / / create an order status enum enum OrderStatus: string

    { case PendingPayment = 'pending_payment'; case Processing = 'processing'; case Completed = 'completed'; case Refunded = 'refunded'; case Cancelled = 'cancelled'; }
  141. / / create an enum value using from() method with

    a valid value $orderStatus = OrderStatus : : from('processing'); var_dump($orderStatus); / / enum(OrderStatus : : Processing) echo $orderStatus - > value; / / processing / / try to create an enum value using from() method with an invalid value $orderStatus = OrderStatus : : from('non_existent'); / / Fatal error: Uncaught ValueError: "non_existent" is not a / / valid backing value for enum "OrderStatus" / / try to create an enum value using tryFrom() method with an invalid value $orderStatus = OrderStatus : : tryFrom('non_existent'); var_dump($orderStatus); / / null
  142. Array Unpacking com chaves em string

  143. o PHP 7.4 tinha adicionado suporte para o unpacking de

    arrays em outros arrays utilizando o spread operator
  144. porém, não era permitido o unpacking de arrays com chaves

    em string devido: (1) incertezas em relação a semântica e (2) limitações no argument unpacking
  145. a limitação do argument unpacking foi resolvida graças a introdução

    de named arguments no PHP 8.0
  146. / / using array unpacking with string keys $user1 =

    ['name' = > 'Alice']; $user2 = ['name' = > 'Bob']; / / array unpacking with string keys in PHP 8.0 returns an error var_dump(array_merge($user1, $user2)); / / ["name" = > "Bob"] var_dump([ . . . $user1, . . . $user2]); / / Fatal error: Uncaught Error: Cannot unpack array with string keys / / array unpacking with string keys in PHP 8.1 var_dump(array_merge($user1, $user2)); / / ["name" = > "Bob"] var_dump([ . . . $user1, . . . $user2]); / / ["name" = > "Bob"]
  147. com o suporte a essa funcionalidade é possível combinar as

    funcionalidades de argument unpacking e named arguments
  148. / / combine argument unpacking with named arguments function greet(string

    $greeting, string $name) : void { echo "$greeting, $name!" . PHP_EOL; } greet('Hi', 'Alice'); / / Hi, Alice! greet('Bonjour', 'Bob'); / / Bonjour, Bob! greet(greeting: 'Hallo', name: 'Carol'); / / Hallo, Carol! greet( . . . ['name' = > 'David', 'greeting' = > 'Konichiwa']); / / Konichiwa David!
  149. Readonly Property

  150. uma propriedade readonly não pode ser modi fi cada após

    a sua inicialização
  151. os objetos de valores são imutáveis, isto é, as propriedades

    são inicializadas no construtor e não podem ser alteradas depois
  152. a alternativa mais próxima disso é declarar uma propriedade privada

    e expor um getter público
  153. / / create an email value object and exposes a

    getter method class Email { public function _ _ construct( private string $value ) { / / throws an exception if the email is not valid $this - > value = $value; } public function value() : string { return $this - > value; } }
  154. o lado negativo dessa abordagem é necessitar de um boilerplate

    maior
  155. / / create an email value object with less boilerplate

    class Email { public function _ _ construct( public readonly string $value ) { / / throws an exception if the email is not valid $this - > value = $value; } } $email = new Email('johndoe@example.com'); echo($email - > value); / / johndoe@example.com $email - > value = 'foobar@example.com'; / / Uncaught Error: Cannot modify readonly property Email : : $value
  156. uma propriedade readonly só pode ser inicializada uma vez e

    somente no escopo em que ela foi declarada
  157. o modi fi cador readonly só pode ser aplicado em

    propriedades tipadas
  158. First-Class Callable Syntax

  159. trata-se de uma nova sintaxe para a criação de closures

  160. / / create closures with both old and new syntax

    $strlen1 = Closure : : fromCallable('strlen'); $strlen2 = strlen( . . . );
  161. essa sintaxe é mais expressiva, acessível a ferramentas de análise

    estática e respeita o escopo em que foi criada
  162. Type Never

  163. o tipo never é um novo tipo de retorno adicionado

    no PHP 8.1
  164. uma função ou método declarados com o tipo de retorno

    never indica que nunca retornará um valor…
  165. …isto é, sempre lançará uma exceção ou terminará com die

    ou exit
  166. / creates a function with never return type function redirect(string

    $url) : never { echo "redirected to $url . . . \n"; header('Location: ' . $url); exit(); } redirect('https: / / w w w .example.com'); / / redirected to https: / / w w w .example.com . . . 
 / / The rest of the code will not be executed! echo 'this will not be shown . . . ';
  167. o objetivo do tipo de retorno never é indicar uma

    função que previne que o resto do código chamado seja executado
  168. se uma função ou método com o tipo de retorno

    never não lançar uma exceção ou não terminar o programa será lançada a exceção TypeError
  169. / / create a function with never return type function

    dispatch(string $message) : never { echo $message; } dispatch('test'); / / Uncaught TypeError: dispatch() : never - returning / / function must not implicitly return
  170. o suporte ao tipo de retorno never torna possível não

    utilizar mais a annotation @return noreturn
  171. Quando vou poder 
 utilizá-lo?

  172. o PHP 8.1 estará disponível a partir do fi nal

    de novembro de 2021
  173. porém, as versões RC ou release candidates podem ser utilizadas

    agora para testes
  174. Ok! Como faço para 
 utilizá-lo neste momento?

  175. a forma mais prática é a utilização de uma imagem

    Docker
  176. None
  177. $ docker image pull php:8.1.0RC5-cli - alpine3.14

  178. $ alias php81='docker container run - it - - rm

    - v `pwd`:/app - w /app php:8.1.0RC5-cli - alpine3.14 php’
  179. $ php81 - - version 
 PHP 8.1.0RC5 (cli) (built:

    Oct 28 2021 23 : 22 : 17) (NTS) Copyright (c) The PHP Group Zend Engine v4.1.0-dev, Copyright (c) Zend Technologies
  180. dependendo do editor que você utilizar, você pode ter problemas

    com o syntax highlight e o aviso de erros do editor
  181. o PhpStorm 2021.2 já possui suporte para algumas funcionalidades do

    PHP 8.1
  182. Conclusão

  183. o PHP tem tido uma enorme evolução e tem se

    tornado uma linguagem mais robusta
  184. porém, sem perder a fl exibilidade e a pequena curva

    de aprendizado que o torna uma linguagem tão democrática
  185. as novas funcionalidades ajudarão o seu código a ter mais

    garantias, ser mais expressivo e te de dar mais poderes
  186. vá em frente e divirta-se!

  187. Avalie!

  188. @marcelgsantos speakerdeck.com/marcelgsantos Obrigado. Perguntas?