Skip to content

Commit 42dd2e7

Browse files
committed
add docs on how to create an uploader
1 parent 6a89dc9 commit 42dd2e7

File tree

1 file changed

+126
-1
lines changed

1 file changed

+126
-1
lines changed

7.x-dev/crud-uploaders.md

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,132 @@ We've already created Uploaders for the most common scenarios:
7070

7171
Do you want to create your own Uploader class, for your custom field? Here's how you can do that, and how Uploader classes work behind the scenes.
7272

73-
// TODO
73+
First thing you need to decide if you need "Ajax" or "Non-Ajax" upload. The big difference is that the "non-ajax" uploaders process the file upload when you submit your form, while the ajax upload process the file using a javascript ajax request, so it can be uploaded before you submit the form.
74+
75+
First let's see how to create a non-ajax uploader, for that we will create a `CustomUploader` class that extends the abstract class `Uploader`.
76+
77+
```php
78+
namespace App\Uploaders\CustomUploader;
79+
80+
use Backpack\CRUD\app\Library\Uploaders\Uploader;
81+
82+
class CustomUploader extends Uploader
83+
{
84+
// the function we need to implement
85+
public function uploadFiles(Model $entry, $values)
86+
{
87+
// $entry is the model instance we are working with
88+
// $values is the sent files from request.
89+
90+
// do your upload logic here
91+
92+
return $valueToBeStoredInTheDatabaseEntry;
93+
}
94+
95+
// in case you want to use your uploader inside repeatable fields
96+
protected function uploadRepeatableFiles($values, $previousValues, $entry = null)
97+
{
98+
}
99+
}
100+
```
101+
102+
You can now use this uploader in your field definition:
103+
104+
```php
105+
CRUD::field('avatar')->type('upload')->withFiles([
106+
'uploader' => \App\Uploaders\CustomUploader::class,
107+
]);
108+
```
109+
110+
But most likely, you have a `custom_upload` field that you'd like to use this uploader without having to specify it every time. You can do that by adding it to the `UploadersRepository` in your Service Provider `boot()` method:
111+
112+
```php
113+
// in your App\Providers\AppServiceProvider.php
114+
115+
protected function boot()
116+
{
117+
app('UploadersRepository')->addUploaderClasses(['custom_upload' => \App\Uploaders\CustomUploader::class], 'withFiles');
118+
}
119+
```
120+
121+
You can now use `CRUD::field('avatar')->type('custom_upload')->withFiles();` and it will use your custom uploader. What happen behind the scenes is that Backpack will register your uploader to be ran in 3 different model events: `saving`, `retrieved` and `deleting`.
122+
123+
The `Uploader` class has 3 "entry points" for the mentioned events: **`storeUploadedFiles()`**, **`retrieveUploadedFiles()`** and **`deleteUploadedFiles()`**. You can overwrite these methods in your custom uploader to add your custom logic but for most uploaders you will not need to overwrite them as they are "setup" methods for the action that will be performed, and after setup they call the relevant methods that each uploader will implement, like ```uploadFiles()``` or ```uploadRepeatableFiles()```.
124+
125+
The base uploader class has most of the functionality implemented and use **"strategy methods"** to configure the underlying behavior.
126+
127+
**`shouldUploadFiles`** - a method that returns a boolean to determine if the files should be uploaded. By default it returns true, but you can overwrite it to add your custom logic.
128+
129+
**`shouldKeepPreviousValuesUnchanged`** - a method that returns a boolean to determine if the previous values should be kept unchanged and not perform the upload.
130+
131+
**`hasDeletedFiles`** - a method that returns a boolean to determine if the files were deleted from the field.
132+
133+
This is the implementation of those methods in `SingleFile` uploader:
134+
```php
135+
protected function shouldKeepPreviousValueUnchanged(Model $entry, $entryValue): bool
136+
{
137+
// if a string is sent as the value, it means the file was not changed so we should keep
138+
// previous value unchanged
139+
return is_string($entryValue);
140+
}
141+
142+
protected function hasDeletedFiles($entryValue): bool
143+
{
144+
// if the value is null, it means the file was deleted from the field
145+
return $entryValue === null;
146+
}
147+
148+
protected function shouldUploadFiles($value): bool
149+
{
150+
// when the value is an instance of UploadedFile, it means the file was uploaded and we should upload it
151+
return is_a($value, 'Illuminate\Http\UploadedFile', true);
152+
}
153+
```
154+
155+
For the ajax uploaders, the process is similar, but the `CustomUploader` class should extend `BackpackAjaxUploader` **(requires backpack/pro)** instead of `Uploader`.
156+
157+
```php
158+
159+
namespace App\Uploaders\CustomUploader;
160+
161+
use Backpack\Pro\Uploaders\BackpackAjaxUploader;
162+
163+
class CustomUploader extends BackpackAjaxUploader
164+
{
165+
// this is called on `saving` event of the main entry, at this point you already performed the upload
166+
// of the files in the ajax endpoint. By default they are in a temp folder, so here is the place
167+
// where you should move them to the final disk and path and setup what will be saved in the database.
168+
public function uploadFiles(Model $entry, $values)
169+
{
170+
return $valueToBeStoredInTheDatabaseEntry;
171+
}
172+
173+
// in case you want to use your uploader inside repeatable fields
174+
protected function uploadRepeatableFiles($values, $previousValues, $entry = null)
175+
{
176+
}
177+
}
178+
```
179+
180+
The process to register the uploader in the `UploadersRepositoy` is the same as the non-ajax uploader. `app('UploadersRepository')->addUploaderClasses(['custom_upload' => \App\Uploaders\CustomUploader::class], 'withFiles');` in the boot method of your provider.
181+
182+
In addition to the field configuration, ajax uploaders require that you use the `AjaxUploadOperation` trait in your controller. The operation is responsible to register the ajax route where your files will be sent and the upload process will be handled and the delete route from where you can delete **temporary files**.
183+
184+
Similar to model events, there are two "setup" methods for those endpoints: **`processAjaxEndpointUploads()`** and **`deleteAjaxEndpointUpload()`**. You can overwrite them to add your custom logic but most of the time you will not need to do that and just implement the `uploadFiles()` and `uploadRepeatableFiles()` methods.
185+
186+
The ajax uploader also has the same "strategy methods" as the non-ajax uploader (see above), but adds a few more:
187+
**`ajaxEndpointSuccessResponse($files = null)`** - this should return a `JsonResponse` with the needed information when the upload is successful. By default it returns a json response with the file path.
188+
189+
**`ajaxEndpointErrorResponse($message)`** - use this method to change the endpoint response in case the upload failed. Similar to the success it should return a `JsonResponse`.
190+
191+
**`getAjaxEndpointDisk()`** - by default a `temporaryDisk` is used to store the files before they are moved to the final disk. (when uploadFiles() is called). You can overwrite this method to change the disk used.
192+
193+
**`getAjaxEndpointPath()`** - by default the path is `/temp` but you can overwrite this method to change the path used.
194+
195+
**`getDefaultAjaxEndpointValidation()`** - Should return the default validation rules (in the format of `BackpackCustomRule`) for the ajax endpoint. By default it returns a `ValidGenericAjaxEndpoint` rule.
196+
197+
198+
For any other customization you would like to perform, please check the source code of the `Uploader` and `BackpackAjaxUploader` classes.
74199

75200
<a name="faq-uploaders"></a>
76201
## FAQ about Uploaders

0 commit comments

Comments
 (0)