knuckleswtf / knuckleswtf/scribe
array request not render and not examplae
- 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

### 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
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