@@ -10,7 +10,7 @@ before proceeding. It will help you get a grasp on how the bundle works, and why
10
10
11
11
## Installing VichUploaderBundle
12
12
13
- Install the bundle with the help of composer :
13
+ Install the bundle with the help of Composer :
14
14
15
15
``` bash
16
16
docker-compose exec php composer require vich/uploader-bundle
@@ -20,7 +20,7 @@ This will create a new configuration file that you will need to slightly change
20
20
to make it look like this.
21
21
22
22
``` yaml
23
- # config/packages/vich_uploader.yaml
23
+ # api/ config/packages/vich_uploader.yaml
24
24
vich_uploader :
25
25
db_driver : orm
26
26
@@ -49,40 +49,74 @@ use ApiPlatform\Core\Annotation\ApiResource;
49
49
use App\C ontroller\C reateMediaObjectAction;
50
50
use Doctrine\O RM\M apping as ORM;
51
51
use Symfony\C omponent\H ttpFoundation\F ile\F ile;
52
+ use Symfony\C omponent\S erializer\A nnotation\G roups;
52
53
use Symfony\C omponent\V alidator\C onstraints as Assert;
53
54
use Vich\UploaderB undle\M apping\A nnotation as Vich;
54
55
55
56
/**
56
57
* @ORM\E ntity
57
- * @ApiResource(iri="http://schema.org/MediaObject", collectionOperations={
58
- * "get",
59
- * "post"={
60
- * "method"="POST",
61
- * "path"="/media_objects",
62
- * "controller"=CreateMediaObjectAction::class,
63
- * "defaults"={"_api_receive"=false},
58
+ * @ApiResource(
59
+ * iri="http://schema.org/MediaObject",
60
+ * normalizationContext={
61
+ * "groups"={"media_object_read"},
64
62
* },
65
- * })
63
+ * collectionOperations={
64
+ * "post"={
65
+ * "controller"=CreateMediaObjectAction::class,
66
+ * "defaults"={
67
+ * "_api_receive"=false,
68
+ * },
69
+ * "access_control"="is_granted('ROLE_USER')",
70
+ * "validation_groups"={"Default", "media_object_create"},
71
+ * "swagger_context"={
72
+ * "consumes"={
73
+ * "multipart/form-data",
74
+ * },
75
+ * "parameters"={
76
+ * {
77
+ * "in"="formData",
78
+ * "name"="file",
79
+ * "type"="file",
80
+ * "description"="The file to upload",
81
+ * },
82
+ * },
83
+ * },
84
+ * },
85
+ * "get",
86
+ * },
87
+ * itemOperations={
88
+ * "get",
89
+ * },
90
+ * )
66
91
* @Vich\Uploadabl e
67
92
*/
68
93
class MediaObject
69
94
{
70
95
// ...
71
96
97
+ /**
98
+ * @var string|null
99
+ *
100
+ * @ApiProperty(iri="http://schema.org/contentUrl")
101
+ * @Groups({"media_object_read"})
102
+ */
103
+ public $contentUrl;
104
+
72
105
/**
73
106
* @var File|null
74
- * @Assert\N otNull()
75
- * @Vich\Uploadabl eField(mapping="media_object", fileNameProperty="contentUrl")
107
+ *
108
+ * @Assert\N otNull(groups={"media_object_create"})
109
+ * @Vich\Uploadabl eField(mapping="media_object", fileNameProperty="filePath")
76
110
*/
77
111
public $file;
78
112
79
113
/**
80
114
* @var string|null
115
+ *
81
116
* @ORM\C olumn(nullable=true)
82
- * @ApiProperty(iri="http://schema.org/contentUrl")
83
117
*/
84
- public $contentUrl ;
85
-
118
+ public $filePath ;
119
+
86
120
// ...
87
121
}
88
122
` ` `
@@ -99,91 +133,125 @@ that handles the file upload.
99
133
namespace App\C ontroller;
100
134
101
135
use ApiPlatform\C ore\B ridge\S ymfony\V alidator\E xception\V alidationException;
136
+ use ApiPlatform\C ore\M etadata\R esource\F actory\R esourceMetadataFactoryInterface;
137
+ use ApiPlatform\C ore\Util\Requ estAttributesExtractor;
138
+ use ApiPlatform\C ore\V alidator\V alidatorInterface;
102
139
use App\E ntity\M ediaObject;
103
- use App\F orm\M ediaObjectType;
104
- use Sensio\B undle\F rameworkExtraBundle\C onfiguration\I sGranted;
105
- use Symfony\B ridge\D octrine\R egistryInterface;
106
- use Symfony\C omponent\F orm\F ormFactoryInterface;
140
+ use Doctrine\C ommon\P ersistence\M anagerRegistry;
107
141
use Symfony\C omponent\H ttpFoundation\R equest;
108
- use Symfony\C omponent\V alidator \V alidator \V alidatorInterface ;
142
+ use Symfony\C omponent\H ttpKernel \E xception \B adRequestHttpException ;
109
143
110
144
final class CreateMediaObjectAction
111
145
{
146
+ private $managerRegistry;
112
147
private $validator;
113
- private $doctrine;
114
- private $factory;
148
+ private $resourceMetadataFactory;
115
149
116
- public function __construct(RegistryInterface $doctrine, FormFactoryInterface $factory, ValidatorInterface $validator )
150
+ public function __construct(ManagerRegistry $managerRegistry, ValidatorInterface $validator, ResourceMetadataFactoryInterface $resourceMetadataFactory )
117
151
{
152
+ $this->managerRegistry = $managerRegistry;
118
153
$this->validator = $validator;
119
- $this->doctrine = $doctrine;
120
- $this->factory = $factory;
154
+ $this->resourceMetadataFactory = $resourceMetadataFactory;
121
155
}
122
156
123
- /**
124
- * @IsGranted("ROLE_USER")
125
- */
126
157
public function __invoke(Request $request): MediaObject
127
158
{
159
+ $uploadedFile = $request->files->get('file');
160
+
161
+ if (!$uploadedFile) {
162
+ throw new BadRequestHttpException('"file" is required');
163
+ }
164
+
128
165
$mediaObject = new MediaObject();
166
+ $mediaObject->file = $uploadedFile;
129
167
130
- $form = $this->factory->create(MediaObjectType::class, $mediaObject);
131
- $form->handleRequest($request);
132
- if ($form->isSubmitted() && $form->isValid()) {
133
- $em = $this->doctrine->getManager();
134
- $em->persist($mediaObject);
135
- $em->flush();
168
+ $this->validate($mediaObject, $request);
136
169
137
- // Prevent the serialization of the file property
138
- $mediaObject->file = null;
170
+ $em = $this->managerRegistry->getManager();
171
+ $em->persist($mediaObject);
172
+ $em->flush();
139
173
140
- return $mediaObject;
141
- }
174
+ return $mediaObject;
175
+ }
142
176
143
- // This will be handled by API Platform and returns a validation error.
144
- throw new ValidationException($this->validator->validate($mediaObject));
177
+ /**
178
+ * @throws ValidationException
179
+ */
180
+ private function validate(MediaObject $mediaObject, Request $request): void
181
+ {
182
+ $attributes = RequestAttributesExtractor::extractAttributes($request);
183
+ $resourceMetadata = $this->resourceMetadataFactory->create(MediaObject::class);
184
+ $validationGroups = $resourceMetadata->getOperationAttribute($attributes, 'validation_groups', null, true);
185
+
186
+ $this->validator->validate($mediaObject, ['groups' => $validationGroups]);
145
187
}
146
188
}
147
189
` ` `
148
190
149
- As you can see, the action uses a form. You will need this form to be like this :
191
+ # # Resolving the File URL
192
+
193
+ Returning the plain file path on the filesystem where the file is stored is not useful for the client, which needs a
194
+ URL to work with.
195
+
196
+ An [event subscriber](events.md) could be used to set the `contentUrl` property :
150
197
151
198
` ` ` php
152
199
<?php
153
- // api/src/Form/MediaObjectType .php
200
+ // api/src/EventSubscriber/ResolveMediaObjectContentUrlSubscriber .php
154
201
155
- namespace App\F orm ;
202
+ namespace App\E ventSubscriber ;
156
203
204
+ use ApiPlatform\C ore\E ventListener\E ventPriorities;
205
+ use ApiPlatform\C ore\Util\Requ estAttributesExtractor;
157
206
use App\E ntity\M ediaObject;
158
- use Symfony\C omponent\F orm\A bstractType;
159
- use Symfony\C omponent\F orm\E xtension\C ore\T ype\F ileType;
160
- use Symfony\C omponent\F orm\F ormBuilderInterface;
161
- use Symfony\C omponent\O ptionsResolver\O ptionsResolver;
207
+ use Symfony\C omponent\E ventDispatcher\E ventSubscriberInterface;
208
+ use Symfony\C omponent\H ttpFoundation\R esponse;
209
+ use Symfony\C omponent\H ttpKernel\E vent\G etResponseForControllerResultEvent;
210
+ use Symfony\C omponent\H ttpKernel\K ernelEvents;
211
+ use Vich\UploaderB undle\S torage\S torageInterface;
162
212
163
- final class MediaObjectType extends AbstractType
213
+ final class ResolveMediaObjectContentUrlSubscriber implements EventSubscriberInterface
164
214
{
165
- public function buildForm(FormBuilderInterface $builder, array $options)
215
+ private $storage;
216
+
217
+ public function __construct(StorageInterface $storage)
166
218
{
167
- $builder
168
- // Configure each field you want to be submitted here, like a classic form.
169
- ->add('file', FileType::class, [
170
- 'label' => 'label.file',
171
- 'required' => false,
172
- ])
173
- ;
219
+ $this->storage = $storage;
174
220
}
175
221
176
- public function configureOptions(OptionsResolver $resolver)
222
+ public static function getSubscribedEvents(): array
177
223
{
178
- $resolver->setDefaults([
179
- 'data_class' => MediaObject::class,
180
- 'csrf_protection' => false,
181
- ]);
224
+ return [
225
+ KernelEvents::VIEW => ['onPreSerialize', EventPriorities::PRE_SERIALIZE],
226
+ ];
182
227
}
183
228
184
- public function getBlockPrefix()
229
+ public function onPreSerialize(GetResponseForControllerResultEvent $event): void
185
230
{
186
- return '';
231
+ $controllerResult = $event->getControllerResult();
232
+ $request = $event->getRequest();
233
+
234
+ if ($controllerResult instanceof Response || !$request->attributes->getBoolean('_api_respond', true)) {
235
+ return;
236
+ }
237
+
238
+ if (!$attributes = RequestAttributesExtractor::extractAttributes($request) || !\i s_a($attributes['resource_class'], MediaObject::class, true)) {
239
+ return;
240
+ }
241
+
242
+ $mediaObjects = $controllerResult;
243
+
244
+ if (!is_iterable($mediaObjects)) {
245
+ $mediaObjects = [$mediaObjects];
246
+ }
247
+
248
+ foreach ($mediaObjects as $mediaObject) {
249
+ if (!$mediaObject instanceof MediaObject) {
250
+ continue;
251
+ }
252
+
253
+ $mediaObject->contentUrl = $this->storage->resolveUri($mediaObject, 'file');
254
+ }
187
255
}
188
256
}
189
257
` ` `
@@ -197,9 +265,9 @@ your data, you will get a response looking like this:
197
265
198
266
` ` ` json
199
267
{
200
- "@type": "http://schema.org/ImageObject ",
201
- "@id": "/media_objects/<id>",
202
- "contentUrl": "<filename>",
268
+ "@type": "http://schema.org/MediaObject ",
269
+ "@id": "/media_objects/<id>",
270
+ "contentUrl": "<url>"
203
271
}
204
272
` ` `
205
273
@@ -232,7 +300,8 @@ class Book
232
300
233
301
/**
234
302
* @var MediaObject|null
235
- * @ORM\M anyToOne(targetEntity="App\E ntity\M ediaObject")
303
+ *
304
+ * @ORM\M anyToOne(targetEntity=MediaObject::class)
236
305
* @ORM\J oinColumn(nullable=true)
237
306
* @ApiProperty(iri="http://schema.org/image")
238
307
*/
0 commit comments