Passed
Push — feature/publishable ( 878fc4...f86055 )
by Daniel
05:21
created

PublishableNormalizer::denormalize()   C

Complexity

Conditions 13
Paths 29

Size

Total Lines 79
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 36
c 1
b 0
f 0
dl 0
loc 79
ccs 0
cts 35
cp 0
rs 6.6166
cc 13
nc 29
nop 4
crap 182

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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