Passed
Pull Request — feature/publishable (#43)
by Daniel
06:17
created

PublishableNormalizer   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 128
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 22
eloc 53
c 2
b 0
f 0
dl 0
loc 128
ccs 0
cts 52
cp 0
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A supportsNormalization() 0 6 4
A normalize() 0 10 2
A supportsDenormalization() 0 3 2
C denormalize() 0 76 12
A hasCacheableSupportsMethod() 0 3 1
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
    /**
69
     * {@inheritdoc}
70
     */
71
    public function denormalize($data, $type, $format = null, array $context = [])
72
    {
73
        $context[self::ALREADY_CALLED] = true;
74
        $configuration = $this->publishableHelper->getConfiguration($type);
75
76
        // It's not possible to change the publishedResource and draftResource properties
77
        unset($data[$configuration->associationName], $data[$configuration->reverseAssociationName]);
78
79
        // User doesn't have draft access: cannot set or change the publication date
80
        if (!$this->publishableHelper->isGranted()) {
81
            unset($data[$configuration->fieldName]);
82
        }
83
84
        // It's a new object
85
        if (!isset($context[AbstractNormalizer::OBJECT_TO_POPULATE])) {
86
            // User doesn't have draft access: force publication date
87
            if (!$this->publishableHelper->isGranted()) {
88
                $data[$configuration->fieldName] = date('Y-m-d H:i:s');
89
            }
90
91
            return $this->denormalizer->denormalize($data, $type, $format, $context);
92
        }
93
94
        $object = $context[AbstractNormalizer::OBJECT_TO_POPULATE];
95
        if (isset($data[$configuration->fieldName])) {
96
            $publicationDate = new \DateTimeImmutable($data[$configuration->fieldName]);
97
98
            // User changed the publication date with an earlier one on a published resource: ignore it
99
            if (
100
                $this->publishableHelper->isPublished($object) &&
101
                !empty($publicationDate) &&
102
                new \DateTimeImmutable() >= $publicationDate
103
            ) {
104
                unset($data[$configuration->fieldName]);
105
            }
106
        }
107
108
        // No field has been updated: nothing to do here anymore
109
        if (empty($data)) {
110
            return $this->denormalizer->denormalize($data, $type, $format, $context);
111
        }
112
113
        $em = $this->registry->getManagerForClass($type);
114
        $classMetadata = $em->getClassMetadata($type);
115
116
        // Resource is a draft: nothing to do here anymore
117
        if (
118
            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

118
            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...
119
            !$this->publishableHelper->isPublished($object)
120
        ) {
121
            return $this->denormalizer->denormalize($data, $type, $format, $context);
122
        }
123
124
        // User doesn't have draft access: update the original object
125
        if (!$this->publishableHelper->isGranted()) {
126
            return $this->denormalizer->denormalize($data, $type, $format, $context);
127
        }
128
129
        // Any field has been modified: create a draft
130
        $draft = clone $object; // Identifier(s) should be reset from AbstractComponent::__clone method
131
132
        // Add draft object to UnitOfWork
133
        $em->persist($draft);
134
135
        // Empty publishedDate on draft
136
        $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

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