ObjectNormalizer::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 5
1
<?php
2
/*
3
 * This file is part of AppBundle the package.
4
 *
5
 * (c) Ruslan Muriev <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace RonteLtd\JsonApiBundle\Serializer\Normalizer;
12
13
use RonteLtd\JsonApiBundle\Annotation\Attribute;
14
use RonteLtd\JsonApiBundle\Annotation\Relationship;
15
use RonteLtd\JsonApiBundle\Annotation\ObjectNormalizer as ObjectNormalizerAnnotation;
16
use RonteLtd\JsonApiBundle\Serializer\Exception\LogicException;
17
use RonteLtd\JsonApiBundle\Serializer\Mapping\AttributeMetadata;
18
use RonteLtd\JsonApiBundle\Serializer\Mapping\AttributeMetadataInterface;
19
use RonteLtd\JsonApiBundle\Serializer\Mapping\ClassAnnotationInterface;
20
use RonteLtd\JsonApiBundle\Serializer\Mapping\ClassMetadataInterface;
21
use RonteLtd\JsonApiBundle\Serializer\Mapping\Factory\MetadataFactoryInterface;
22
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
23
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
24
use Symfony\Component\Serializer\Exception\CircularReferenceException;
25
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
26
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
27
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer as BaseObjectNormalizer;
28
29
/**
30
 * Class JsonApiNormalizer
31
 *
32
 * @package RonteLtd\JsonApiBundle\Serializer\Normalizer
33
 * @author Ruslan Muriev <[email protected]>
34
 */
35
class ObjectNormalizer extends BaseObjectNormalizer
36
{
37
    /**
38
     * @var MetadataFactoryInterface
39
     */
40
    protected $metadataFactory;
41
42
    /**
43
     * ObjectNormalizer constructor.
44
     *
45
     * @param MetadataFactoryInterface $metadataFactory
46
     * @param ClassMetadataFactoryInterface|null $classMetadataFactory
47
     * @param NameConverterInterface|null $nameConverter
48
     * @param PropertyAccessorInterface|null $propertyAccessor
49
     * @param PropertyTypeExtractorInterface|null $propertyTypeExtractor
50
     */
51
    public function __construct(MetadataFactoryInterface $metadataFactory, ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null)
52
    {
53
        parent::__construct($classMetadataFactory, $nameConverter, $propertyAccessor, $propertyTypeExtractor);
54
55
        $this->metadataFactory = $metadataFactory;
56
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61
    public function supportsNormalization($data, $format = null)
62
    {
63
        if (parent::supportsNormalization($data) === false) {
64
            return false;
65
        }
66
67
        return (boolean) $this->getNormalizerClassAnnotation(
68
            $this->metadataFactory->getMetadataFor($data)
69
        );
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     *
75
     * @throws CircularReferenceException
76
     */
77
    public function normalize($object, $format = null, array $context = array())
78
    {
79
        $this->setCircularReferenceHandler(function ($object) {
0 ignored issues
show
Unused Code introduced by
The parameter $object is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
80
            return [];
81
        });
82
83
        $data = parent::normalize($object, $format, $context);
84
85
        if (!is_array($data)) {
86
            return $data;
87
        }
88
89
        // Get all metadata
90
        $classMetadata = $this->metadataFactory->getMetadataFor($object);
91
        $attributesMetadata = $classMetadata->getAttributesMetadata();
92
        $classAnnotation = $this->getNormalizerClassAnnotation($classMetadata);
93
94
        $jsonApi = [
95
            'type' => $classAnnotation->getName(),
96
            'id' => $object->getId(),
97
            'attributes' => [],
98
            'relationships' => []
99
        ];
100
101
        foreach ($data as $atrName => $atrVal) {
102
            $attrMetadataName = $this->nameConverter ? $this->nameConverter->denormalize($atrName) : $atrName;
103
            if (!array_key_exists($attrMetadataName, $attributesMetadata)) {
104
                continue;
105
            }
106
107
            $attributeMetadata = $attributesMetadata[$attrMetadataName];
108
            if (!$attributeMetadata instanceof AttributeMetadataInterface) {
109
                continue;
110
            }
111
112
            $attribute = $attributeMetadata->getAttribute();
113
            $relationship = $attributeMetadata->getRelationship();
114
115
            if ($attribute instanceof Attribute) {
116
                $name = $attribute->getName() ?: strtolower($atrName);
117
118
                $jsonApi['attributes'][$name] = $atrVal;
119
            }
120
121
            if ($relationship instanceof Relationship) {
122
                $name = $relationship->getName() ?: strtolower($atrName);
123
124
                $jsonApi['relationships'][$name]["data"] = $atrVal;
125
            }
126
        }
127
128
        return $jsonApi;
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134
    public function supportsDenormalization($data, $type, $format = null)
135
    {
136
        return false;
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142
    public function denormalize($data, $class, $format = null, array $context = array())
143
    {
144
        throw new LogicException(sprintf('Cannot denormalize with "%s".', \JsonSerializable::class));
145
    }
146
147
    /**
148
     * @param ClassMetadataInterface $classMetadata
149
     * @return ObjectNormalizerAnnotation bool
150
     */
151
    protected function getNormalizerClassAnnotation(ClassMetadataInterface $classMetadata)
152
    {
153
        foreach ($classMetadata->getClassAnnotations() as $annotation) {
154
            if ($annotation instanceof ObjectNormalizerAnnotation) {
155
                return $annotation;
156
            }
157
        }
158
159
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by RonteLtd\JsonApiBundle\S...rmalizerClassAnnotation of type RonteLtd\JsonApiBundle\Annotation\ObjectNormalizer.

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...
160
    }
161
}