Completed
Pull Request — master (#743)
by Asmir
03:52
created

DeserializationGraphNavigator::accept()   D

Complexity

Conditions 26
Paths 77

Size

Total Lines 111
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 48
CRAP Score 29.4638

Importance

Changes 0
Metric Value
dl 0
loc 111
ccs 48
cts 58
cp 0.8276
rs 4.5382
c 0
b 0
f 0
cc 26
eloc 59
nc 77
nop 3
crap 29.4638

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
 * Copyright 2016 Johannes M. Schmitt <[email protected]>
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace JMS\Serializer;
20
21
use JMS\Serializer\Construction\ObjectConstructorInterface;
22
use JMS\Serializer\EventDispatcher\EventDispatcher;
23
use JMS\Serializer\EventDispatcher\EventDispatcherInterface;
24
use JMS\Serializer\EventDispatcher\ObjectEvent;
25
use JMS\Serializer\EventDispatcher\PreDeserializeEvent;
26
use JMS\Serializer\EventDispatcher\PreSerializeEvent;
27
use JMS\Serializer\Exception\ExpressionLanguageRequiredException;
28
use JMS\Serializer\Exception\InvalidArgumentException;
29
use JMS\Serializer\Exception\RuntimeException;
30
use JMS\Serializer\Exclusion\ExpressionLanguageExclusionStrategy;
31
use JMS\Serializer\Expression\ExpressionEvaluatorInterface;
32
use JMS\Serializer\Handler\HandlerRegistryInterface;
33
use JMS\Serializer\Metadata\ClassMetadata;
34
use Metadata\MetadataFactoryInterface;
35
36
/**
37
 * Handles traversal along the object graph.
38
 *
39
 * This class handles traversal along the graph, and calls different methods
40
 * on visitors, or custom handlers to process its nodes.
41
 *
42
 * @author Johannes M. Schmitt <[email protected]>
43
 */
44
final class DeserializationGraphNavigator implements GraphNavigatorInterface
45
{
46
    /**
47
     * @var ExpressionLanguageExclusionStrategy
48
     */
49
    private $expressionExclusionStrategy;
50
51
    private $dispatcher;
52
    private $metadataFactory;
53
    private $handlerRegistry;
54
    private $objectConstructor;
55
56 398
    public function __construct(
57
        MetadataFactoryInterface $metadataFactory,
58
        HandlerRegistryInterface $handlerRegistry,
59
        ObjectConstructorInterface $objectConstructor,
60
        EventDispatcherInterface $dispatcher = null,
61
        ExpressionEvaluatorInterface $expressionEvaluator = null
62
    )
63
    {
64 398
        $this->dispatcher = $dispatcher ?: new EventDispatcher();
65 398
        $this->metadataFactory = $metadataFactory;
66 398
        $this->handlerRegistry = $handlerRegistry;
67 398
        $this->objectConstructor = $objectConstructor;
68 398
        if ($expressionEvaluator) {
69 33
            $this->expressionExclusionStrategy = new ExpressionLanguageExclusionStrategy($expressionEvaluator);
70
        }
71 398
    }
72
73
    /**
74
     * Called for each node of the graph that is being traversed.
75
     *
76
     * @param mixed $data the data depends on the direction, and type of visitor
77
     * @param null|array $type array has the format ["name" => string, "params" => array]
78
     * @param Context $context
79
     * @return mixed the return value depends on the direction, and type of visitor
80
     */
81 166
    public function accept($data, array $type = null, Context $context)
82
    {
83 166
        $visitor = $context->getVisitor();
84
85
        // If the type was not given, we infer the most specific type from the
86
        // input data in serialization mode.
87 166
        if (null === $type) {
88
89
            throw new RuntimeException('The type must be given for all properties when deserializing.');
90
        }
91
        // Sometimes data can convey null but is not of a null type.
92
        // Visitors can have the power to add this custom null evaluation
93 166
        if ($visitor instanceof NullAwareVisitorInterface && $visitor->isNull($data) === true) {
94 7
            $type = array('name' => 'NULL', 'params' => array());
95
        }
96
97 166
        switch ($type['name']) {
98 166
            case 'NULL':
99 9
                return $visitor->visitNull($data, $type, $context);
0 ignored issues
show
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\SerializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\DeserializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
100
101 158
            case 'string':
102 65
                return $visitor->visitString($data, $type, $context);
0 ignored issues
show
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\SerializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\DeserializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
103
104 152
            case 'int':
105 152
            case 'integer':
106 24
                return $visitor->visitInteger($data, $type, $context);
0 ignored issues
show
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\SerializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\DeserializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
107
108 147
            case 'bool':
109 147
            case 'boolean':
110 18
                return $visitor->visitBoolean($data, $type, $context);
0 ignored issues
show
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\SerializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\DeserializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
111
112 137
            case 'double':
113 129
            case 'float':
114 35
                return $visitor->visitDouble($data, $type, $context);
0 ignored issues
show
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\SerializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\DeserializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
115
116 121
            case 'array':
117 51
                return $visitor->visitArray($data, $type, $context);
0 ignored issues
show
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\SerializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\DeserializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
118
119 104
            case 'resource':
120
                throw new RuntimeException('Resources are not supported in serialized data.');
121
122
            default:
123
124 104
                $context->increaseDepth();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class JMS\Serializer\Context as the method increaseDepth() does only exist in the following sub-classes of JMS\Serializer\Context: JMS\Serializer\DeserializationContext. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
125
126
                // Trigger pre-serialization callbacks, and listeners if they exist.
127
                // Dispatch pre-serialization event before handling data to have ability change type in listener
128 104
                if ($this->dispatcher->hasListeners('serializer.pre_deserialize', $type['name'], $context->getFormat())) {
129
                    $this->dispatcher->dispatch('serializer.pre_deserialize', $type['name'], $context->getFormat(), $event = new PreDeserializeEvent($context, $data, $type));
0 ignored issues
show
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\DeserializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
130
                    $type = $event->getType();
131
                    $data = $event->getData();
132
                }
133
134
                // First, try whether a custom handler exists for the given type. This is done
135
                // before loading metadata because the type name might not be a class, but
136
                // could also simply be an artifical type.
137 104
                if (null !== $handler = $this->handlerRegistry->getHandler($context->getDirection(), $type['name'], $context->getFormat())) {
138 38
                    $rs = \call_user_func($handler, $visitor, $data, $type, $context);
139 38
                    $context->decreaseDepth();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class JMS\Serializer\Context as the method decreaseDepth() does only exist in the following sub-classes of JMS\Serializer\Context: JMS\Serializer\DeserializationContext. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
140
141 38
                    return $rs;
142
                }
143
144 89
                $exclusionStrategy = $context->getExclusionStrategy();
145
146
                /** @var $metadata ClassMetadata */
147 89
                $metadata = $this->metadataFactory->getMetadataForClass($type['name']);
148
149 89
                if ($metadata->usingExpression && !$this->expressionExclusionStrategy) {
150
                    throw new ExpressionLanguageRequiredException("To use conditional exclude/expose in {$metadata->name} you must configure the expression language.");
151
                }
152
153 89
                if (!empty($metadata->discriminatorMap) && $type['name'] === $metadata->discriminatorBaseClass) {
154 17
                    $metadata = $this->resolveMetadata($data, $metadata);
155
                }
156
157 86
                if ($exclusionStrategy->shouldSkipClass($metadata, $context)) {
0 ignored issues
show
Documentation introduced by
$metadata is of type object<Metadata\ClassHie...ableClassMetadata>|null, but the function expects a object<JMS\Serializer\Metadata\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
158
                    $context->decreaseDepth();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class JMS\Serializer\Context as the method decreaseDepth() does only exist in the following sub-classes of JMS\Serializer\Context: JMS\Serializer\DeserializationContext. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
159
160
                    return null;
161
                }
162
163 86
                $context->pushClassMetadata($metadata);
0 ignored issues
show
Documentation introduced by
$metadata is of type object<Metadata\ClassHie...ableClassMetadata>|null, but the function expects a object<JMS\Serializer\Metadata\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
164
165 86
                $object = $this->objectConstructor->construct($visitor, $metadata, $data, $type, $context);
0 ignored issues
show
Bug introduced by
It seems like $visitor defined by $context->getVisitor() on line 83 can also be of type object<JMS\Serializer\Se...zationVisitorInterface>; however, JMS\Serializer\Construct...rInterface::construct() does only seem to accept object<JMS\Serializer\De...zationVisitorInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Documentation introduced by
$metadata is of type object<Metadata\ClassHie...ableClassMetadata>|null, but the function expects a object<JMS\Serializer\Metadata\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\DeserializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
166
167 86
                $visitor->startVisitingObject($metadata, $object, $type, $context);
0 ignored issues
show
Documentation introduced by
$metadata is of type object<Metadata\ClassHie...ableClassMetadata>|null, but the function expects a object<JMS\Serializer\Metadata\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\SerializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\DeserializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
168 86
                foreach ($metadata->propertyMetadata as $propertyMetadata) {
169 86
                    if ($exclusionStrategy->shouldSkipProperty($propertyMetadata, $context)) {
170
                        continue;
171
                    }
172
173 86
                    if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipProperty($propertyMetadata, $context)) {
174
                        continue;
175
                    }
176
177 86
                    if ($propertyMetadata->readOnly) {
178 23
                        continue;
179
                    }
180
181 83
                    $context->pushPropertyMetadata($propertyMetadata);
182 83
                    $visitor->visitProperty($propertyMetadata, $data, $context);
0 ignored issues
show
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\SerializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\DeserializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
183 83
                    $context->popPropertyMetadata();
184
                }
185
186 85
                $rs = $visitor->endVisitingObject($metadata, $data, $type, $context);
0 ignored issues
show
Documentation introduced by
$metadata is of type object<Metadata\ClassHie...ableClassMetadata>|null, but the function expects a object<JMS\Serializer\Metadata\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\SerializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Compatibility introduced by
$context of type object<JMS\Serializer\Context> is not a sub-type of object<JMS\Serializer\DeserializationContext>. It seems like you assume a child class of the class JMS\Serializer\Context to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
187 85
                $this->afterVisitingObject($metadata, $rs, $type, $context);
0 ignored issues
show
Documentation introduced by
$metadata is of type object<Metadata\ClassHie...ableClassMetadata>|null, but the function expects a object<JMS\Serializer\Metadata\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
188
189 85
                return $rs;
190
        }
191
    }
192
193 17
    private function resolveMetadata($data, ClassMetadata $metadata)
194
    {
195
        switch (true) {
196 17
            case \is_array($data) && isset($data[$metadata->discriminatorFieldName]):
197 8
                $typeValue = (string)$data[$metadata->discriminatorFieldName];
198 8
                break;
199
200
            // Check XML attribute for discriminatorFieldName
201 9
            case \is_object($data) && $metadata->xmlDiscriminatorAttribute && isset($data[$metadata->discriminatorFieldName]):
202 1
                $typeValue = (string)$data[$metadata->discriminatorFieldName];
203 1
                break;
204
205
            // Check XML element with namespace for discriminatorFieldName
206 8
            case \is_object($data) && !$metadata->xmlDiscriminatorAttribute && null !== $metadata->xmlDiscriminatorNamespace && isset($data->children($metadata->xmlDiscriminatorNamespace)->{$metadata->discriminatorFieldName}):
207 1
                $typeValue = (string)$data->children($metadata->xmlDiscriminatorNamespace)->{$metadata->discriminatorFieldName};
208 1
                break;
209
210
            // Check XML element for discriminatorFieldName
211 7
            case \is_object($data) && isset($data->{$metadata->discriminatorFieldName}):
212 4
                $typeValue = (string)$data->{$metadata->discriminatorFieldName};
213 4
                break;
214
215
            default:
216 3
                throw new \LogicException(sprintf(
217 3
                    'The discriminator field name "%s" for base-class "%s" was not found in input data.',
218 3
                    $metadata->discriminatorFieldName,
219 3
                    $metadata->name
220
                ));
221
        }
222
223 14
        if (!isset($metadata->discriminatorMap[$typeValue])) {
224
            throw new \LogicException(sprintf(
225
                'The type value "%s" does not exist in the discriminator map of class "%s". Available types: %s',
226
                $typeValue,
227
                $metadata->name,
228
                implode(', ', array_keys($metadata->discriminatorMap))
229
            ));
230
        }
231
232 14
        return $this->metadataFactory->getMetadataForClass($metadata->discriminatorMap[$typeValue]);
233
    }
234
235 85
    private function afterVisitingObject(ClassMetadata $metadata, $object, array $type, Context $context)
236
    {
237 85
        $context->decreaseDepth();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class JMS\Serializer\Context as the method decreaseDepth() does only exist in the following sub-classes of JMS\Serializer\Context: JMS\Serializer\DeserializationContext. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
238 85
        $context->popClassMetadata();
239
240 85
        foreach ($metadata->postDeserializeMethods as $method) {
241 6
            $method->invoke($object);
242
        }
243
244 85
        if ($this->dispatcher->hasListeners('serializer.post_deserialize', $metadata->name, $context->getFormat())) {
245 1
            $this->dispatcher->dispatch('serializer.post_deserialize', $metadata->name, $context->getFormat(), new ObjectEvent($context, $object, $type));
246
        }
247 85
    }
248
}
249