MELHORANDO
SUA API COM
DSLS@augustohp
DOMAIN SPECIFIC LANGUAGE
A SEGUIR,
UMA
DSL
VALIDAÇÃO
1 <?php
2
3 use RespectValidationValidator as v;
4
5 v::stringType()
6 ->exactLength(8)
7 ->contains("-")
8 ->contains("/^[A-Z]{3}/")
9 ->contains("/[0-9]{4}$/")
10 ->assert($something);
1 <?php
2
3 namespace EasyTaxiValidationRules;
4
5 use RespectValidationRules as BaseRules;
6
7 class CarPlate extends BaseRulesAllOf
8 {
9 public function __construct()
10 {
11 $this->name = 'Brazilian car plate';
12
13 parent::__construct(
14 new BaseRulesStringType(),
15 new ExactLength(8),
16 new BaseRuleContains("-")->setName('Separator'),
17 new BaseRuleContains("/^[A-Z]{3}/")->setName("Prefix")
18 new BaseRuleContains("/^[0-9]{4}/")->setName("Sufix")
19 );
20 }
21 }
1 <?php
2
3 namespace EasyTaxiValidationRules;
4
5 use RespectValidationRules as BaseRules;
6
7 class CarPlate extends BaseRulesAllOf
8 {
9 public function __construct()
10 {
11 $this->name = 'Brazilian car plate';
12
13 parent::__construct(
14 new BaseRulesStringType(),
15 new ExactLength(8),
16 new BaseRuleContains("-")->setName('Separator'),
17 new BaseRuleContains("/^[A-Z]{3}/")->setName("Prefix")
18 new BaseRuleContains("/^[0-9]{4}/")->setName("Sufix")
19 );
20 }
21 }
1 <?php
2
3 // ... dentro de algum método de um Controller ...
4
5 ValidationValidator::arrayType()
6 ->key('driver_name', v::driverName())
7 ->key('driver_birthdate', v::minimumAge(18))
8 ->key(
9 'driver_car', v::arrayType(
10 v::key("model", v::carModel($this->get('model.vehicle'))),
11 v::key("assembler", v::carAssembler()),
12 v::key("year", v::maximumAge(5)),
13 v::key("plate", v::carPlate())
14 )
15 )
16 ->key('license_number', v::driverLicense())
17 ->key('taxi_permission', v::taxiPermissionNumber())
18 ->key('address', v::address())
19 ->assert($_POST);
COMO
CRIAR
ISSO?
1 <?php
2
3 namespace RespectValidation;
4
5 class Validator
6 {
7 public function stringType() {}
8 public function contains($search) {}
9 public function assert($mixed) {}
10 }
1 <?php
2
3 namespace RespectValidation;
4
5 interface Rule
6 {
7 public function isValid($mixed): bool;
8 }
IMPLEMENTANDO
UMA
REGRA
1 <?php
2
3 namespace RespectValidationRules;
4
5 use RespectValidation;
6
7 class StringType implements ValidationRule
8 {
9 public function isValid($mixed): bool
10 {
11 return is_string($mixed);
12 }
13 }
3 namespace RespectValidationRules;
4
5 use RespectValidation;
6
7 class AllOf implements ValidationRule
8 {
9 protected $rules = [];
10
17 public function __construct(ValidationRule ...$rules)
18 {
19 $this->rules = $rules;
20 }
21
22 public function isValid($mixed): bool
23 {
24 foreach ($this->rules as $rule) {
25 if (false === $rule->isValid($mixed)) {
26 return false;
27 }
28 }
29
30 return true;
31 }
32 }
INSTANCIANDO
REGRAS
1 <?php
2
3 $factory = new RespectValidationRuleFactory;
4 $rule = $factory->createInstance('StringType');
1 <?php
2
3 namespace RespectValidation;
4
5 class RuleFactory
6 {
7 public function createInstance($ruleName, array $args = []): Rule
8 {
9 $ruleNamespace = 'RespectValidationRules';
10 $className = $ruleNamespace . $rule;
11 $reflection = new ReflectionClass($className);
12
13 return $reflection->newInstanceArgs($args);
14 }
15 }
PODEMOS
TER
MENOS
CÓDIGO ?
1 <?php
2
3 namespace RespectValidation;
4
5 class RuleFactory
6 {
7 public function __call($methodName, $methodArguments)
8 {
9 return $this->createInstance($methodName, $methodArguments);
10 }
11
12 public function createInstance($ruleName, array $args = []): Rule
13 {
14 /* ... */
15 }
16 }
1 <?php
2
3 $checkFor = new RespectValidationRuleFactory;
4
5 $isString = $checkFor->StringType();
6
7 $isTwitterUserName = $checkFor->AllOf(
8 $checkFor->AlNum(),
9 $checkFor->NoWhitespace(),
10 $checkFor->Length(1, 15)
11 );
PODEMOS
TER
MENOS
CÓDIGO ?
3 namespace RespectValidation;
4
5 class RuleFactory
6 {
7 private static $factory = null;
8
9 public static function getInstance()
10 {
11 if (is_null(self::$factory)) {
12 self::$factory = new static;
13 }
14
15 return self::$factory;
16 }
17
18 public static function __callStatic($methodName, $methodArguments)
19 {
20 $factory = self::getFactory();
21
22 return $factory->createInstance($methodName, $methodArguments);
23 }
24
25 /* ... */
26 }
1 <?php
2
3 use RespectValidationRuleFactory as v;
4
5 $isTwitterUserName = v::AllOf(
6 v::AlNum(),
7 v::NoWhitespace(),
8 v::Length(1, 15)
9 );
@ANNOTATION
O QUE TEMOS
1 <?php
2
3 namespace EasyTaxiValidation;
4
5 interface Rule
6 {
7 public function isValid($mixed): bool;
8 }
1 <?php
2
3 namespace EasyTaxiValidationRules;
4
5 use EasyTaxiValidation;
6 use RespectValidationValidator as v;
7
8 class PlateValidator implements Rule
9 {
10 public function isValid($mixed): bool
11 {
12 return v::stringType()
13 ->exactLength(8)
14 ->contains("-")
15 ->contains("/^[A-Z]{3}/")
16 ->contains("/[0-9]{4}$/")
17 ->setName('Car plate')
18 ->validate($mixed);
19 }
20 }
O QUE QUEREMOS
1 <?php
2
3 namespace EasyTaxiDriver;
4
5 class Car
6 {
7 /**
8 * @PlateValidator
9 */
10 private $plate = '';
11
12 public function __construct($plate)
13 {
14 $this->plate = $plate;
15 }
16 }
1 <?php
2
3 use EasyTaxiAnnotation;
4 use EasyTaxiValidation;
5
6 $filter = new AnnotationFilter();
7 $factory = new AnnotationFactory($filter);
8 $validator = new ValidationValidator($factory);
9 $fusca = new Car('AAA-1111');
10
11 $validator->annotations($fusca);
FILTRANDO
A PARTE
INTERESSANTE
DE UM
COMENTÁRIO
1 <?php
2
3 namespace EasyTaxiAnnotation;
4
5 class Filter
6 {
7 public function firstAnnotation($doc): string {
8 foreach ($this->breakLines($doc) as $line) {
9 if (false === $this->hasAnnotation($line)) {
10 continue;
11 }
12
13 return $this->filterName($line);
14 }
15 }
16
17 private function breakLines($doc): array {
18 return explode(PHP_EOL, $doc);
19 }
20
21 private function hasAnnotation($line): bool {
22 return false !== strpos($line, '@');
23 }
24
25 private function filterName($line): string {
26 return trim(str_replace(['*', '/', '@'], '', $line));
27 }
28 }
CRIANDO
REGRAS A PARTIR
DE UM
COMENTÁRIO
3 namespace EasyTaxiAnnotation;
4
5 class Factory
6 {
7 private $filter = null;
8
9 public function __construct(Filter $annotationFilter)
10 {
11 $this->filter = $annotationFilter;
12 }
13
14 public function createFromProperty($instance, $propertyName)
15 {
16 $object = new ReflectionObject($instance);
17 $property = $object->getProperty($propertyName);
18
19 return $this->createInstanceFromComment($property->getDocComment());
20 }
21
22 private function createInstanceFromComment($doc)
23 {
24 $annotationClass = $this->filter->firstAnnotation($doc);
25 $class = new ReflectionClass($annotationClass);
26
27 return $class->newInstance();
28 }
29 }
JUNTANDO
TUDO
NUM
MONTINHO
SÓ
3 namespace EasyTaxiValidation;
4
5 use EasyTaxiAnnotation;
6
7 class Validator
8 {
9 private $annotationFactory = null;
10
11 public function __construct(AnnotationFactory $factory) {
12 $this->annotationFactory = $factory;
13 }
14
15 public function annotations($object) {
16 $annotation = $this->annotationFactory;
17 $class = new ReflectionObject($object);
18 $properties = $class->getProperties();
19 foreach ($properties as $property) {
20 $propertyName = $property->getName();
21 $rule = $annotation->createFromProperty($object, $propertyName);
22 if ($rule->isValid($object)) {
23 continue;
24 }
25
26 throw new Exception("$propertyName is not valid.");
27 }
28 }
29 }
1 <?php
2
3 use EasyTaxiAnnotation;
4 use EasyTaxiValidation;
5
6 $filter = new AnnotationFilter();
7 $factory = new AnnotationFactory($filter);
8 $validator = new ValidationValidator($factory);
9 $fusca = new Car('AAA-1111');
10
11 $validator->annotation($fusca);
OUTROS
EXEMPLOS
BEHAT
COMPOSER
PHING
DQL
PHPUNIT MOCK OBJECTS
A VIDA DE
UMA
DSL
DOMÍNIO
DSL
INTERNAO
DSL
EXTERNA
LIMITES DE
UMA
DSL
AUTOMATIZAR
TAREFAS
REPETITIVAS
<TARGET NAME=“TEST”>
<TARGET NAME=“DEPLOY”>
<TARGET NAME=“BUILD”>
<CONDITION>
<CONDITION>
FAIL
A SEGUIR,
UMA
MENSAGEM
1 <?php
2
3 use RespectValidationValidator as v;
4
5 v::stringType()
6 ->exactLength(8)
7 ->contains("-")
8 ->contains("/^[A-Z]{3}/")
9 ->contains("/[0-9]{4}$/")
10 ->assert($something);
A VIDA DE
UMA
MENSAGEM
99% JAPA
MAS
AQUELE 1%
É ITALIANO
PARA
QUEM
VOCÊ ESTÁ
FALANDO
PARA
QUEM
VOCÊ ESTÁ
CODANDO
UMA BOA
MENSAGEM
TEM LIMITES
FAZ USO
DE
CONHECIMENTO
PRÉVIO
FAZ USO
DE UM
VOCABULÁRIO
COMUM
DOMÍNIOS
QUE
#%$!*&
SÃO
DSLS
?
DSLS
SÃO
BOAS
MENSAGENS
DEPENDEM
DE BONS
DOMÍNIOS
SÃO MAIS
ESPECÍFICAS
DO QUE
LINGUAGENS
GENÉRICAS
POR ISSO
COMUNICAM
MAIS
COISAS
DESENVOLVIMENTO
É SOBRE
COMUNICAÇÃO
PERGUNTAS?
AGRADECIMENTOS
@NELSONSAR
@IVONASCIMENTO
@ALGANET @ITEASYTAXI
HTTP://BIT.LY/PHPX-DSLS

Melhorando sua API com DSLs

  • 1.
  • 2.
  • 3.
  • 4.
  • 6.
    1 <?php 2 3 useRespectValidationValidator as v; 4 5 v::stringType() 6 ->exactLength(8) 7 ->contains("-") 8 ->contains("/^[A-Z]{3}/") 9 ->contains("/[0-9]{4}$/") 10 ->assert($something);
  • 7.
    1 <?php 2 3 namespaceEasyTaxiValidationRules; 4 5 use RespectValidationRules as BaseRules; 6 7 class CarPlate extends BaseRulesAllOf 8 { 9 public function __construct() 10 { 11 $this->name = 'Brazilian car plate'; 12 13 parent::__construct( 14 new BaseRulesStringType(), 15 new ExactLength(8), 16 new BaseRuleContains("-")->setName('Separator'), 17 new BaseRuleContains("/^[A-Z]{3}/")->setName("Prefix") 18 new BaseRuleContains("/^[0-9]{4}/")->setName("Sufix") 19 ); 20 } 21 }
  • 8.
    1 <?php 2 3 namespaceEasyTaxiValidationRules; 4 5 use RespectValidationRules as BaseRules; 6 7 class CarPlate extends BaseRulesAllOf 8 { 9 public function __construct() 10 { 11 $this->name = 'Brazilian car plate'; 12 13 parent::__construct( 14 new BaseRulesStringType(), 15 new ExactLength(8), 16 new BaseRuleContains("-")->setName('Separator'), 17 new BaseRuleContains("/^[A-Z]{3}/")->setName("Prefix") 18 new BaseRuleContains("/^[0-9]{4}/")->setName("Sufix") 19 ); 20 } 21 }
  • 9.
    1 <?php 2 3 //... dentro de algum método de um Controller ... 4 5 ValidationValidator::arrayType() 6 ->key('driver_name', v::driverName()) 7 ->key('driver_birthdate', v::minimumAge(18)) 8 ->key( 9 'driver_car', v::arrayType( 10 v::key("model", v::carModel($this->get('model.vehicle'))), 11 v::key("assembler", v::carAssembler()), 12 v::key("year", v::maximumAge(5)), 13 v::key("plate", v::carPlate()) 14 ) 15 ) 16 ->key('license_number', v::driverLicense()) 17 ->key('taxi_permission', v::taxiPermissionNumber()) 18 ->key('address', v::address()) 19 ->assert($_POST);
  • 10.
  • 11.
    1 <?php 2 3 namespaceRespectValidation; 4 5 class Validator 6 { 7 public function stringType() {} 8 public function contains($search) {} 9 public function assert($mixed) {} 10 }
  • 13.
    1 <?php 2 3 namespaceRespectValidation; 4 5 interface Rule 6 { 7 public function isValid($mixed): bool; 8 }
  • 14.
  • 15.
    1 <?php 2 3 namespaceRespectValidationRules; 4 5 use RespectValidation; 6 7 class StringType implements ValidationRule 8 { 9 public function isValid($mixed): bool 10 { 11 return is_string($mixed); 12 } 13 }
  • 16.
    3 namespace RespectValidationRules; 4 5use RespectValidation; 6 7 class AllOf implements ValidationRule 8 { 9 protected $rules = []; 10 17 public function __construct(ValidationRule ...$rules) 18 { 19 $this->rules = $rules; 20 } 21 22 public function isValid($mixed): bool 23 { 24 foreach ($this->rules as $rule) { 25 if (false === $rule->isValid($mixed)) { 26 return false; 27 } 28 } 29 30 return true; 31 } 32 }
  • 17.
  • 18.
    1 <?php 2 3 $factory= new RespectValidationRuleFactory; 4 $rule = $factory->createInstance('StringType');
  • 19.
    1 <?php 2 3 namespaceRespectValidation; 4 5 class RuleFactory 6 { 7 public function createInstance($ruleName, array $args = []): Rule 8 { 9 $ruleNamespace = 'RespectValidationRules'; 10 $className = $ruleNamespace . $rule; 11 $reflection = new ReflectionClass($className); 12 13 return $reflection->newInstanceArgs($args); 14 } 15 }
  • 20.
  • 21.
    1 <?php 2 3 namespaceRespectValidation; 4 5 class RuleFactory 6 { 7 public function __call($methodName, $methodArguments) 8 { 9 return $this->createInstance($methodName, $methodArguments); 10 } 11 12 public function createInstance($ruleName, array $args = []): Rule 13 { 14 /* ... */ 15 } 16 }
  • 22.
    1 <?php 2 3 $checkFor= new RespectValidationRuleFactory; 4 5 $isString = $checkFor->StringType(); 6 7 $isTwitterUserName = $checkFor->AllOf( 8 $checkFor->AlNum(), 9 $checkFor->NoWhitespace(), 10 $checkFor->Length(1, 15) 11 );
  • 23.
  • 24.
    3 namespace RespectValidation; 4 5class RuleFactory 6 { 7 private static $factory = null; 8 9 public static function getInstance() 10 { 11 if (is_null(self::$factory)) { 12 self::$factory = new static; 13 } 14 15 return self::$factory; 16 } 17 18 public static function __callStatic($methodName, $methodArguments) 19 { 20 $factory = self::getFactory(); 21 22 return $factory->createInstance($methodName, $methodArguments); 23 } 24 25 /* ... */ 26 }
  • 25.
    1 <?php 2 3 useRespectValidationRuleFactory as v; 4 5 $isTwitterUserName = v::AllOf( 6 v::AlNum(), 7 v::NoWhitespace(), 8 v::Length(1, 15) 9 );
  • 27.
  • 28.
  • 29.
    1 <?php 2 3 namespaceEasyTaxiValidation; 4 5 interface Rule 6 { 7 public function isValid($mixed): bool; 8 }
  • 30.
    1 <?php 2 3 namespaceEasyTaxiValidationRules; 4 5 use EasyTaxiValidation; 6 use RespectValidationValidator as v; 7 8 class PlateValidator implements Rule 9 { 10 public function isValid($mixed): bool 11 { 12 return v::stringType() 13 ->exactLength(8) 14 ->contains("-") 15 ->contains("/^[A-Z]{3}/") 16 ->contains("/[0-9]{4}$/") 17 ->setName('Car plate') 18 ->validate($mixed); 19 } 20 }
  • 31.
  • 32.
    1 <?php 2 3 namespaceEasyTaxiDriver; 4 5 class Car 6 { 7 /** 8 * @PlateValidator 9 */ 10 private $plate = ''; 11 12 public function __construct($plate) 13 { 14 $this->plate = $plate; 15 } 16 }
  • 33.
    1 <?php 2 3 useEasyTaxiAnnotation; 4 use EasyTaxiValidation; 5 6 $filter = new AnnotationFilter(); 7 $factory = new AnnotationFactory($filter); 8 $validator = new ValidationValidator($factory); 9 $fusca = new Car('AAA-1111'); 10 11 $validator->annotations($fusca);
  • 34.
  • 35.
    1 <?php 2 3 namespaceEasyTaxiAnnotation; 4 5 class Filter 6 { 7 public function firstAnnotation($doc): string { 8 foreach ($this->breakLines($doc) as $line) { 9 if (false === $this->hasAnnotation($line)) { 10 continue; 11 } 12 13 return $this->filterName($line); 14 } 15 } 16 17 private function breakLines($doc): array { 18 return explode(PHP_EOL, $doc); 19 } 20 21 private function hasAnnotation($line): bool { 22 return false !== strpos($line, '@'); 23 } 24 25 private function filterName($line): string { 26 return trim(str_replace(['*', '/', '@'], '', $line)); 27 } 28 }
  • 36.
  • 37.
    3 namespace EasyTaxiAnnotation; 4 5class Factory 6 { 7 private $filter = null; 8 9 public function __construct(Filter $annotationFilter) 10 { 11 $this->filter = $annotationFilter; 12 } 13 14 public function createFromProperty($instance, $propertyName) 15 { 16 $object = new ReflectionObject($instance); 17 $property = $object->getProperty($propertyName); 18 19 return $this->createInstanceFromComment($property->getDocComment()); 20 } 21 22 private function createInstanceFromComment($doc) 23 { 24 $annotationClass = $this->filter->firstAnnotation($doc); 25 $class = new ReflectionClass($annotationClass); 26 27 return $class->newInstance(); 28 } 29 }
  • 38.
  • 39.
    3 namespace EasyTaxiValidation; 4 5use EasyTaxiAnnotation; 6 7 class Validator 8 { 9 private $annotationFactory = null; 10 11 public function __construct(AnnotationFactory $factory) { 12 $this->annotationFactory = $factory; 13 } 14 15 public function annotations($object) { 16 $annotation = $this->annotationFactory; 17 $class = new ReflectionObject($object); 18 $properties = $class->getProperties(); 19 foreach ($properties as $property) { 20 $propertyName = $property->getName(); 21 $rule = $annotation->createFromProperty($object, $propertyName); 22 if ($rule->isValid($object)) { 23 continue; 24 } 25 26 throw new Exception("$propertyName is not valid."); 27 } 28 } 29 }
  • 40.
    1 <?php 2 3 useEasyTaxiAnnotation; 4 use EasyTaxiValidation; 5 6 $filter = new AnnotationFilter(); 7 $factory = new AnnotationFactory($filter); 8 $validator = new ValidationValidator($factory); 9 $fusca = new Car('AAA-1111'); 10 11 $validator->annotation($fusca);
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 49.
  • 50.
  • 51.
  • 52.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
    1 <?php 2 3 useRespectValidationValidator as v; 4 5 v::stringType() 6 ->exactLength(8) 7 ->contains("-") 8 ->contains("/^[A-Z]{3}/") 9 ->contains("/[0-9]{4}$/") 10 ->assert($something);
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 68.
  • 69.
  • 70.
  • 71.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.