Completed
Pull Request — master (#743)
by Asmir
07:41
created

afterVisitingObject()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 8
cts 8
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 4
nop 4
crap 3
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 397
    public function __construct(
57
        MetadataFactoryInterface $metadataFactory,
58
        HandlerRegistryInterface $handlerRegistry,
59
        ObjectConstructorInterface $objectConstructor,
60
        EventDispatcherInterface $dispatcher = null,
61
        ExpressionEvaluatorInterface $expressionEvaluator = null
62
    )
63
    {
64 397
        $this->dispatcher = $dispatcher ?: new EventDispatcher();
65 397
        $this->metadataFactory = $metadataFactory;
66 397
        $this->handlerRegistry = $handlerRegistry;
67 397
        $this->objectConstructor = $objectConstructor;
68 397
        if ($expressionEvaluator) {
69 33
            $this->expressionExclusionStrategy = new ExpressionLanguageExclusionStrategy($expressionEvaluator);
70
        }
71 397
    }
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 165
    public function accept($data, array $type = null, Context $context)
82
    {
83 165
        $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 165
        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 165
        if ($visitor instanceof NullAwareVisitorInterface && $visitor->isNull($data) === true) {
94 7
            $type = array('name' => 'NULL', 'params' => array());
95
        }
96
97 165
        switch ($type['name']) {
98 165
            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 157
            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 151
            case 'int':
105 151
            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 146
            case 'bool':
109 146
            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 136
            case 'double':
113 128
            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 120
            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 103
            case 'resource':
120
                throw new RuntimeException('Resources are not supported in serialized data.');
121
122
            default:
123
124 103
                $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 103
                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 103
                if (null !== $handler = $this->handlerRegistry->getHandler($context->getDirection(), $type['name'], $context->getFormat())) {
138 37
                    $rs = \call_user_func($handler, $visitor, $data, $type, $context);
139 37
                    $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 37
                    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