GetFromInlineValidatorBase.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. <?php
  2. namespace Knuckles\Scribe\Extracting\Strategies;
  3. use Knuckles\Camel\Extraction\ExtractedEndpointData;
  4. use Knuckles\Scribe\Extracting\MethodAstParser;
  5. use Knuckles\Scribe\Extracting\ParsesValidationRules;
  6. use PhpParser\Node;
  7. use PhpParser\Node\Stmt\ClassMethod;
  8. class GetFromInlineValidatorBase extends Strategy
  9. {
  10. use ParsesValidationRules;
  11. public function __invoke(ExtractedEndpointData $endpointData, array $routeRules): ?array
  12. {
  13. if (!$endpointData->method instanceof \ReflectionMethod) {
  14. return [];
  15. }
  16. $methodAst = MethodAstParser::getMethodAst($endpointData->method);
  17. [$validationRules, $customParameterData] = $this->lookForInlineValidationRules($methodAst);
  18. $bodyParametersFromValidationRules = $this->getParametersFromValidationRules($validationRules, $customParameterData);
  19. return $this->normaliseArrayAndObjectParameters($bodyParametersFromValidationRules);
  20. }
  21. public function lookForInlineValidationRules(ClassMethod $methodAst): array
  22. {
  23. // Validation usually happens early on, so let's assume it's in the first 6 statements
  24. $statements = array_slice($methodAst->stmts, 0, 6);
  25. $validationRules = null;
  26. $validationAssignmentExpression = null;
  27. $index = null;
  28. foreach ($statements as $index => $node) {
  29. // Filter to only assignment expressions
  30. if (!($node instanceof Node\Stmt\Expression) || !($node->expr instanceof Node\Expr\Assign)) {
  31. continue;
  32. }
  33. $validationAssignmentExpression = $node->expr;
  34. $rvalue = $validationAssignmentExpression->expr;
  35. // Look for $validated = $request->validate(...)
  36. if (
  37. $rvalue instanceof Node\Expr\MethodCall && $rvalue->var instanceof Node\Expr\Variable
  38. && in_array($rvalue->var->name, ["request", "req"]) && $rvalue->name->name == "validate"
  39. ) {
  40. $validationRules = $rvalue->args[0]->value;
  41. break;
  42. } else if (
  43. // Try $validator = Validator::make(...)
  44. $rvalue instanceof Node\Expr\StaticCall && $rvalue->class->parts && end($rvalue->class->parts) == "Validator"
  45. && $rvalue->name->name == "make"
  46. ) {
  47. $validationRules = $rvalue->args[1]->value;
  48. break;
  49. }
  50. }
  51. if ($validationAssignmentExpression && !$this->isAssignmentMeantForThisStrategy($validationAssignmentExpression)) {
  52. return [[], []];
  53. }
  54. // If validation rules were saved in a variable (like $rules),
  55. // find the var and expand the value
  56. if ($validationRules instanceof Node\Expr\Variable) {
  57. foreach (array_reverse(array_slice($statements, 0, $index)) as $earlierStatement) {
  58. if (
  59. $earlierStatement instanceof Node\Stmt\Expression
  60. && $earlierStatement->expr instanceof Node\Expr\Assign
  61. && $earlierStatement->expr->var instanceof Node\Expr\Variable
  62. && $earlierStatement->expr->var->name == $validationRules->name
  63. ) {
  64. $validationRules = $earlierStatement->expr->expr;
  65. break;
  66. }
  67. }
  68. }
  69. if (!$validationRules instanceof Node\Expr\Array_) {
  70. return [[], []];
  71. }
  72. $rules = [];
  73. $customParameterData = [];
  74. foreach ($validationRules->items as $item) {
  75. /** @var Node\Expr\ArrayItem $item */
  76. if (!$item->key instanceof Node\Scalar\String_) {
  77. continue;
  78. }
  79. $paramName = $item->key->value;
  80. // Might be an expression or concatenated string, etc.
  81. // For now, let's focus on simple strings and arrays of strings
  82. if ($item->value instanceof Node\Scalar\String_) {
  83. $rules[$paramName] = $item->value->value;
  84. } else if ($item->value instanceof Node\Expr\Array_) {
  85. $rulesList = [];
  86. foreach ($item->value->items as $arrayItem) {
  87. /** @var Node\Expr\ArrayItem $arrayItem */
  88. if ($arrayItem->value instanceof Node\Scalar\String_) {
  89. $rulesList[] = $arrayItem->value->value;
  90. }
  91. }
  92. $rules[$paramName] = join('|', $rulesList);
  93. } else {
  94. $rules[$paramName] = [];
  95. continue;
  96. }
  97. $description = $example = null;
  98. $comments = join("\n", array_map(
  99. fn($comment) => ltrim(ltrim($comment->getReformattedText(), "/")),
  100. $item->getComments()
  101. )
  102. );
  103. if ($comments) {
  104. $description = trim(str_replace(['No-example.', 'No-example'], '', $comments));
  105. $example = null;
  106. if (preg_match('/(.*\s+|^)Example:\s*([\s\S]+)\s*/m', $description, $matches)) {
  107. $description = trim($matches[1]);
  108. $example = $matches[2];
  109. }
  110. }
  111. $customParameterData[$paramName] = compact('description', 'example');
  112. }
  113. return [$rules, $customParameterData];
  114. }
  115. protected function getMissingCustomDataMessage($parameterName)
  116. {
  117. return "No extra data found for parameter '$parameterName' from your inline validator. You can add a comment above '$parameterName' with a description and example.";
  118. }
  119. protected function shouldCastUserExample()
  120. {
  121. return true;
  122. }
  123. protected function isAssignmentMeantForThisStrategy(Node\Expr\Assign $validationAssignmentExpression): bool
  124. {
  125. return true;
  126. }
  127. }