Skip to content

Commit 6d5cc6d

Browse files
committed
fix: adding support for storeAs from the request
1 parent aaa9597 commit 6d5cc6d

File tree

2 files changed

+177
-2
lines changed

2 files changed

+177
-2
lines changed

docs-v3/content/docs/mcp/fields.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,138 @@ class PostRepository extends Repository
344344
}
345345
```
346346

347+
## File Field with Custom Filenames
348+
349+
The File field supports custom filenames from request data, perfect for automation workflows like n8n where you want to control the filename during upload.
350+
351+
### Basic File Upload
352+
353+
```php
354+
class ExpenseRepository extends Repository
355+
{
356+
public function fields(RestifyRequest $request): array
357+
{
358+
return [
359+
field('receipt_path')->file()
360+
->path('expense_receipts/'.Auth::id())
361+
->storeOriginalName('receipt_filename')
362+
->storeSize('receipt_size')
363+
->deletable()
364+
->disk('s3'),
365+
366+
field('receipt_filename')
367+
->description('Original filename of the uploaded receipt.'),
368+
369+
field('receipt_size')
370+
->description('Size of the uploaded receipt in bytes.'),
371+
];
372+
}
373+
}
374+
```
375+
376+
### Custom Filename from Request
377+
378+
Use `storeAs()` with a callback to read the custom filename from the request:
379+
380+
```php
381+
field('receipt_path')->file()
382+
->path('expense_receipts/'.Auth::id())
383+
->storeAs(fn($request) => $request->input('receipt_filename'))
384+
->storeOriginalName('receipt_filename')
385+
->storeSize('receipt_size')
386+
->deletable()
387+
->disk('s3'),
388+
389+
field('receipt_filename')
390+
->description('Custom filename for the receipt. Provide a meaningful name.'),
391+
```
392+
393+
### Smart Extension Handling
394+
395+
The File field automatically handles file extensions:
396+
397+
```php
398+
// Request: receipt_filename = "Invoice_Jan_2024"
399+
// Uploaded file: expense.pdf
400+
// Result: Invoice_Jan_2024.pdf (extension auto-appended)
401+
402+
// Request: receipt_filename = "Invoice_Jan_2024.pdf"
403+
// Uploaded file: expense.pdf
404+
// Result: Invoice_Jan_2024.pdf (used as-is)
405+
406+
// Request: receipt_filename = "" or null
407+
// Uploaded file: expense.pdf
408+
// Result: a1b2c3d4e5f6.pdf (fallback to auto-generated hash)
409+
```
410+
411+
### File Field Behaviors
412+
413+
**With Callable `storeAs()`:**
414+
```php
415+
->storeAs(fn($request) => $request->input('custom_name'))
416+
```
417+
- Uses the returned filename for storage
418+
- Uses the same filename for `storeOriginalName()` column
419+
- Auto-appends extension if missing
420+
- Falls back to auto-generated name if returns empty/null
421+
422+
**With Static `storeAs()`:**
423+
```php
424+
->storeAs('avatar.jpg')
425+
```
426+
- Uses the static filename for storage
427+
- Uses uploaded file's original name for `storeOriginalName()` column
428+
- Extension must be included in the static string
429+
430+
**Without `storeAs()`:**
431+
```php
432+
field('receipt')->file()
433+
```
434+
- Auto-generates hash-based filename
435+
- Uses uploaded file's original name for `storeOriginalName()` column
436+
437+
### MCP-Optimized File Fields
438+
439+
Hide file fields from MCP responses to reduce token usage:
440+
441+
```php
442+
class ExpenseRepository extends Repository
443+
{
444+
public function fields(RestifyRequest $request): array
445+
{
446+
return [
447+
field('receipt_path')->file()
448+
->path('expense_receipts/'.Auth::id())
449+
->storeAs(fn($request) => $request->input('receipt_filename'))
450+
->storeOriginalName('receipt_filename')
451+
->resolveUsingTemporaryUrl($request->boolean('temporary_urls'))
452+
->hideFromMcp()
453+
->description('Only send the absolute URL if you have it, otherwise do not send this field.')
454+
->disk('s3'),
455+
456+
field('receipt_filename')
457+
->description('Filename of the receipt. Keep it descriptive.'),
458+
];
459+
}
460+
}
461+
```
462+
463+
### File Upload Automation Example
464+
465+
Perfect for n8n workflows where files are extracted from emails:
466+
467+
```json
468+
{
469+
"receipt_path": "<uploaded_file>",
470+
"receipt_filename": "Invoice_ABC_Company_Jan_2024",
471+
"amount": 1500.00,
472+
"date": "2024-01-15",
473+
"vendor": "ABC Company"
474+
}
475+
```
476+
477+
The file will be stored as `expense_receipts/123/Invoice_ABC_Company_Jan_2024.pdf` and the `receipt_filename` column will contain `Invoice_ABC_Company_Jan_2024.pdf`.
478+
347479
## Best Practices
348480

349481
### 1. Field Selection Strategy
@@ -358,6 +490,7 @@ class PostRepository extends Repository
358490
- Inline simple relationship data instead of separate API calls
359491
- Use computed fields to provide aggregated information
360492
- Avoid deeply nested relationship structures
493+
- Hide file fields from MCP using `hideFromMcp()` to save tokens
361494

362495
### 3. Security Considerations
363496

src/Fields/File.php

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@ class File extends Field implements DeletableContract, StorableContract
4343
*/
4444
public $sizeColumn;
4545

46+
/**
47+
* The custom filename resolved from storeAs callback.
48+
*
49+
* @var string|null
50+
*/
51+
protected $customFilename;
52+
53+
/**
54+
* Whether to use the custom filename for the original name column.
55+
*
56+
* @var bool
57+
*/
58+
protected $useCustomFilenameForOriginal = false;
59+
4660
/**
4761
* The callback that should be executed to store the file.
4862
*
@@ -188,12 +202,38 @@ protected function storeFile(Request $request, string $requestAttribute)
188202
}
189203

190204
if (! $this->storeAs) {
205+
$this->customFilename = null;
206+
$this->useCustomFilenameForOriginal = false;
207+
191208
return $file->store($this->getStorageDir(), $this->getStorageDisk());
192209
}
193210

211+
$isCallable = is_callable($this->storeAs);
212+
$filename = $isCallable
213+
? call_user_func($this->storeAs, $request)
214+
: $this->storeAs;
215+
216+
// If storeAs returns empty/null, fallback to auto-generated name
217+
if (empty($filename)) {
218+
$this->customFilename = null;
219+
$this->useCustomFilenameForOriginal = false;
220+
221+
return $file->store($this->getStorageDir(), $this->getStorageDisk());
222+
}
223+
224+
// Smart extension handling - append if missing
225+
$extension = $file->getClientOriginalExtension();
226+
if ($extension && ! str_ends_with(strtolower($filename), '.'.$extension)) {
227+
$filename = $filename.'.'.$extension;
228+
}
229+
230+
$this->customFilename = $filename;
231+
// Only use custom filename for original name if it came from a callable
232+
$this->useCustomFilenameForOriginal = $isCallable;
233+
194234
return $file->storeAs(
195235
$this->getStorageDir(),
196-
is_callable($this->storeAs) ? call_user_func($this->storeAs, $request) : $this->storeAs,
236+
$filename,
197237
$this->getStorageDisk()
198238
);
199239
}
@@ -223,7 +263,9 @@ protected function mergeExtraStorageColumns($request, array $attributes): array
223263
$file = $this->resolveFileFromRequest($request);
224264

225265
if ($this->originalNameColumn) {
226-
$attributes[$this->originalNameColumn] = $file->getClientOriginalName();
266+
$attributes[$this->originalNameColumn] = ($this->useCustomFilenameForOriginal && $this->customFilename)
267+
? $this->customFilename
268+
: $file->getClientOriginalName();
227269
}
228270

229271
if ($this->sizeColumn) {

0 commit comments

Comments
 (0)