Completed
Push — master ( d66330...eb9ee0 )
by Grégoire
02:15
created

GalleryController::getGalleryGalleryItemsAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\MediaBundle\Controller\Api;
15
16
use FOS\RestBundle\Context\Context;
17
use FOS\RestBundle\Controller\Annotations as Rest;
18
use FOS\RestBundle\Request\ParamFetcherInterface;
19
use FOS\RestBundle\View\View as FOSRestView;
20
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
21
use Sonata\DatagridBundle\Pager\PagerInterface;
22
use Sonata\MediaBundle\Form\Type\ApiGalleryItemType;
23
use Sonata\MediaBundle\Form\Type\ApiGalleryType;
24
use Sonata\MediaBundle\Model\GalleryInterface;
25
use Sonata\MediaBundle\Model\GalleryItemInterface;
26
use Sonata\MediaBundle\Model\GalleryManagerInterface;
27
use Sonata\MediaBundle\Model\MediaInterface;
28
use Sonata\MediaBundle\Model\MediaManagerInterface;
29
use Symfony\Component\Form\FormFactoryInterface;
30
use Symfony\Component\Form\FormInterface;
31
use Symfony\Component\HttpFoundation\Request;
32
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
33
34
/**
35
 * @final since sonata-project/media-bundle 3.21.0
36
 *
37
 * @author Hugo Briand <[email protected]>
38
 */
39
class GalleryController
40
{
41
    /**
42
     * @var GalleryManagerInterface
43
     */
44
    protected $galleryManager;
45
46
    /**
47
     * @var MediaManagerInterface
48
     */
49
    protected $mediaManager;
50
51
    /**
52
     * @var FormFactoryInterface
53
     */
54
    protected $formFactory;
55
56
    /**
57
     * @var string
58
     */
59
    protected $galleryItemClass;
60
61
    /**
62
     * @param string $galleryItemClass
63
     */
64
    public function __construct(GalleryManagerInterface $galleryManager, MediaManagerInterface $mediaManager, FormFactoryInterface $formFactory, $galleryItemClass)
65
    {
66
        $this->galleryManager = $galleryManager;
67
        $this->mediaManager = $mediaManager;
68
        $this->formFactory = $formFactory;
69
        $this->galleryItemClass = $galleryItemClass;
70
    }
71
72
    /**
73
     * Retrieves the list of galleries (paginated).
74
     *
75
     * @ApiDoc(
76
     *  resource=true,
77
     *  output={"class"="Sonata\DatagridBundle\Pager\PagerInterface", "groups"={"sonata_api_read"}}
78
     * )
79
     *
80
     * @Rest\QueryParam(
81
     *     name="page",
82
     *     requirements="\d+",
83
     *     default="1",
84
     *     description="Page for gallery list pagination"
85
     * )
86
     * @Rest\QueryParam(
87
     *     name="count",
88
     *     requirements="\d+",
89
     *     default="10",
90
     *     description="Number of galleries by page"
91
     * )
92
     * @Rest\QueryParam(
93
     *     name="enabled",
94
     *     requirements="0|1",
95
     *     nullable=true,
96
     *     strict=true,
97
     *     description="Enabled/Disabled galleries filter"
98
     * )
99
     * @Rest\QueryParam(
100
     *     name="orderBy",
101
     *     map=true,
102
     *     requirements="ASC|DESC",
103
     *     nullable=true,
104
     *     strict=true,
105
     *     description="Order by array (key is field, value is direction)"
106
     * )
107
     *
108
     * @Rest\View(serializerGroups={"sonata_api_read"}, serializerEnableMaxDepthChecks=true)
109
     *
110
     * @return PagerInterface
111
     */
112
    public function getGalleriesAction(ParamFetcherInterface $paramFetcher)
113
    {
114
        $supportedCriteria = [
115
            'enabled' => '',
116
        ];
117
118
        $page = $paramFetcher->get('page');
119
        $limit = $paramFetcher->get('count');
120
        $sort = $paramFetcher->get('orderBy');
121
        $criteria = array_intersect_key($paramFetcher->all(), $supportedCriteria);
122
123
        foreach ($criteria as $key => $value) {
124
            if (null === $value) {
125
                unset($criteria[$key]);
126
            }
127
        }
128
129
        if (!$sort) {
130
            $sort = [];
131
        } elseif (!\is_array($sort)) {
132
            $sort = [$sort => 'asc'];
133
        }
134
135
        return $this->getGalleryManager()->getPager($criteria, $page, $limit, $sort);
136
    }
137
138
    /**
139
     * Retrieves a specific gallery.
140
     *
141
     * @ApiDoc(
142
     *  requirements={
143
     *      {"name"="id", "dataType"="integer", "requirement"="\d+", "description"="Gallery identifier"}
144
     *  },
145
     *  output={"class"="sonata_media_api_form_gallery", "groups"={"sonata_api_read"}},
146
     *  statusCodes={
147
     *      200="Returned when successful",
148
     *      404="Returned when gallery is not found"
149
     *  }
150
     * )
151
     *
152
     * @Rest\View(serializerGroups={"sonata_api_read"}, serializerEnableMaxDepthChecks=true)
153
     *
154
     * @param int $id Gallery identifier
155
     *
156
     * @return GalleryInterface
157
     */
158
    public function getGalleryAction($id)
159
    {
160
        return $this->getGallery($id);
161
    }
162
163
    /**
164
     * Retrieves the medias of specified gallery.
165
     *
166
     * @ApiDoc(
167
     *  requirements={
168
     *      {"name"="id", "dataType"="integer", "requirement"="\d+", "description"="Gallery identifier"}
169
     *  },
170
     *  output={"class"="Sonata\MediaBundle\Model\Media", "groups"={"sonata_api_read"}},
171
     *  statusCodes={
172
     *      200="Returned when successful",
173
     *      404="Returned when gallery is not found"
174
     *  }
175
     * )
176
     *
177
     * @Rest\View(serializerGroups={"sonata_api_read"}, serializerEnableMaxDepthChecks=true)
178
     *
179
     * @param int $id Gallery identifier
180
     *
181
     * @return MediaInterface[]
182
     */
183
    public function getGalleryMediasAction($id)
184
    {
185
        $galleryItems = $this->getGallery($id)->getGalleryItems();
186
187
        $media = [];
188
        foreach ($galleryItems as $galleryItem) {
189
            $media[] = $galleryItem->getMedia();
190
        }
191
192
        return $media;
193
    }
194
195
    /**
196
     * Retrieves the gallery items of specified gallery.
197
     *
198
     * @ApiDoc(
199
     *  requirements={
200
     *      {"name"="id", "dataType"="integer", "requirement"="\d+", "description"="Gallery identifier"}
201
     *  },
202
     *  output={"class"="Sonata\MediaBundle\Model\GalleryItem", "groups"={"sonata_api_read"}},
203
     *  statusCodes={
204
     *      200="Returned when successful",
205
     *      404="Returned when gallery is not found"
206
     *  }
207
     * )
208
     *
209
     * @Rest\View(serializerGroups={"sonata_api_read"}, serializerEnableMaxDepthChecks=true)
210
     *
211
     * @param int $id Gallery identifier
212
     *
213
     * @return GalleryItemInterface[]
214
     */
215
    public function getGalleryGalleryItemsAction($id)
216
    {
217
        return $this->getGallery($id)->getGalleryItems();
218
    }
219
220
    /**
221
     * Adds a gallery.
222
     *
223
     * @ApiDoc(
224
     *  input={"class"="sonata_media_api_form_gallery", "name"="", "groups"={"sonata_api_write"}},
225
     *  output={"class"="sonata_media_api_form_gallery", "groups"={"sonata_api_read"}},
226
     *  statusCodes={
227
     *      200="Returned when successful",
228
     *      400="Returned when an error has occurred while gallery creation",
229
     *  }
230
     * )
231
     *
232
     * @param Request $request A Symfony request
233
     *
234
     * @throws NotFoundHttpException
235
     *
236
     * @return GalleryInterface
237
     */
238
    public function postGalleryAction(Request $request)
239
    {
240
        return $this->handleWriteGallery($request);
241
    }
242
243
    /**
244
     * Updates a gallery.
245
     *
246
     * @ApiDoc(
247
     *  requirements={
248
     *      {"name"="id", "dataType"="integer", "requirement"="\d+", "description"="Gallery identifier"}
249
     *  },
250
     *  input={"class"="sonata_media_api_form_gallery", "name"="", "groups"={"sonata_api_write"}},
251
     *  output={"class"="sonata_media_api_form_gallery", "groups"={"sonata_api_read"}},
252
     *  statusCodes={
253
     *      200="Returned when successful",
254
     *      400="Returned when an error has occurred while gallery creation",
255
     *      404="Returned when unable to find gallery"
256
     *  }
257
     * )
258
     *
259
     * @param int     $id      User identifier
260
     * @param Request $request Symfony request
261
     *
262
     * @throws NotFoundHttpException
263
     *
264
     * @return GalleryInterface
265
     */
266
    public function putGalleryAction($id, Request $request)
267
    {
268
        return $this->handleWriteGallery($request, $id);
269
    }
270
271
    /**
272
     * Adds a medium to a gallery.
273
     *
274
     * @ApiDoc(
275
     *  requirements={
276
     *      {"name"="galleryId", "dataType"="integer", "requirement"="\d+", "description"="Gallery identifier"},
277
     *      {"name"="mediaId", "dataType"="integer", "requirement"="\d+", "description"="Medium identifier"}
278
     *  },
279
     *  input={"class"="sonata_media_api_form_gallery_item", "name"="", "groups"={"sonata_api_write"}},
280
     *  output={"class"="sonata_media_api_form_gallery", "groups"={"sonata_api_read"}},
281
     *  statusCodes={
282
     *      200="Returned when successful",
283
     *      400="Returned when an error has occurred while gallery/media attachment",
284
     *  }
285
     * )
286
     *
287
     * @param int     $galleryId Gallery identifier
288
     * @param int     $mediaId   Medium identifier
289
     * @param Request $request   Symfony request
290
     *
291
     * @throws NotFoundHttpException
292
     *
293
     * @return GalleryInterface
294
     */
295
    public function postGalleryMediaGalleryItemAction($galleryId, $mediaId, Request $request)
296
    {
297
        $gallery = $this->getGallery($galleryId);
298
        $media = $this->getMedia($mediaId);
299
300
        foreach ($gallery->getGalleryItems() as $galleryItem) {
301
            if ($galleryItem->getMedia()->getId() === $media->getId()) {
302
                return FOSRestView::create([
303
                    'error' => sprintf('Gallery "%s" already has media "%s"', $galleryId, $mediaId),
304
                ], 400);
305
            }
306
        }
307
308
        return $this->handleWriteGalleryItem($gallery, $media, null, $request);
309
    }
310
311
    /**
312
     * Updates a medium to a gallery.
313
     *
314
     * @ApiDoc(
315
     *  requirements={
316
     *      {"name"="galleryId", "dataType"="integer", "requirement"="\d+", "description"="Gallery identifier"},
317
     *      {"name"="mediaId", "dataType"="integer", "requirement"="\d+", "description"="Medium identifier"}
318
     *  },
319
     *  input={"class"="sonata_media_api_form_gallery_item", "name"="", "groups"={"sonata_api_write"}},
320
     *  output={"class"="sonata_media_api_form_gallery", "groups"={"sonata_api_read"}},
321
     *  statusCodes={
322
     *      200="Returned when successful",
323
     *      404="Returned when an error if medium cannot be found in gallery",
324
     *  }
325
     * )
326
     *
327
     * @param int     $galleryId Gallery identifier
328
     * @param int     $mediaId   Medium identifier
329
     * @param Request $request   Symfony request
330
     *
331
     * @throws NotFoundHttpException
332
     *
333
     * @return GalleryInterface
334
     */
335
    public function putGalleryMediaGalleryItemAction($galleryId, $mediaId, Request $request)
336
    {
337
        $gallery = $this->getGallery($galleryId);
338
        $media = $this->getMedia($mediaId);
339
340
        foreach ($gallery->getGalleryItems() as $galleryItem) {
341
            if ($galleryItem->getMedia()->getId() === $media->getId()) {
342
                return $this->handleWriteGalleryItem($gallery, $media, $galleryItem, $request);
343
            }
344
        }
345
346
        throw new NotFoundHttpException(sprintf('Gallery "%s" does not have media "%s"', $galleryId, $mediaId));
347
    }
348
349
    /**
350
     * Deletes a medium association to a gallery.
351
     *
352
     * @ApiDoc(
353
     *  requirements={
354
     *      {"name"="galleryId", "dataType"="integer", "requirement"="\d+", "description"="Gallery identifier"},
355
     *      {"name"="mediaId", "dataType"="integer", "requirement"="\d+", "description"="Medium identifier"}
356
     *  },
357
     *  statusCodes={
358
     *      200="Returned when medium is successfully deleted from gallery",
359
     *      400="Returned when an error has occurred while medium deletion of gallery",
360
     *      404="Returned when unable to find gallery or media"
361
     *  }
362
     * )
363
     *
364
     * @param int $galleryId A gallery identifier
365
     * @param int $mediaId   A media identifier
366
     *
367
     * @throws NotFoundHttpException
368
     *
369
     * @return Rest\View
370
     */
371
    public function deleteGalleryMediaGalleryItemAction($galleryId, $mediaId)
372
    {
373
        $gallery = $this->getGallery($galleryId);
374
        $media = $this->getMedia($mediaId);
375
376
        foreach ($gallery->getGalleryItems() as $key => $galleryItem) {
377
            if ($galleryItem->getMedia()->getId() === $media->getId()) {
378
                $gallery->getGalleryItems()->remove($key);
379
                $this->getGalleryManager()->save($gallery);
380
381
                return ['deleted' => true];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array('deleted' => true); (array<string,boolean>) is incompatible with the return type documented by Sonata\MediaBundle\Contr...yMediaGalleryItemAction of type FOS\RestBundle\Controller\Annotations\View.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
382
            }
383
        }
384
385
        return FOSRestView::create([
386
            'error' => sprintf('Gallery "%s" does not have media "%s" associated', $galleryId, $mediaId),
387
        ], 400);
388
    }
389
390
    /**
391
     * Deletes a gallery.
392
     *
393
     * @ApiDoc(
394
     *  requirements={
395
     *      {"name"="id", "dataType"="integer", "requirement"="\d+", "description"="Gallery identifier"}
396
     *  },
397
     *  statusCodes={
398
     *      200="Returned when gallery is successfully deleted",
399
     *      400="Returned when an error has occurred while gallery deletion",
400
     *      404="Returned when unable to find gallery"
401
     *  }
402
     * )
403
     *
404
     * @param int $id A Gallery identifier
405
     *
406
     * @throws NotFoundHttpException
407
     *
408
     * @return Rest\View
409
     */
410
    public function deleteGalleryAction($id)
411
    {
412
        $gallery = $this->getGallery($id);
413
414
        $this->galleryManager->delete($gallery);
415
416
        return ['deleted' => true];
417
    }
418
419
    /**
420
     * Write a GalleryItem, this method is used by both POST and PUT action methods.
421
     *
422
     * @return FormInterface
423
     */
424
    protected function handleWriteGalleryItem(GalleryInterface $gallery, MediaInterface $media, ?GalleryItemInterface $galleryItem = null, Request $request)
425
    {
426
        $form = $this->formFactory->createNamed(null, ApiGalleryItemType::class, $galleryItem, [
427
            'csrf_protection' => false,
428
        ]);
429
430
        $form->handleRequest($request);
431
432
        if ($form->isValid()) {
433
            $galleryItem = $form->getData();
434
            $galleryItem->setMedia($media);
435
436
            $gallery->addGalleryItem($galleryItem);
437
            $this->galleryManager->save($gallery);
0 ignored issues
show
Documentation introduced by
$gallery is of type object<Sonata\MediaBundle\Model\GalleryInterface>, but the function expects a object<Sonata\Doctrine\Model\T>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
438
439
            $view = FOSRestView::create($galleryItem);
440
441
            $context = new Context();
442
            $context->setGroups(['sonata_api_read']);
443
            $context->enableMaxDepth();
444
445
            $view->setContext($context);
446
447
            return $view;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $view; (FOS\RestBundle\View\View) is incompatible with the return type documented by Sonata\MediaBundle\Contr...:handleWriteGalleryItem of type Symfony\Component\Form\FormInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
448
        }
449
450
        return $form;
451
    }
452
453
    /**
454
     * Retrieves gallery with identifier $id or throws an exception if it doesn't exist.
455
     *
456
     * @throws NotFoundHttpException
457
     *
458
     * @return GalleryInterface
459
     */
460
    protected function getGallery($id)
461
    {
462
        $gallery = $this->getGalleryManager()->findOneBy(['id' => $id]);
463
464
        if (null === $gallery) {
465
            throw new NotFoundHttpException(sprintf('Gallery (%d) not found', $id));
466
        }
467
468
        return $gallery;
469
    }
470
471
    /**
472
     * Retrieves media with identifier $id or throws an exception if it doesn't exist.
473
     *
474
     * @throws NotFoundHttpException
475
     *
476
     * @return MediaInterface
477
     */
478
    protected function getMedia($id)
479
    {
480
        $media = $this->getMediaManager()->findOneBy(['id' => $id]);
481
482
        if (null === $media) {
483
            throw new NotFoundHttpException(sprintf('Media (%d) not found', $id));
484
        }
485
486
        return $media;
487
    }
488
489
    /**
490
     * @return GalleryManagerInterface
491
     */
492
    protected function getGalleryManager()
493
    {
494
        return $this->galleryManager;
495
    }
496
497
    /**
498
     * @return MediaManagerInterface
499
     */
500
    protected function getMediaManager()
501
    {
502
        return $this->mediaManager;
503
    }
504
505
    /**
506
     * Write a Gallery, this method is used by both POST and PUT action methods.
507
     *
508
     * @param Request  $request Symfony request
509
     * @param int|null $id      A Gallery identifier
510
     *
511
     * @return Rest\View|FormInterface
512
     */
513
    protected function handleWriteGallery($request, $id = null)
514
    {
515
        $gallery = $id ? $this->getGallery($id) : null;
516
517
        $form = $this->formFactory->createNamed(null, ApiGalleryType::class, $gallery, [
518
            'csrf_protection' => false,
519
        ]);
520
521
        $form->handleRequest($request);
522
523
        if ($form->isValid()) {
524
            $gallery = $form->getData();
525
            $this->galleryManager->save($gallery);
526
527
            $context = new Context();
528
            $context->setGroups(['sonata_api_read']);
529
            $context->enableMaxDepth();
530
531
            $view = FOSRestView::create($gallery);
532
            $view->setContext($context);
533
534
            return $view;
535
        }
536
537
        return $form;
538
    }
539
}
540