Completed
Push — master ( 63e549...ae2d46 )
by Jordi Sala
08:25
created

GalleryController::handleWriteGalleryItem()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
383
                $this->getGalleryManager()->save($gallery);
384
385
                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...
386
            }
387
        }
388
389
        return FOSRestView::create([
390
            'error' => sprintf('Gallery "%s" does not have media "%s" associated', $galleryId, $mediaId),
391
        ], 400);
392
    }
393
394
    /**
395
     * Deletes a gallery.
396
     *
397
     * @ApiDoc(
398
     *  requirements={
399
     *      {"name"="id", "dataType"="integer", "requirement"="\d+", "description"="gallery identifier"}
400
     *  },
401
     *  statusCodes={
402
     *      200="Returned when gallery is successfully deleted",
403
     *      400="Returned when an error has occurred while gallery deletion",
404
     *      404="Returned when unable to find gallery"
405
     *  }
406
     * )
407
     *
408
     * @param int $id A Gallery identifier
409
     *
410
     * @throws NotFoundHttpException
411
     *
412
     * @return View
413
     */
414
    public function deleteGalleryAction($id)
415
    {
416
        $gallery = $this->getGallery($id);
417
418
        $this->galleryManager->delete($gallery);
419
420
        return ['deleted' => true];
421
    }
422
423
    /**
424
     * Write a GalleryItem, this method is used by both POST and PUT action methods.
425
     *
426
     * @param GalleryInterface     $gallery
427
     * @param MediaInterface       $media
428
     * @param GalleryItemInterface $galleryItem
429
     * @param Request              $request
430
     *
431
     * @return FormInterface
432
     */
433
    protected function handleWriteGalleryItem(GalleryInterface $gallery, MediaInterface $media, GalleryItemInterface $galleryItem = null, Request $request)
434
    {
435
        $form = $this->formFactory->createNamed(null, 'sonata_media_api_form_gallery_item', $galleryItem, [
436
            'csrf_protection' => false,
437
        ]);
438
439
        $form->handleRequest($request);
440
441
        if ($form->isValid()) {
442
            $galleryItem = $form->getData();
443
            $galleryItem->setMedia($media);
444
445
            $gallery->addGalleryItem($galleryItem);
446
            $this->galleryManager->save($gallery);
447
448
            $view = FOSRestView::create($galleryItem);
449
450
            $context = new Context();
451
            $context->setGroups(['sonata_api_read']);
452
            $context->enableMaxDepth();
453
454
            $view->setContext($context);
455
456
            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...
457
        }
458
459
        return $form;
460
    }
461
462
    /**
463
     * Retrieves gallery with id $id or throws an exception if it doesn't exist.
464
     *
465
     * @param $id
466
     *
467
     * @throws NotFoundHttpException
468
     *
469
     * @return GalleryInterface
470
     */
471
    protected function getGallery($id)
472
    {
473
        $gallery = $this->getGalleryManager()->findOneBy(['id' => $id]);
474
475
        if (null === $gallery) {
476
            throw new NotFoundHttpException(sprintf('Gallery (%d) not found', $id));
477
        }
478
479
        return $gallery;
480
    }
481
482
    /**
483
     * Retrieves media with id $id or throws an exception if it doesn't exist.
484
     *
485
     * @param $id
486
     *
487
     * @throws NotFoundHttpException
488
     *
489
     * @return MediaInterface
490
     */
491
    protected function getMedia($id)
492
    {
493
        $media = $this->getMediaManager()->findOneBy(['id' => $id]);
494
495
        if (null === $media) {
496
            throw new NotFoundHttpException(sprintf('Media (%d) not found', $id));
497
        }
498
499
        return $media;
500
    }
501
502
    /**
503
     * @return GalleryManagerInterface
504
     */
505
    protected function getGalleryManager()
506
    {
507
        return $this->galleryManager;
508
    }
509
510
    /**
511
     * @return MediaManagerInterface
512
     */
513
    protected function getMediaManager()
514
    {
515
        return $this->mediaManager;
516
    }
517
518
    /**
519
     * Write a Gallery, this method is used by both POST and PUT action methods.
520
     *
521
     * @param Request  $request Symfony request
522
     * @param int|null $id      A Gallery identifier
523
     *
524
     * @return View|FormInterface
525
     */
526
    protected function handleWriteGallery($request, $id = null)
527
    {
528
        $gallery = $id ? $this->getGallery($id) : null;
529
530
        $form = $this->formFactory->createNamed(null, 'sonata_media_api_form_gallery', $gallery, [
531
            'csrf_protection' => false,
532
        ]);
533
534
        $form->handleRequest($request);
535
536
        if ($form->isValid()) {
537
            $gallery = $form->getData();
538
            $this->galleryManager->save($gallery);
539
540
            $context = new Context();
541
            $context->setGroups(['sonata_api_read']);
542
            $context->enableMaxDepth();
543
544
            $view = FOSRestView::create($gallery);
545
            $view->setContext($context);
546
547
            return $view;
548
        }
549
550
        return $form;
551
    }
552
}
553