Completed
Pull Request — 1.x (#6)
by Dorian
01:46
created

SubArrayHydratingHandler   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 181
Duplicated Lines 7.18 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 40
lcom 1
cbo 5
dl 13
loc 181
rs 8.2608
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A getKey() 0 1 1
A setKey() 0 1 1
A getClassName() 0 1 1
A setClassName() 0 1 1
A getHydrator() 0 1 1
A setHydrator() 0 1 1
A getErrorMessage() 0 1 1
A setErrorMessage() 0 1 1
A getSubErrorMessage() 0 1 1
A setSubErrorMessage() 0 1 1
A getValidators() 0 1 1
A setValidators() 0 1 1
A addValidator() 0 1 1
A getGetter() 0 1 1
A setGetter() 0 1 1
A getAssociative() 0 1 1
A setAssociative() 0 1 1
A __construct() 0 11 1
D handle() 3 35 10
C handleSub() 0 35 7
A getSubArray() 0 7 2
A validate() 10 10 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SubArrayHydratingHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SubArrayHydratingHandler, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace MetaHydrator\Handler;
3
4
use MetaHydrator\Exception\HydratingException;
5
use MetaHydrator\Exception\ValidationException;
6
use MetaHydrator\Reflection\Getter;
7
use MetaHydrator\Reflection\GetterInterface;
8
use MetaHydrator\Validator\ValidatorInterface;
9
use Mouf\Hydrator\Hydrator;
10
11
/**
12
 * Class SubArrayHydratingHandler
13
 * @package MetaHydrator\Handler
14
 */
15
class SubArrayHydratingHandler implements HydratingHandlerInterface
16
{
17
    /** @var string */
18
    protected $key;
19
    public function getKey() { return $this->key; }
20
    public function setKey(string $key) { $this->key = $key; }
21
22
    /** @var string */
23
    protected $className;
24
    public function getClassName() { return $this->className; }
25
    public function setClassName(string $className) { $this->className = $className; }
26
27
    /** @var Hydrator */
28
    protected $hydrator;
29
    public function getHydrator() { return $this->hydrator; }
30
    public function setHydrator(Hydrator $hydrator) { $this->hydrator = $hydrator; }
31
32
    /** @var string */
33
    protected $errorMessage;
34
    public function getErrorMessage() { return $this->errorMessage; }
35
    public function setErrorMessage(string $errorMessage) { $this->errorMessage = $errorMessage; }
36
37
    /** @var string */
38
    private $subErrorMessage;
39
    public function getSubErrorMessage() { return $this->subErrorMessage; }
40
    public function setSubErrorMessage($subErrorMessage) { $this->subErrorMessage = $subErrorMessage; }
41
42
    /** @var ValidatorInterface[] */
43
    private $validators;
44
    public function getValidators() { return $this->validators; }
45
    public function setValidators(array $validators) { $this->validators = $validators; }
46
    public function addValidator(ValidatorInterface $validator) { $this->validators[] = $validator; }
47
48
    /** @var GetterInterface */
49
    protected $getter;
50
    public function getGetter() { return $this->getter; }
51
    public function setGetter(GetterInterface $getter) { $this->getter = $getter; }
52
53
    /** @var bool */
54
    private $associative;
55
    public function getAssociative() { return $this->associative; }
56
    public function setAssociative(bool $associative) { $this->associative = $associative; }
57
58
    /**
59
     * SubArrayHydratingHandler constructor.
60
     * @param string $key
61
     * @param string $className
62
     * @param Hydrator $hydrator
63
     * @param ValidatorInterface[] $validators
64
     * @param string $errorMessage
65
     * @param string $subErrorMessage
66
     * @param GetterInterface $getter
67
     * @param bool $associative
68
     */
69
    public function __construct(string $key, string $className, Hydrator $hydrator, array $validators = [], string $errorMessage = "", string $subErrorMessage = "", GetterInterface $getter = null, bool $associative = false)
70
    {
71
        $this->key = $key;
72
        $this->className = $className;
73
        $this->hydrator = $hydrator;
74
        $this->validators = $validators;
75
        $this->errorMessage = $errorMessage;
76
        $this->subErrorMessage = $subErrorMessage;
77
        $this->getter = $getter ?? new Getter(false);
78
        $this->associative = $associative;
79
    }
80
81
    /**
82
     * @param array $data
83
     * @param array $targetData
84
     * @param $object
85
     *
86
     * @throws HydratingException
87
     */
88
    public function handle(array $data, array &$targetData, $object = null)
89
    {
90
        if (!array_key_exists($this->key, $data)) {
91
            if ($object !== null) {
92
                return;
93
            } else {
94
                $subArray = null;
95
            }
96
        } elseif ($data[$this->key] === null) {
97
            $subArray = null;
98 View Code Duplication
        } elseif (!is_array($data[$this->key])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
99
            throw new HydratingException([$this->key => $this->errorMessage]);
100
        } else {
101
            $subArrayData = $data[$this->key];
102
            $subArray = $this->getSubArray($object) ?? null;
103
            $errorsMap = [];
104
            $ok = true;
105
            foreach ($subArrayData as $key => $subData) {
106
                $ok = $this->handleSub($key, $subData, $subArray, $errorsMap) && $ok;
0 ignored issues
show
Bug introduced by
It seems like $subArray defined by $this->getSubArray($object) ?? null on line 102 can also be of type null; however, MetaHydrator\Handler\Sub...ingHandler::handleSub() does only seem to accept array, 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...
107
            }
108
            if (!$ok) {
109
                throw new HydratingException([$this->key => $errorsMap]);
110
            }
111
        }
112
113
        $this->validate($subArray, $object);
114
115
        if ($this->associative) {
116
            $targetData[$this->key] = $subArray;
117
        } elseif ($subArray !== null) {
118
            $targetData[$this->key] = array_values($subArray);
119
        } else {
120
            $targetData[$this->key] = null;
121
        }
122
    }
123
124
    /**
125
     * @param string $key
126
     * @param array|null $data
127
     * @param array $array
128
     * @param array $errorsMap
129
     * @return bool
130
     */
131
    protected function handleSub($key, $data, &$array, &$errorsMap)
132
    {
133
        if ($data === null) {
134
            if (array_key_exists($key, $array)) {
135
                unset($array[$key]);
136
            }
137
            $errorsMap[$key] = null;
138
            return true;
139
        }
140
        if (!is_array($data)) {
141
            $errorsMap[$key] = $this->subErrorMessage;
142
            return false;
143
        }
144
        if (isset($array[$key])) {
145
            $subObject = $array[$key];
146
            try {
147
                $this->hydrator->hydrateObject($data, $subObject);
148
                $errorsMap[$key] = null;
149
                return true;
150
            } catch (HydratingException $exception) {
151
                $errorsMap[$key] = $exception->getErrorsMap();
152
                return false;
153
            }
154
        } else {
155
            try {
156
                $subObject = $this->hydrator->hydrateNewObject($data, $this->className);
157
                $array[$key] = $subObject;
158
                $errorsMap[$key] = null;
159
                return true;
160
            } catch (HydratingException $exception) {
161
                $errorsMap[$key] = $exception->getErrorsMap();
162
                return false;
163
            }
164
        }
165
    }
166
167
    /**
168
     * @param $object
169
     * @return array
170
     */
171
    protected function getSubArray($object)
172
    {
173
        if ($object === null) {
174
            return [];
175
        }
176
        return $this->getter->get($object, $this->key);
177
    }
178
179
    /**
180
     * @param mixed $parsedValue
181
     * @param mixed $contextObject
182
     *
183
     * @throws HydratingException
184
     */
185 View Code Duplication
    private function validate($parsedValue, $contextObject = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
186
    {
187
        try {
188
            foreach ($this->validators as $validator) {
189
                $validator->validate($parsedValue, $contextObject);
190
            }
191
        } catch (ValidationException $exception) {
192
            throw new HydratingException([ $this->key => $exception->getInnerError() ]);
193
        }
194
    }
195
}
196