knuckleswtf / knuckleswtf/scribe

array request not render and not examplae

Open
#853 3 comments 0 reactions 0 assignees View on GitHub
awaiting requester action bug
Dominant language
PHP
Stars
2.3k
Forks
357
PR merge metrics
No merged PRs in 30d

Description

### Scribe version

4.35.0

### PHP version

8.2

### Framework

Laravel

### Framework version

v10.48.10

### Scribe config

```ruby
php artisan scribe:config:diff
laravel.middleware.0 => ""
try_it_out.use_csrf => true
auth.enabled => true
auth.default => true
```

### What happened?

# File request

```php
namespace Modules\Plan\app\Http\Requests;

use App\Models\PosUpdate;
use App\Pos;
use App\Support\Enum\ActionCode;
use Carbon\Carbon;
use Illuminate\Foundation\Http\FormRequest;
use Modules\Plan\app\Models\Plan;
use Modules\Plan\app\Models\PlanResult;
use Modules\Plan\app\Services\PlanConfigurationService;

class CheckinRequest extends FormRequest
{

protected $bodyParams = array();

/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
$configService = app(PlanConfigurationService::class);
$config = $configService->getCheckinConfiguration();
$posChecklists = $configService->getChecklistsConfiguration();

$rules = [
'plan_id' => 'sometimes|nullable|exists:plans,id',
'posCode' => 'required|exists:pos,posCode',
];
if (!empty($posChecklists)) {
$checklistCount = count($posChecklists);

$rules['checklist'] = ['required', 'array', 'min:'.$checklistCount, 'max:'.$checklistCount];

$checklistRules = [
'*.id' => 'required|integer|exists:pos_checklists,id',
'*.status' => 'required|boolean',
'*.note' => 'nullable|string',
'*.photo' => 'nullable|file|mimetypes:image/jpeg,image/jpg,image/png,image/webp|max:500',
];

$rules['checklist.*'] = $checklistRules;

foreach ($posChecklists as $item) {

$this->bodyParams['checklist'][] = [
'id' => [
'description' => 'ID of the checklist item',
'example' => $item->id
],
'status' => [
'description' => "Status of the checklist item {$item->name}",
'example' => true
],
'note' => [
'description' => 'Note for the checklist item',
'example' => 'This is a sample note'
],
'photo' => [
'description' => 'Optional photo for the checklist item',
'example' => new \Illuminate\Http\UploadedFile(public_path('assets/media/demo/checkin/img-1.png'), 'sample.jpg')
],
];
}
}

foreach ($config as $item) {
$this->addConfigRules($rules, $item);
}

return $rules;
}

protected function prepareForValidation()
{
$checklistData = $this->input('checklist', []);
foreach ($checklistData as $id => &$data) {
if (isset($data['status'])) {
$data['status'] = filter_var($data['status'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
}
}

$this->merge([
'checklist' => $checklistData,
]);
}

public function withValidator($validator)
{

$validator->after(function ($validator) {
if (!$validator->errors()->isEmpty()) {
return $validator;
}

$this->handleChecklistsValidation($validator);
$this->handlePostValidation($validator);

});
}

protected function handleChecklistsValidation($validator){
$configService = app(PlanConfigurationService::class);

$requiredChecklistIds = $configService->getChecklistsConfiguration()->pluck('id')->sort()->values()->all();

$submittedChecklistIds = collect($this->input('checklist', []))
->pluck('id')
->sort()
->values()
->all();

if ($requiredChecklistIds !== $submittedChecklistIds) {
$validator->errors()->add('checklist', 'Tất cả các mục trong danh sách kiểm tra phải được bao gồm và khớp với ID được yêu cầu.');
}
}
protected function handlePostValidation($validator)
{
$mergeData = [];
$planOrPos = $this->planOrPosExists();
$pendingUpdate = $this->checkForPendingUpdates($validator);

if ($pendingUpdate) {
$mergeData['pendingUpdate'] = $pendingUpdate;
}

if (!$this->input('plan_id')) {
$this->handleNoPlanOrPos($validator, $mergeData);
$lastAction = $mergeData['lastAction'];

} elseif (!$planOrPos) {
$validator->errors()->add('error', 'Không tìm thấy kế hoạch hoặc điểm bán phù hợp.');
return;
} else {
$mergeData['plan'] = $planOrPos;
$mergeData['pos'] = $planOrPos->pos()->where('posCode', $this->posCode)->first();
$lastAction = $mergeData['pos']->visits()->latest()->first();
}
if ($lastAction && $lastAction->action_code == ActionCode::ACTION_CHECK_IN) {
$validator->errors()->add('action_code', 'Check-out is required before check-in.');
}

$this->merge($mergeData);
}

protected function handleNoPlanOrPos($validator, &$mergeData)
{
$pos = Pos::where('posCode', $this->input('posCode'))->first();
if (!$pos) {
$validator->errors()->add('posCode', 'No valid POS found with provided POS Code.');
return; // Exit early if no valid POS is found to prevent further processing
}
$lastAction = $pos->visits()->latest()->first();

$mergeData['pos'] = $pos;
$mergeData['lastAction'] = $lastAction; // Store last action to use in post validation checks
}

private function planOrPosExists()
{
$posCode = $this->input('posCode');

return Plan::with('pos')
->where('month', Carbon::now()->format('Y-m'))
->where('status', Plan::STATUS_PLAN_APPROVED)
->whereHas('pos', function ($query) use ($posCode) {
$query->where('posCode', $posCode);
})
->find($this->input('plan_id'));
}

private function checkForPendingUpdates($validator)
{
$posCode = $this->input('posCode');
$pendingUpdate = PosUpdate::where('pos_id', $posCode)
->where('status', PosUpdate::STATUS_PENDING)
->first();

if ($pendingUpdate) {
$existingCheckin = PlanResult::where('posCode', $posCode)
->where('pos_update_id', $pendingUpdate->id)
->first();
if ($existingCheckin) {
$validator->errors()->add('pos_update', 'Check-in không được phép do tồn tại một đề xuất đang chờ duyệt.');
}
}

return $pendingUpdate;
}

protected function addConfigRules(&$rules, $item)
{
switch ($item['type']) {
case 'file':
$this->handleFileRules($rules, $item);
break;
case 'coordinates':
$rules['lat'] = 'required|numeric';
$rules['lon'] = 'required|numeric';
break;
}
}

protected function handleFileRules(&$rules, $item)
{
$baseKey = $item['key'];
$rules[$baseKey] = [
'required',
'array',
'max:' . $item['max_photos'],
];

$rules[$baseKey . '.*'] = [
'file',
'mimetypes:' . $item['rules']['mimetypes'],
'max:' . $item['rules']['max'],
'dimensions:max_width=' . $item['rules']['max_width'],
];

$this->bodyParams[$baseKey] = [
'description' => "{$item['name']} - {$item['description']}",
"required" => true,
'example' => new \Illuminate\Http\UploadedFile(public_path('assets/media/demo/checkin/img-1.png'), 'sample.jpg')
];
}

public function bodyParameters()
{
return array_merge([
'plan_id' => [
'description' => 'ID kế hoạch',
'example' => 1
],
'posCode' => [
'description' => 'Mã điểm bán',
'example' => 124000010436
],
// 'checklist' => [
// 'description' => 'Điểm bán sẵn sàng để bán hàng?. [Get list](/docs#pos-GETapi-v1-pos-pos-readiness-checklists)',
// ],
], $this->bodyParams);
}

}

```

php artisan scribe:generate

![image](https://github.com/knuckleswtf/scribe/assets/86423592/d2e50981-ac6d-40ff-b6e7-601167ae4c24)

### Docs

- [X] I've checked [the docs](https://scribe.knuckles.wtf/laravel), the [troubleshooting guide](https://scribe.knuckles.wtf/laravel/troubleshooting), and [existing issues](https://github.com/knuckleswtf/scribe/issues?q=is%3Aissue+), but I didn't find a solution

Contributor guide

Open the contributing guide

Research direction

The report centers on the CheckinRequest bodyParameters() method and its dynamically built array fields; start by running php artisan scribe:generate with the shown request. Trace how Scribe processes the checklist and file-array parameters, then verify that the generated documentation renders their examples and request structure correctly.

Written by the indexing model from the issue text.

Assessment

Tech stack
laravel, php
Domain
api, documentation
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.