Passed
Pull Request — master (#50)
by Daniel
07:31
created

hasCacheableSupportsMethod()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

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 1
nc 1
nop 0
crap 2
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\Normalizer;
15
16
use Doctrine\ORM\Mapping\ClassMetadataInfo;
17
use Doctrine\Persistence\ManagerRegistry;
18
use Silverback\ApiComponentBundle\Exception\InvalidArgumentException;
19
use Silverback\ApiComponentBundle\Publishable\PublishableHelper;
20
use Symfony\Component\HttpFoundation\RequestStack;
21
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
22
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
23
use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
24
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
25
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
26
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
27
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
28
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
29
30
/**
31
 * Adds `published` property on response, if not set.
32
 *
33
 * @author Vincent Chalamon <[email protected]>
34
 */
35
final class PublishableNormalizer implements ContextAwareNormalizerInterface, CacheableSupportsMethodInterface, NormalizerAwareInterface, ContextAwareDenormalizerInterface, DenormalizerAwareInterface
36
{
37
    use NormalizerAwareTrait;
38
    use DenormalizerAwareTrait;
39
40
    private const ALREADY_CALLED = 'PUBLISHABLE_NORMALIZER_ALREADY_CALLED';
41
42
    private PublishableHelper $publishableHelper;
43
    private ManagerRegistry $registry;
44
    private RequestStack $requestStack;
45
46
    public function __construct(PublishableHelper $publishableHelper, ManagerRegistry $registry, RequestStack $requestStack)
47
    {
48
        $this->publishableHelper = $publishableHelper;
49
        $this->registry = $registry;
50
        $this->requestStack = $requestStack;
51
    }
52
53
    public function normalize($object, $format = null, array $context = [])
54
    {
55
        $context[self::ALREADY_CALLED] = true;
56
        $context[MetadataNormalizer::METADATA_CONTEXT]['published'] = $this->publishableHelper->isActivePublishedAt($object);
57
58
        return $this->normalizer->normalize($object, $format, $context);
59
    }
60
61
    public function supportsNormalization($data, $format = null, $context = []): bool
62
    {
63
        return !isset($context[self::ALREADY_CALLED]) &&
64
            \is_object($data) &&
65
            !$data instanceof \Traversable &&
66
            $this->publishableHelper->isPublishable($data);
67
    }
68
69
    /**
70
     * {@inheritdoc}
71
     */
72
    public function denormalize($data, $type, $format = null, array $context = [])
73
    {
74
        $context[self::ALREADY_CALLED] = true;
75
        $configuration = $this->publishableHelper->getConfiguration($type);
76
        if (!$configuration) {
0 ignored issues
show
introduced by
$configuration is of type Silverback\ApiComponentB...\Annotation\Publishable, thus it always evaluated to true.
Loading history...
77
            throw new InvalidArgumentException(sprintf('Could not get configuration for %s', $type));
78
        }
79
80
        // It's not possible to change the publishedResource and draftResource properties
81
        unset($data[$configuration->associationName], $data[$configuration->reverseAssociationName]);
82
83
        // User doesn't have draft access: cannot set or change the publication date
84
        if (!$this->publishableHelper->isGranted()) {
85
            unset($data[$configuration->fieldName]);
86
        }
87
88
        $request = $this->requestStack->getMasterRequest();
89
        if ($request && true === $this->publishableHelper->isPublishedRequest($request)) {
90
            return $this->denormalizer->denormalize($data, $type, $format, $context);
91
        }
92
93
        // It's a new object
94
        if (!isset($context[AbstractNormalizer::OBJECT_TO_POPULATE])) {
95
            // User doesn't have draft access: force publication date
96
            if (!$this->publishableHelper->isGranted()) {
97
                $data[$configuration->fieldName] = date('Y-m-d H:i:s');
98
            }
99
100
            return $this->denormalizer->denormalize($data, $type, $format, $context);
101
        }
102
103
        $object = $context[AbstractNormalizer::OBJECT_TO_POPULATE];
104
        if (isset($data[$configuration->fieldName])) {
105
            $publicationDate = new \DateTimeImmutable($data[$configuration->fieldName]);
106
107
            // User changed the publication date with an earlier one on a published resource: ignore it
108
            if (
109
                $this->publishableHelper->isActivePublishedAt($object) &&
110
                new \DateTimeImmutable() >= $publicationDate
111
            ) {
112
                unset($data[$configuration->fieldName]);
113
            }
114
        }
115
116
        // No field has been updated: nothing to do here anymore
117
        if (empty($data)) {
118
            return $this->denormalizer->denormalize($data, $type, $format, $context);
119
        }
120
121
        $em = $this->registry->getManagerForClass($type);
122
        if (!$em) {
123
            throw new InvalidArgumentException(sprintf('Could not find entity manager for class %s', $type));
124
        }
125
        /** @var ClassMetadataInfo $classMetadata */
126
        $classMetadata = $em->getClassMetadata($type);
127
128
        // Resource is a draft: nothing to do here anymore
129
        // or User doesn't have draft access: update the original object
130
        if (
131
            null !== $classMetadata->getFieldValue($object, $configuration->associationName) ||
132
            !$this->publishableHelper->isActivePublishedAt($object) ||
133
            !$this->publishableHelper->isGranted()
134
        ) {
135
            return $this->denormalizer->denormalize($data, $type, $format, $context);
136
        }
137
138
        // Any field has been modified: create a draft
139
        $draft = clone $object; // Identifier(s) should be reset from AbstractComponent::__clone method
140
141
        // Add draft object to UnitOfWork
142
        $em->persist($draft);
143
144
        // Empty publishedDate on draft
145
        $classMetadata->setFieldValue($draft, $configuration->fieldName, null);
146
147
        // Set publishedResource on draft
148
        $classMetadata->setFieldValue($draft, $configuration->associationName, $object);
149
150
        // Set draftResource on data
151
        $classMetadata->setFieldValue($object, $configuration->reverseAssociationName, $draft);
152
153
        $context[AbstractNormalizer::OBJECT_TO_POPULATE] = $draft;
154
155
        return $this->denormalizer->denormalize($data, $type, $format, $context);
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161
    public function supportsDenormalization($data, $type, $format = null, array $context = []): bool
162
    {
163
        return !isset($context[self::ALREADY_CALLED]) && $this->publishableHelper->isPublishable($type);
164
    }
165
166
    public function hasCacheableSupportsMethod(): bool
167
    {
168
        return false;
169
    }
170
}
171