Skip to content

Commit

Permalink
Melhorias na captura de parametros usando formrequest e automatização…
Browse files Browse the repository at this point in the history
… de respostas.
  • Loading branch information
leandrodiogenes committed Jun 3, 2020
1 parent 85c9fbc commit acc0c22
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 33 deletions.
10 changes: 9 additions & 1 deletion src/Extracting/ParamHelpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
namespace Knuckles\Scribe\Extracting;

use Faker\Factory;
use Faker\Provider\pt_BR\Address;
use Faker\Provider\pt_BR\Payment;
use Faker\Provider\pt_BR\PhoneNumber;
use Illuminate\Http\UploadedFile;
use JansenFelipe\FakerBR\FakerBR;
use stdClass;
use Knuckles\Scribe\Tools\WritingUtils as w;

Expand All @@ -12,7 +16,11 @@ trait ParamHelpers

protected function getFaker()
{
$faker = Factory::create();
$faker = Factory::create('pt_BR');
$faker->addProvider(new FakerBR($faker));
$faker->addProvider(new PhoneNumber($faker));
$faker->addProvider(new Address($faker));
$faker->addProvider(new Payment($faker));
if ($this->config->get('faker_seed')) {
$faker->seed($this->config->get('faker_seed'));
}
Expand Down
67 changes: 53 additions & 14 deletions src/Extracting/Strategies/BodyParameters/GetFromFormRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ public function getBodyParametersFromValidationRules(array $validationRules, arr
// Let's have our sentences end with full stops, like civilized people.🙂
$parameterData['description'] = $fullDescription ? rtrim($fullDescription, '.') . '.' : $fullDescription;

$parameterData['description'] = str_replace(':attribute','',$parameterData['description']);
// Set default values for type
if (is_null($parameterData['type'])) {
$parameterData['type'] = 'string';
Expand Down Expand Up @@ -210,6 +211,13 @@ protected function normaliseRules(array $rules)
})->toArray();
}


public function changeDescription(&$parameterData, $concat)
{

$parameterData['description'] .= (!$parameterData['description'] ? '':'<br>').$concat;
}

protected function parseRule($rule, &$parameterData)
{
$parsedRule = $this->parseStringRuleIntoRuleAndArguments($rule);
Expand Down Expand Up @@ -257,13 +265,19 @@ protected function parseRule($rule, &$parameterData)
break;
case 'array':
$parameterData['setter'] = function () {
return [$this->generateDummyValue('string')];
return [];
};
$parameterData['type'] = $rule;
break;
case 'uuid':
$parameterData['setter'] = function () {
return $this->generateDummyValue('uuid');
};
$parameterData['type'] = $rule;
break;
case 'file':
$parameterData['type'] = 'file';
$parameterData['description'] .= 'O valor precisa ser um arquivo';
$this->changeDescription($parameterData,'O valor precisa ser um arquivo') ;
$parameterData['setter'] = function () {
return $this->generateDummyValue('file');
};
Expand All @@ -273,14 +287,13 @@ protected function parseRule($rule, &$parameterData)
* Special string types
*/
case 'timezone':
// Laravel's message merely says "The value must be a valid zone"
$parameterData['description'] .= "The value must be a valid time zone, such as <code>Africa/Accra</code>. ";
$this->changeDescription($parameterData,"The value must be a valid time zone, such as <code>Africa/Accra</code>. ");
$parameterData['setter'] = function () {
return $this->getFaker()->timezone;
};
break;
case 'email':
$parameterData['description'] .= d::getDescription($rule) . ' ';
$this->changeDescription($parameterData,d::getDescription($rule) . ' ');
$parameterData['setter'] = function () {
return $this->getFaker()->safeEmail;
};
Expand All @@ -291,34 +304,39 @@ protected function parseRule($rule, &$parameterData)
return $this->getFaker()->url;
};
$parameterData['type'] = 'string';
// Laravel's message is "The value format is invalid". Ugh.🤮
$parameterData['description'] .= "O valor precisa ser uma URL válida";
$this->changeDescription($parameterData,$this->changeDescription($parameterData,"O valor precisa ser uma URL válida"));
break;
case 'ip':
$parameterData['description'] .= d::getDescription($rule) . ' ';
$this->changeDescription($parameterData,d::getDescription($rule) . ' ');
$parameterData['type'] = 'string';
$parameterData['setter'] = function () {
return $this->getFaker()->ipv4;
};
break;
case 'cpf':
$this->changeDescription($parameterData,d::getDescription($rule) . ' ');
$parameterData['type'] = 'string';
$parameterData['setter'] = function () {
return $this->getFaker()->cpf;
};
break;
case 'json':
$parameterData['type'] = 'string';
$parameterData['description'] .= d::getDescription($rule) . ' ';
$this->changeDescription($parameterData,d::getDescription($rule) . ' ');
$parameterData['setter'] = function () {
return json_encode([$this->getFaker()->word, $this->getFaker()->word,]);
};
break;
case 'date':
$parameterData['type'] = 'string';
$parameterData['description'] .= d::getDescription($rule) . ' ';
$this->changeDescription($parameterData,d::getDescription($rule) . ' ');
$parameterData['setter'] = function () {
return date(\DateTime::ISO8601, time());
};
break;
case 'date_format':
$parameterData['type'] = 'string';
// Laravel description here is "The value must match the format Y-m-d". Not descriptive enough.
$parameterData['description'] .= "O valor precisa ser uma data válida no formato {$arguments[0]} ";
$this->changeDescription($parameterData,"O valor precisa ser uma data válida no formato {$arguments[0]} ");
$parameterData['setter'] = function () use ($arguments) {
return date($arguments[0], time());
};
Expand All @@ -345,12 +363,33 @@ protected function parseRule($rule, &$parameterData)
$parameterData['setter'] = function () { return $this->getFaker()->numberBetween($arguments[0], $arguments[1]); };
break;*/

case 'unique':
$this->changeDescription($parameterData,'Será validado se já existe um registro com esse valor cadastrado. ');
break;
case 'min_words':
$parameterData['type'] = $parameterData['type'] ?: 'number';
$parameterData['description'] .= "*O campo precisa conter no minimo {$arguments[0]} ".plural('palavra.','palavras.',$arguments[0]).' ';
break;
case 'digits':
$parameterData['type'] = $parameterData['type'] ?: 'number';
$this->changeDescription($parameterData,d::getDescription($rule, [':digits' => $arguments[0]]).' ');
break;

case 'between':
$parameterData['type'] = $parameterData['type'] ?: 'number';
$this->changeDescription($parameterData,d::getDescription($rule, [':min' => $arguments[0], ':max' => $arguments[1]], $parameterData['type'] == 'string' ? 'string':'numeric').' ');
$parameterData['setter'] = function () use ($arguments) { return $this->getFaker()->numberBetween($arguments[0], $arguments[1]); };
break;
case 'digits_between':
$parameterData['type'] = $parameterData['type'] ?: 'number';
$this->changeDescription($parameterData,d::getDescription($rule, [':min' => $arguments[0], ':max' => $arguments[1]]).' ');
break;
/**
* Special file types.
*/
case 'image':
$parameterData['type'] = 'file';
$parameterData['description'] .= d::getDescription($rule) . ' ';
$this->changeDescription($parameterData,d::getDescription($rule) . ' ');
$parameterData['setter'] = function () {
// This is fine because the file example generator generates an image
return $this->generateDummyValue('file');
Expand All @@ -363,7 +402,7 @@ protected function parseRule($rule, &$parameterData)
case 'in':
// Not using the rule description here because it only says "The attribute is invalid"
$description = 'O valor precisa ser um dos seguintes:' . w::getListOfValuesAsFriendlyHtmlString($arguments);
$parameterData['description'] .= $description . ' ';
$this->changeDescription($parameterData,$description . ' ');
$parameterData['setter'] = function () use ($arguments) {
return Arr::random($arguments);
};
Expand Down
28 changes: 26 additions & 2 deletions src/Extracting/Strategies/Responses/ResponseCalls.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
use Knuckles\Scribe\Tools\Utils;
use ReflectionClass;
use ReflectionFunctionAbstract;
use Knuckles\Scribe\Tools\Utils as u;
use ReflectionParameter;

/**
* Make a call to the route and retrieve its response.
Expand Down Expand Up @@ -121,10 +123,33 @@ private function configureEnvironment(array $rulesToApply)
* @param array $headers
*
* @return Request
* @throws \ReflectionException
*/
protected function prepareRequest(Route $route, array $rulesToApply, array $urlParams, array $bodyParams, array $queryParams, array $fileParameters, array $headers)
{
$uri = Utils::getFullUrl($route, $urlParams);

[$controllerName, $methodName] = u::getRouteClassAndMethodNames($route);
$controller = new ReflectionClass($controllerName);
$method = u::getReflectedRouteMethod([$controllerName, $methodName]);

$parameters = $method->getParameters();

$replaceParameters = [];
foreach($parameters as $key=> $parameter){
/**@var ReflectionParameter $parameter * */
$class = $parameter->getClass();
if(!empty($class) and $class->isSubclassOf(\Illuminate\Database\Eloquent\Model::class)){
$className = $class->getName();
if(class_exists($className)) {
$class_instance = app($className);
if( $class_instance and !empty($model = $class_instance->first())){
$replaceParameters[$parameter->getName()] = $model->id;
}
}
}
}

$uri = Utils::getFullUrl($route, $urlParams,$replaceParameters);
$routeMethods = $this->getMethods($route);
$method = array_shift($routeMethods);
$cookies = isset($rulesToApply['cookies']) ? $rulesToApply['cookies'] : [];
Expand Down Expand Up @@ -323,7 +348,6 @@ protected function makeApiCall(Request $request, Route $route)
*/
protected function callLaravelOrLumenRoute(Request $request): \Symfony\Component\HttpFoundation\Response
{
ini_set('xdebug.max_nesting_level', 1200);
// Confirm we're running in Laravel, not Lumen
if (app()->bound(Kernel::class)) {
$kernel = app(Kernel::class);
Expand Down
48 changes: 32 additions & 16 deletions src/Extracting/Strategies/Responses/UseApiResourceTags.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public function __invoke(Route $route, ReflectionClass $controller, ReflectionFu
* @param Tag[] $tags
*
* @return array|null
* @throws Exception
*/
public function getApiResourceResponse(array $tags)
{
Expand All @@ -70,9 +71,10 @@ public function getApiResourceResponse(array $tags)
}

[$statusCode, $apiResourceClass] = $this->getStatusCodeAndApiResourceClass($apiResourceTag);
[$model, $factoryStates, $relations, $pagination] = $this->getClassToBeTransformedAndAttributes($tags);
$modelInstance = $this->instantiateApiResourceModel($model, $factoryStates, $relations);
[$model, $factoryStates, $relations, $pagination, $useFactory] = $this->getClassToBeTransformedAndAttributes($tags);

$this->startDbTransaction();
$modelInstance = $this->instantiateApiResourceModel($model, $factoryStates, $relations,$useFactory);
try {
$resource = new $apiResourceClass($modelInstance);
} catch (Exception $e) {
Expand All @@ -84,7 +86,7 @@ public function getApiResourceResponse(array $tags)
// Collections can either use the regular JsonResource class (via `::collection()`,
// or a ResourceCollection (via `new`)
// See https://laravel.com/docs/5.8/eloquent-resources
$models = [$modelInstance, $this->instantiateApiResourceModel($model, $factoryStates, $relations)];
$models = [$modelInstance, $this->instantiateApiResourceModel($model, $factoryStates, $relations,$useFactory)];
// Pagination can be in two forms:
// [15] : means ::paginate(15)
// [15, 'simple'] : means ::simplePaginate(15)
Expand Down Expand Up @@ -113,6 +115,7 @@ public function getApiResourceResponse(array $tags)
/** @var Response $response */
$response = $resource->toResponse(app(Request::class));

$this->endDbTransaction();
return [
[
'status' => $statusCode ?: 200,
Expand Down Expand Up @@ -143,37 +146,45 @@ private function getClassToBeTransformedAndAttributes(array $tags): array
}));

$type = null;
$useFactory = false;
$states = [];
$relations = [];
$pagination = [];
if ($modelTag) {
['content' => $type, 'attributes' => $attributes] = a::parseIntoContentAndAttributes($modelTag->getContent(), ['states', 'with', 'paginate']);
['content' => $type, 'attributes' => $attributes] = a::parseIntoContentAndAttributes($modelTag->getContent(), ['states', 'with', 'paginate','relations','useFactory']);
$states = $attributes['states'] ? explode(',', $attributes['states']) : [];
$useFactory = !!$attributes['useFactory'];
$relations = $attributes['with'] ? explode(',', $attributes['with']) : [];
if(empty($relations)){
$relations = $attributes['relations'] ? explode(',', $attributes['relations']) : [];
}
$pagination = $attributes['paginate'] ? explode(',', $attributes['paginate']) : [];
}

if (empty($type)) {
throw new Exception("Couldn't detect an Eloquent API resource model from your docblock. Did you remember to specify a model using @apiResourceModel?");
}

return [$type, $states, $relations, $pagination];
return [$type, $states, $relations, $pagination, $useFactory];
}

/**
* @param string $type
*
* @param array $relations
* @param array $factoryStates
*
* @param array $relations
* @param bool $useFactory
* @return Model|object
*/
protected function instantiateApiResourceModel(string $type, array $factoryStates = [], array $relations = [])
protected function instantiateApiResourceModel(string $type, array $factoryStates = [], array $relations = [], $useFactory=false)
{
$this->startDbTransaction();
// $this->startDbTransaction();
try {
// Try Eloquent model factory

if(!$useFactory){
throw new Exception('');
}
// Factories are usually defined without the leading \ in the class name,
// but the user might write it that way in a comment. Let's be safe.
$type = ltrim($type, '\\');
Expand All @@ -183,22 +194,27 @@ protected function instantiateApiResourceModel(string $type, array $factoryState
$factory->states($factoryStates);
}
try {
return $factory->create();
$result = $factory->create();
if(!empty($relations)){
$result->load($relations);
}
return $result;
} catch (Exception $e) {
// If there was no working database, it would fail.
return $factory->make();
}
} catch (Exception $e) {
c::debug("Eloquent model factory failed to instantiate {$type}; trying to fetch from database.");
e::dumpExceptionIfVerbose($e);
// c::debug("Eloquent model factory failed to instantiate {$type}; trying to fetch from database.");
// e::dumpExceptionIfVerbose($e);

$instance = new $type();
if ($instance instanceof \Illuminate\Database\Eloquent\Model) {
try {
// we can't use a factory but can try to get one from the database
$firstInstance = $type::with($relations)->first();
if ($firstInstance) {
return $firstInstance;
$lastInstance = $type::latest()->first();
// $lastInstance = $type::with($relations)->latest()->first();
if ($lastInstance) {
return $lastInstance;
}
} catch (Exception $e) {
// okay, we'll stick with `new`
Expand All @@ -207,7 +223,7 @@ protected function instantiateApiResourceModel(string $type, array $factoryState
}
}
} finally {
$this->endDbTransaction();
// $this->endDbTransaction();
}

return $instance;
Expand Down

0 comments on commit acc0c22

Please # to comment.