Passed
Pull Request — feature/publishable (#43)
by Vincent
06:24
created

PublishableNormalizer::supportsDenormalization()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
cc 2
nc 2
nop 4
crap 6
1
<?php
2
3
/*
4
 * This file is part of the Silverback API Component Bundle Project
5
 *
6
 * (c) Daniel West <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Silverback\ApiComponentBundle\Serializer;
15
16
use Doctrine\Persistence\ManagerRegistry;
17
use Silverback\ApiComponentBundle\Publishable\PublishableHelper;
18
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
19
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
20
use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
21
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
22
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
23
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
24
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
25
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
26
27
/**
28
 * Adds `published` property on response, if not set.
29
 *
30
 * @author Vincent Chalamon <[email protected]>
31
 */
32
final class PublishableNormalizer implements ContextAwareNormalizerInterface, CacheableSupportsMethodInterface, NormalizerAwareInterface, ContextAwareDenormalizerInterface, DenormalizerAwareInterface
33
{
34
    use NormalizerAwareTrait;
35
    use DenormalizerAwareTrait;
36
37
    private const ALREADY_CALLED = 'PUBLISHABLE_NORMALIZER_ALREADY_CALLED';
38
39
    private PublishableHelper $publishableHelper;
40
    private ManagerRegistry $registry;
41
42
    public function __construct(PublishableHelper $publishableHelper, ManagerRegistry $registry)
43
    {
44
        $this->publishableHelper = $publishableHelper;
45
        $this->registry = $registry;
46
    }
47
48
    public function normalize($object, $format = null, array $context = [])
49
    {
50
        $context[self::ALREADY_CALLED] = true;
51
        $data = $this->normalizer->normalize($object, $format, $context);
52
53
        if (!\array_key_exists('published', $data)) {
54
            $data['published'] = $this->publishableHelper->isPublished($object);
55
        }
56
57
        return $data;
58
    }
59
60
    public function supportsNormalization($data, $format = null, $context = []): bool
61
    {
62
        return !isset($context[self::ALREADY_CALLED]) &&
63
            \is_object($data) &&
64
            !$data instanceof \Traversable &&
65
            $this->publishableHelper->isPublishable($data);
66
    }
67
68
    public function denormalize($data, string $type, string $format = null, array $context = [])
69
    {
70
        $context[self::ALREADY_CALLED] = true;
71
        $configuration = $this->publishableHelper->getConfiguration($type);
72
73
        // It's not possible to change the publishedResource and draftResource properties
74
        unset($data[$configuration->associationName], $data[$configuration->reverseAssociationName]);
75
76
        // User doesn't have draft access: cannot set or change the publication date
77
        if (!$this->publishableHelper->isGranted()) {
78
            unset($data[$configuration->fieldName]);
79
        }
80
81
        // It's a new object
82
        if (!isset($context[AbstractNormalizer::OBJECT_TO_POPULATE])) {
83
            // User doesn't have draft access: force publication date
84
            if (!$this->publishableHelper->isGranted()) {
85
                $data[$configuration->fieldName] = date('Y-m-d H:i:s');
86
            }
87
88
            return $this->denormalizer->denormalize($data, $type, $format, $context);
89
        }
90
91
        $object = $context[AbstractNormalizer::OBJECT_TO_POPULATE];
92
        if (isset($data[$configuration->fieldName])) {
93
            $publicationDate = new \DateTimeImmutable($data[$configuration->fieldName]);
94
95
            // User changed the publication date with an earlier one on a published resource: ignore it
96
            if (
97
                $this->publishableHelper->isPublished($object) &&
98
                !empty($publicationDate) &&
99
                new \DateTimeImmutable() >= $publicationDate
100
            ) {
101
                unset($data[$configuration->fieldName]);
102
            }
103
        }
104
105
        // No field has been updated: nothing to do here anymore
106
        if (empty($data)) {
107
            return $this->denormalizer->denormalize($data, $type, $format, $context);
108
        }
109
110
        $em = $this->registry->getManagerForClass($type);
111
        $classMetadata = $em->getClassMetadata($type);
112
113
        // Resource is a draft: nothing to do here anymore
114
        if (
115
            null !== $classMetadata->getFieldValue($object, $configuration->associationName) ||
0 ignored issues
show
Bug introduced by
The method getFieldValue() does not exist on Doctrine\Persistence\Mapping\ClassMetadata. Did you maybe mean getFieldNames()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

115
            null !== $classMetadata->/** @scrutinizer ignore-call */ getFieldValue($object, $configuration->associationName) ||

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
116
            !$this->publishableHelper->isPublished($object)
117
        ) {
118
            return $this->denormalizer->denormalize($data, $type, $format, $context);
119
        }
120
121
        // User doesn't have draft access: update the original object
122
        if (!$this->publishableHelper->isGranted()) {
123
            return $this->denormalizer->denormalize($data, $type, $format, $context);
124
        }
125
126
        // Any field has been modified: create a draft
127
        $draft = clone $object; // Identifier(s) should be reset from AbstractComponent::__clone method
128
129
        // Add draft object to UnitOfWork
130
        $em->persist($draft);
131
132
        // Empty publishedDate on draft
133
        $classMetadata->setFieldValue($draft, $configuration->fieldName, null);
0 ignored issues
show
Bug introduced by
The method setFieldValue() does not exist on Doctrine\Persistence\Mapping\ClassMetadata. It seems like you code against a sub-type of Doctrine\Persistence\Mapping\ClassMetadata such as Doctrine\ORM\Mapping\ClassMetadataInfo. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

133
        $classMetadata->/** @scrutinizer ignore-call */ 
134
                        setFieldValue($draft, $configuration->fieldName, null);
Loading history...
134
135
        // Set publishedResource on draft
136
        $classMetadata->setFieldValue($draft, $configuration->associationName, $object);
137
138
        // Set draftResource on data
139
        $classMetadata->setFieldValue($object, $configuration->reverseAssociationName, $draft);
140
141
        $context[AbstractNormalizer::OBJECT_TO_POPULATE] = $draft;
142
143
        return $this->denormalizer->denormalize($data, $type, $format, $context);
144
    }
145
146
    public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool
147
    {
148
        return !isset($context[self::ALREADY_CALLED]) && $this->publishableHelper->isPublishable($type);
149
    }
150
151
    public function hasCacheableSupportsMethod(): bool
152
    {
153
        return false;
154
    }
155
}
156