Skip to content

Commit 7d9c53c

Browse files
authored
File and Image fields. (#334)
* File and Image fields. * Apply fixes from StyleCI (#335) * wip * Ensure prunable old file will be removed on update. * Apply fixes from StyleCI (#338)
1 parent 325e9e2 commit 7d9c53c

30 files changed

+1316
-34
lines changed

docs/docs/4.0/auth/profile.md

Lines changed: 71 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,22 @@
22

33
[[toc]]
44

5-
To ensure you can get your profile, you should add the `Authenticate` middleware to your restify, this can be easily done by using the `Binaryk\LaravelRestify\Http\Middleware\RestifySanctumAuthenticate::class` into your `restify.middleware` [configuration file](../quickstart.html#configurations);
5+
To ensure you can get your profile, you should add the `Authenticate` middleware to your restify, this can be easily
6+
done by using the `Binaryk\LaravelRestify\Http\Middleware\RestifySanctumAuthenticate::class` into
7+
your `restify.middleware` [configuration file](../quickstart.html#configurations);
68

79
Laravel Restify expose the user profile via `GET: /api/restify/profile` endpoint.
810

911
## Get profile using repository
1012

11-
When retrieving the user profile, by default it is serialized using the `UserRepository` if there is once (Restify will find the repository based on the `User` model).
13+
When retrieving the user profile, by default it is serialized using the `UserRepository` if there is once (Restify will
14+
find the repository based on the `User` model).
1215

1316
```http request
1417
GET: /api/restify/profile
1518
```
1619

17-
This is what we have for a basic profile:
20+
This is what we have for a basic profile:
1821

1922
```json
2023
{
@@ -51,7 +54,8 @@ public function fields(RestifyRequest $request)
5154
}
5255
```
5356

54-
Since the profile is resolved using the UserRepository, you can benefit from the power of related entities. For example, if you want to return user roles:
57+
Since the profile is resolved using the UserRepository, you can benefit from the power of related entities. For example,
58+
if you want to return user roles:
5559

5660
```php
5761
//UserRepository
@@ -61,7 +65,8 @@ public static $related = [
6165
];
6266
```
6367

64-
And make sure the `User` model, has this method, which returns a relationship from another table, or you can simply return an array:
68+
And make sure the `User` model, has this method, which returns a relationship from another table, or you can simply
69+
return an array:
6570

6671
```php
6772
//User.php
@@ -81,6 +86,7 @@ Let's get the profile now, using the `roles` relationship:
8186
```http request
8287
GET: /api/restify/profile?related=roles
8388
```
89+
8490
The result will look like this:
8591

8692
```json
@@ -108,7 +114,8 @@ The result will look like this:
108114

109115
### Without repository
110116

111-
In some cases, you may choose to not use the repository for the profile serialization. In such cases you should add the trait `Binaryk\LaravelRestify\Repositories\UserProfile` into your `UserRepository`:
117+
In some cases, you may choose to not use the repository for the profile serialization. In such cases you should add the
118+
trait `Binaryk\LaravelRestify\Repositories\UserProfile` into your `UserRepository`:
112119

113120
```php
114121
// UserProfile
@@ -127,7 +134,7 @@ class UserRepository extends Repository
127134

128135
In this case, the profile will return the model directly:
129136

130-
:::warn Relations
137+
:::warning Relations
131138
Note that when you're not using the repository, the `?related` will do not work anymore.
132139
:::
133140

@@ -150,10 +157,10 @@ And you will get:
150157
}
151158
```
152159

153-
### Conditionally use repository
154-
155-
In rare cases you may want to utilize the repository only for non admin users for example, to ensure you serialize specific fields for the users:
160+
### Conditionally use repository
156161

162+
In rare cases you may want to utilize the repository only for non admin users for example, to ensure you serialize
163+
specific fields for the users:
157164

158165
```php
159166
use Binaryk\LaravelRestify\Fields\Field;
@@ -190,7 +197,8 @@ This way you instruct Restify to only use the repository for users who are admin
190197

191198
## Update Profile using repository
192199

193-
By default, Restify will validate, and fill only fields presented in your `UserRepository` for updating the user profile. Let's get as an example the following repository fields:
200+
By default, Restify will validate, and fill only fields presented in your `UserRepository` for updating the user
201+
profile. Let's get as an example the following repository fields:
194202

195203
```php
196204
// UserRepository
@@ -215,8 +223,9 @@ If we will try to call the `PUT` method to update the profile without data:
215223

216224
We will get back `4xx` validation:
217225

218-
:::warn Accept header
219-
If you test it via Postman (or other HTTP client), make sure you always pass the `Accept` header `application/json`. This will instruct Laravel to return you back json formatted data:
226+
:::warning
227+
Accept header If you test it via Postman (or other HTTP client), make sure you always pass the `Accept`
228+
header `application/json`. This will instruct Laravel to return you back json formatted data:
220229
:::
221230

222231
```json
@@ -229,6 +238,7 @@ If you test it via Postman (or other HTTP client), make sure you always pass the
229238
}
230239
}
231240
```
241+
232242
So we have to populate the user `name` in the payload:
233243

234244
```json
@@ -237,7 +247,7 @@ So we have to populate the user `name` in the payload:
237247
}
238248
```
239249

240-
Since the payload is valid now, Restify will update the user profile (name in our case):
250+
Since the payload is valid now, Restify will update the user profile (name in our case):
241251

242252
```json
243253
{
@@ -258,7 +268,8 @@ Since the payload is valid now, Restify will update the user profile (name in ou
258268

259269
### Update without repository
260270

261-
If you [don't use the repository](./#get-profile-using-repository) for the user profile, Restify will update only `fillable` user attributes present in the request payload: `$request->only($user->getFillable())`.
271+
If you [don't use the repository](./#get-profile-using-repository) for the user profile, Restify will update
272+
only `fillable` user attributes present in the request payload: `$request->only($user->getFillable())`.
262273

263274
```http request
264275
PUT: /api/restify/profile
@@ -272,7 +283,7 @@ Payload:
272283
}
273284
````
274285

275-
The response will be the updated user:
286+
The response will be the updated user:
276287

277288
```json
278289
{
@@ -289,7 +300,7 @@ The response will be the updated user:
289300

290301
## User avatar
291302

292-
To prepare your users for avatars, you should add the `avatar` column in your users table:
303+
To prepare your users for avatars, you can add the `avatar` column in your users table:
293304

294305
```php
295306
// Migration
@@ -301,23 +312,60 @@ public function up()
301312
}
302313
```
303314

304-
Now you can use the Restify endpoints to update the avatar:
315+
Not you should specify in the user repository that user has avatar file:
316+
317+
```php
318+
use Binaryk\LaravelRestify\Fields\Image;
319+
320+
public function fields(RestifyRequest $request)
321+
{
322+
return [
323+
Field::make('name')->rules('required'),
324+
325+
Image::make('avatar')->storeAs('avatar.jpg')
326+
];
327+
}
328+
```
329+
330+
Now you can use the Restify profile update, and give the avatar as an image.
331+
332+
:::warning Post request
333+
334+
You cannot upload file using PUT or PATCH verbs, so we should use POST request.
335+
:::
305336

306337
```http request
307-
POST: /api/restify/profile/avatar
338+
POST: /api/restify/profile
339+
```
340+
341+
The payload should be a form-data, with an image under `avatar` key:
342+
343+
```json
344+
{
345+
"avatar": "binary image in form data request"
346+
}
308347
```
309348

310-
The payload should be a form-data, with an image under `avatar` key.
349+
If you have to customize path or disk of the storage file, check the [image field](../repository-pattern/field.html#file-fields)
350+
351+
### Avatar without repository
352+
353+
If you don't use the repository for updating the user profile, Restify provides a separate endpoint for updating the avatar.
354+
355+
```http request
356+
POST: api/restify/profile/avatar
357+
```
311358

312-
The default path for storing avatar is: `/avatars/{user_key}/`.
359+
The default path for storing avatar is: `/avatars/{user_key}/`, and it uses by default the `public` disk.
313360

314361
You can modify that by modifying property in a `boot` method of any service provider:
315362

316363
```php
317-
Binaryk\LaravelRestify\Http\Requests\ProfileAvatarRequest::$path
364+
Binaryk\LaravelRestify\Http\Requests\ProfileAvatarRequest::$path = 'users';
365+
Binaryk\LaravelRestify\Http\Requests\ProfileAvatarRequest::$disk = 's3';
318366
```
319367

320-
Or if you need the request to make the path:
368+
Or if you need the request to make the path:
321369

322370
```php
323371
Binaryk\LaravelRestify\Http\Requests\ProfileAvatarRequest::usingPath(function(Illuminate\Http\Request $request) {

docs/docs/4.0/repository-pattern/field.md

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Field
22

3+
[[toc]]
4+
35
Field is basically the model attribute representation. Each Field generally extends the `Binaryk\LaravelRestify\Fields\Field` class from the Laravel Restify.
46
This class ships a variety of mutators, interceptors, validators chaining methods you can use for defining your attribute.
57

@@ -198,7 +200,158 @@ Field::new('token')->value(Str::random(32))->hidden();
198200

199201
# Variations
200202

201-
Bellow we have a list of fields used for the related resources.
203+
## File fields
204+
205+
To illustrate the behavior of Restify file upload fields, let's assume our application's users can upload "avatar photos" to their account. So, our users database table will have an `avatar` column. This column will contain the path to the profile on disk, or, when using a cloud storage provider such as Amazon S3, the profile photo's path within its "bucket".
206+
207+
### Defining the field
208+
209+
Next, let's attach the file field to our `UserRepository`. In this example, we will create the field and instruct it to store the underlying file on the `public` disk. This disk name should correspond to a disk name in your `filesystems` configuration file:
210+
211+
```php
212+
use Binaryk\LaravelRestify\Fields\File;
213+
214+
public function fields(RestifyRequest $request)
215+
{
216+
return [
217+
File::make('avatar')->disk('public')
218+
];
219+
}
220+
```
221+
222+
### How Files Are Stored
223+
224+
When a file is uploaded using this field, Restify will use Laravel's [Filesystem integration](https://laravel.com/docs/filesystem) to store the file on the disk of your choosing with a randomly generated filename. Once the file is stored, Restify will store the relative path to the file in the file field's underlying database column.
225+
226+
To illustrate the default behavior of the `File` field, let's take a look at an equivalent route that would store the file in the same way:
227+
228+
```php
229+
use Illuminate\Http\Request;
230+
231+
Route::post('/avatar', function (Request $request) {
232+
$path = $request->avatar->store('/', 'public');
233+
234+
$request->user()->update([
235+
'avatar' => $path,
236+
]);
237+
});
238+
```
239+
240+
If you are using the `public` disk with the `local` driver, you should run the `php artisan storage:link` Artisan command to create a symbolic link from `public/storage` to `storage/app/public`. To learn more about file storage in Laravel, check out the [Laravel file storage documentation](https://laravel.com/docs/filesystem).
241+
242+
### Image
243+
244+
The `Image` field behaves exactly like the `File` field; however, it will instruct Restify to only accept mimetypes of type `image/*` for it:
245+
246+
```php
247+
Image::make('avatar')->storeAs('avatar.jpg')
248+
```
249+
250+
### Storing Metadata
251+
252+
In addition to storing the path to the file within the storage system, you may also instruct Restify to store the original client filename and its size (in bytes). You may accomplish this using the `storeOriginalName` and `storeSize` methods. Each of these methods accept the name of the column you would like to store the file information:
253+
254+
```php
255+
Image::make('avatar')
256+
->storeOriginalName('avatar_original')
257+
->storeSize('avatar_size')
258+
->storeAs('avatar.jpg')
259+
```
260+
261+
The image above will store the file, with name `avatar.jpg` in the `avatar` column, the file original name into `avatar_original` column and file size in bytes under `avatar_size` column (only if these columns are fillable on your model).
262+
263+
### Pruning & Deletion
264+
265+
File fields are deletable by default, so considering the following field definition:
266+
267+
```php
268+
File::make('avatar')
269+
```
270+
You have a request to delete the avatar of the user with the id 1:
271+
272+
```http request
273+
DELETE: api/restify/users/1/field/avatar
274+
```
275+
276+
You can override this behavior by using the `deletable` method:
277+
278+
```php
279+
File::make('Photo')->disk('public')->deletable(false)
280+
```
281+
282+
So now the field will do not be deletable anymore.
283+
284+
### Customizing File Storage
285+
286+
Previously we learned that, by default, Restify stores the file using the `store` method of the `Illuminate\Http\UploadedFile` class. However, you may fully customize this behavior based on your application's needs.
287+
288+
#### Customizing The Name / Path
289+
290+
If you only need to customize the name or path of the stored file on disk, you may use the `path` and `storeAs` methods of the `File` field:
291+
292+
```php
293+
use Illuminate\Http\Request;
294+
295+
File::make('avatar')
296+
->disk('s3')
297+
->path($request->user()->id.'-attachments')
298+
->storeAs(function (Request $request) {
299+
return sha1($request->attachment->getClientOriginalName());
300+
}),
301+
```
302+
303+
#### Customizing The Entire Storage Process
304+
305+
However, if you would like to take **total** control over the file storage logic of a field, you may use the `store` method. The `store` method accepts a callable which receives the incoming HTTP request and the model instance associated with the request:
306+
307+
```php
308+
use Illuminate\Http\Request;
309+
310+
File::make('avatar')
311+
->store(function (Request $request, $model) {
312+
return [
313+
'attachment' => $request->attachment->store('/', 's3'),
314+
'attachment_name' => $request->attachment->getClientOriginalName(),
315+
'attachment_size' => $request->attachment->getSize(),
316+
];
317+
}),
318+
```
319+
320+
As you can see in the example above, the `store` callback is returning an array of keys and values. These key / value pairs are mapped onto your model instance before it is saved to the database, allowing you to update one or many of the model's database columns after your file is stored.
321+
322+
#### Storeables
323+
324+
Of course, performing all of your file storage logic within a Closure can cause your resource to become bloated. For that reason, Restify allows you to pass an "Storable" class to the `store` method:
325+
326+
```php
327+
File::make('avatar')->store(AvatarStore::class),
328+
```
329+
330+
The storable class should be a simple PHP class and extends the `Binaryk\LaravelRestify\Repositories\Storable` contract:
331+
332+
```php
333+
<?php
334+
335+
namespace Binaryk\LaravelRestify\Tests\Fixtures\User;
336+
337+
use Binaryk\LaravelRestify\Repositories\Storable;
338+
use Illuminate\Database\Eloquent\Model;
339+
use Illuminate\Http\Request;
340+
341+
class AvatarStore implements Storable
342+
{
343+
public function handle(Request $request, Model $model, $attribute): array
344+
{
345+
return [
346+
'avatar' => $request->file('avatar')->storeAs('/', 'avatar.jpg', 'customDisk')
347+
];
348+
}
349+
}
350+
```
351+
352+
:::tip Command
353+
You can use the `php artisan restify:store AvatarStore` command to generate a store file.
354+
:::
202355

203356
## BelongsTo
204357

0 commit comments

Comments
 (0)