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

SerializationGraphNavigator::__construct()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 7
cts 7
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 10
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 SerializationGraphNavigator implements GraphNavigatorInterface
45
{
46
    /**
47
     * @var ExpressionLanguageExclusionStrategy
48
     */
49
    private $expressionExclusionStrategy;
50
51
    private $dispatcher;
52
    private $metadataFactory;
53
    private $handlerRegistry;
54
55 397
    public function __construct(
56
        MetadataFactoryInterface $metadataFactory,
57
        HandlerRegistryInterface $handlerRegistry,
58
        EventDispatcherInterface $dispatcher = null,
59
        ExpressionEvaluatorInterface $expressionEvaluator = null
60
    )
61
    {
62 397
        $this->dispatcher = $dispatcher ?: new EventDispatcher();
63 397
        $this->metadataFactory = $metadataFactory;
64 397
        $this->handlerRegistry = $handlerRegistry;
65 397
        if ($expressionEvaluator) {
66 33
            $this->expressionExclusionStrategy = new ExpressionLanguageExclusionStrategy($expressionEvaluator);
67
        }
68 397
    }
69
70
    /**
71
     * Called for each node of the graph that is being traversed.
72
     *
73
     * @param mixed $data the data depends on the direction, and type of visitor
74
     * @param null|array $type array has the format ["name" => string, "params" => array]
75
     * @param Context $context
76
     * @return mixed the return value depends on the direction, and type of visitor
77
     */
78 348
    public function accept($data, array $type = null, Context $context)
79
    {
80 348
        $visitor = $context->getVisitor();
81
82
        // If the type was not given, we infer the most specific type from the
83
        // input data in serialization mode.
84 348
        if (null === $type) {
85
86 319
            $typeName = \gettype($data);
87 319
            if ('object' === $typeName) {
88 230
                $typeName = \get_class($data);
89
            }
90
91 319
            $type = array('name' => $typeName, 'params' => array());
92
        }
93
        // If the data is null, we have to force the type to null regardless of the input in order to
94
        // guarantee correct handling of null values, and not have any internal auto-casting behavior.
95 200
        else if (null === $data) {
96 25
            $type = array('name' => 'NULL', 'params' => array());
97
        }
98
        // Sometimes data can convey null but is not of a null type.
99
        // Visitors can have the power to add this custom null evaluation
100 348
        if ($visitor instanceof NullAwareVisitorInterface && $visitor->isNull($data) === true) {
101
            $type = array('name' => 'NULL', 'params' => array());
102
        }
103
104 348
        switch ($type['name']) {
105 348
            case 'NULL':
106 53
                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...
107
108 330
            case 'string':
109 203
                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...
110
111 325
            case 'int':
112 325
            case 'integer':
113 63
                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...
114
115 321
            case 'bool':
116 321
            case 'boolean':
117 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...
118
119 314
            case 'double':
120 301
            case 'float':
121 29
                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...
122
123 301
            case 'array':
124 134
                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...
125
126 238
            case 'resource':
127 1
                $msg = 'Resources are not supported in serialized data.';
128 1
                if (null !== $path = $context->getPath()) {
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 getPath() does only exist in the following sub-classes of JMS\Serializer\Context: JMS\Serializer\SerializationContext. 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...
129
                    $msg .= ' Path: ' . $path;
130
                }
131
132 1
                throw new RuntimeException($msg);
133
134
            default:
135
136 237
                if (null !== $data) {
137 237
                    if ($context->isVisiting($data)) {
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 isVisiting() does only exist in the following sub-classes of JMS\Serializer\Context: JMS\Serializer\SerializationContext. 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...
138 3
                        return null;
139
                    }
140 237
                    $context->startVisiting($data);
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 startVisiting() does only exist in the following sub-classes of JMS\Serializer\Context: JMS\Serializer\SerializationContext. 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...
141
                }
142
143
                // If we're serializing a polymorphic type, then we'll be interested in the
144
                // metadata for the actual type of the object, not the base class.
145 237
                if (class_exists($type['name'], false) || interface_exists($type['name'], false)) {
146 237
                    if (is_subclass_of($data, $type['name'], false)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $type['name'] can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
147 6
                        $type = array('name' => \get_class($data), 'params' => array());
148
                    }
149
                }
150
151
                // Trigger pre-serialization callbacks, and listeners if they exist.
152
                // Dispatch pre-serialization event before handling data to have ability change type in listener
153 237
                if ($this->dispatcher->hasListeners('serializer.pre_serialize', $type['name'], $context->getFormat())) {
154 230
                    $this->dispatcher->dispatch('serializer.pre_serialize', $type['name'], $context->getFormat(), $event = new PreSerializeEvent($context, $data, $type));
155 230
                    $type = $event->getType();
156
                }
157
158
                // First, try whether a custom handler exists for the given type. This is done
159
                // before loading metadata because the type name might not be a class, but
160
                // could also simply be an artifical type.
161 237
                if (null !== $handler = $this->handlerRegistry->getHandler($context->getDirection(), $type['name'], $context->getFormat())) {
162 58
                    $rs = \call_user_func($handler, $visitor, $data, $type, $context);
163 58
                    $context->stopVisiting($data);
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 stopVisiting() does only exist in the following sub-classes of JMS\Serializer\Context: JMS\Serializer\SerializationContext. 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...
164
165 58
                    return $rs;
166
                }
167
168 212
                $exclusionStrategy = $context->getExclusionStrategy();
169
170
                /** @var $metadata ClassMetadata */
171 212
                $metadata = $this->metadataFactory->getMetadataForClass($type['name']);
172
173 209
                if ($metadata->usingExpression && !$this->expressionExclusionStrategy) {
174 3
                    throw new ExpressionLanguageRequiredException("To use conditional exclude/expose in {$metadata->name} you must configure the expression language.");
175
                }
176
177 206
                if ($exclusionStrategy->shouldSkipClass($metadata, $context)) {
178 8
                    $context->stopVisiting($data);
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 stopVisiting() does only exist in the following sub-classes of JMS\Serializer\Context: JMS\Serializer\SerializationContext. 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...
179
180 8
                    return null;
181
                }
182
183 206
                $context->pushClassMetadata($metadata);
184
185 206
                foreach ($metadata->preSerializeMethods as $method) {
186 3
                    $method->invoke($data);
187
                }
188
189 206
                $object = $data;
190 206
                $visitor->startVisitingObject($metadata, $object, $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...
191 206
                foreach ($metadata->propertyMetadata as $propertyMetadata) {
192 205
                    if ($exclusionStrategy->shouldSkipProperty($propertyMetadata, $context)) {
193 14
                        continue;
194
                    }
195
196 205
                    if (null !== $this->expressionExclusionStrategy && $this->expressionExclusionStrategy->shouldSkipProperty($propertyMetadata, $context)) {
197 23
                        continue;
198
                    }
199
200 205
                    $context->pushPropertyMetadata($propertyMetadata);
201 205
                    $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...
202 201
                    $context->popPropertyMetadata();
203
                }
204
205 201
                $this->afterVisitingObject($metadata, $data, $type, $context);
206
207 201
                return $visitor->endVisitingObject($metadata, $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...
208
        }
209
    }
210
211 201
    private function afterVisitingObject(ClassMetadata $metadata, $object, array $type, Context $context)
212
    {
213 201
        $context->stopVisiting($object);
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 stopVisiting() does only exist in the following sub-classes of JMS\Serializer\Context: JMS\Serializer\SerializationContext. 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...
214 201
        $context->popClassMetadata();
215
216 201
        foreach ($metadata->postSerializeMethods as $method) {
217 3
            $method->invoke($object);
218
        }
219
220 201
        if ($this->dispatcher->hasListeners('serializer.post_serialize', $metadata->name, $context->getFormat())) {
221 2
            $this->dispatcher->dispatch('serializer.post_serialize', $metadata->name, $context->getFormat(), new ObjectEvent($context, $object, $type));
222
        }
223 201
    }
224
}
225