diff --git a/src/Extracting/ParamHelpers.php b/src/Extracting/ParamHelpers.php index 55467f28..9256291c 100644 --- a/src/Extracting/ParamHelpers.php +++ b/src/Extracting/ParamHelpers.php @@ -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; @@ -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')); } diff --git a/src/Extracting/Strategies/BodyParameters/GetFromFormRequest.php b/src/Extracting/Strategies/BodyParameters/GetFromFormRequest.php index 778dab0e..cfe89f07 100644 --- a/src/Extracting/Strategies/BodyParameters/GetFromFormRequest.php +++ b/src/Extracting/Strategies/BodyParameters/GetFromFormRequest.php @@ -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'; @@ -210,6 +211,13 @@ protected function normaliseRules(array $rules) })->toArray(); } + + public function changeDescription(&$parameterData, $concat) + { + + $parameterData['description'] .= (!$parameterData['description'] ? '':'
').$concat; + } + protected function parseRule($rule, &$parameterData) { $parsedRule = $this->parseStringRuleIntoRuleAndArguments($rule); @@ -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'); }; @@ -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 Africa/Accra. "; + $this->changeDescription($parameterData,"The value must be a valid time zone, such as Africa/Accra. "); $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; }; @@ -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()); }; @@ -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'); @@ -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); }; diff --git a/src/Extracting/Strategies/Responses/ResponseCalls.php b/src/Extracting/Strategies/Responses/ResponseCalls.php index db5d4665..eb495d4c 100644 --- a/src/Extracting/Strategies/Responses/ResponseCalls.php +++ b/src/Extracting/Strategies/Responses/ResponseCalls.php @@ -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. @@ -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'] : []; @@ -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); diff --git a/src/Extracting/Strategies/Responses/UseApiResourceTags.php b/src/Extracting/Strategies/Responses/UseApiResourceTags.php index d7658507..6c305cef 100644 --- a/src/Extracting/Strategies/Responses/UseApiResourceTags.php +++ b/src/Extracting/Strategies/Responses/UseApiResourceTags.php @@ -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) { @@ -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) { @@ -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) @@ -113,6 +115,7 @@ public function getApiResourceResponse(array $tags) /** @var Response $response */ $response = $resource->toResponse(app(Request::class)); + $this->endDbTransaction(); return [ [ 'status' => $statusCode ?: 200, @@ -143,13 +146,18 @@ 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']) : []; } @@ -157,23 +165,26 @@ private function getClassToBeTransformedAndAttributes(array $tags): array 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, '\\'); @@ -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` @@ -207,7 +223,7 @@ protected function instantiateApiResourceModel(string $type, array $factoryState } } } finally { - $this->endDbTransaction(); +// $this->endDbTransaction(); } return $instance;