Completed
Pull Request — master (#143)
by Alexander
05:04
created

Model::toJson()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
/**
3
 * Yandex PHP Library
4
 *
5
 * @copyright NIX Solutions Ltd.
6
 * @link https://github.com/nixsolutions/yandex-php-library
7
 */
8
9
/**
10
 * @namespace
11
 */
12
namespace Yandex\Common;
13
14
/**
15
 * Class Model
16
 * @package Yandex\Common
17
 */
18
abstract class Model
19
{
20
    protected $mappingClasses = [];
21
22
    /**
23
     * Contains property name mappings.
24
     *
25
     * [
26
     *  'data_array_property1' => 'objectProperty1',
27
     *  'data_array_property2' => 'objectProperty2',
28
     * ]
29
     *
30
     * Data array property uses as keys
31
     * because there is can be more then one rule per object property
32
     *
33
     * f.g. $data['nmodels'] and ['modelsnum'] should map in modelsCount property.
34
     * Otherwise not unique array keys cause remapping of properties.
35
     *
36
     * @var array
37
     */
38
    protected $propNameMap = [];
39
40
    /**
41
     * Constructor
42
     *
43
     * @param array $data
44
     */
45 98
    public function __construct($data = [])
46
    {
47 98
        $this->fromArray($data);
48 98
    }
49
50
    /**
51
     * Set from array
52
     *
53
     * @param array $data
54
     * @return $this
55
     */
56 96
    public function fromArray($data)
57
    {
58 96
        foreach ($data as $key => $val) {
59 68
            if (is_int($key)) {
60 47
                if (method_exists($this, "add")) {
61 47
                    $this->add($val);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Yandex\Common\Model as the method add() does only exist in the following sub-classes of Yandex\Common\Model: Yandex\DataSync\Models\Database\Delta\RecordFields, Yandex\DataSync\Models\Database\Delta\Records, Yandex\DataSync\Models\Database\Deltas, Yandex\DataSync\Models\Databases, Yandex\Market\Content\Models\Base\Models, Yandex\Market\Content\Models\Categories, Yandex\Market\Content\Models\Children, Yandex\Market\Content\Models\Comments, Yandex\Market\Content\Models\Contras, Yandex\Market\Content\Models\DeliveryMethods, Yandex\Market\Content\Models\Filters, Yandex\Market\Content\Models\GeoRegions, Yandex\Market\Content\Models\ModelOpinions, Yandex\Market\Content\Models\ModelVisualPhotos, Yandex\Market\Content\Models\OfferPhotos, Yandex\Market\Content\Models\Offers, Yandex\Market\Content\Models\Options, Yandex\Market\Content\Models\Outlets, Yandex\Market\Content\Models\Photos, Yandex\Market\Content\Models\Pros, Yandex\Market\Content\Models\Reviews, Yandex\Market\Content\Models\Schedules, Yandex\Market\Content\Models\SearchResults, Yandex\Market\Content\Models\ShopOpinions, Yandex\Market\Content\Models\Shops, Yandex\Market\Content\Models\Vendors, Yandex\Market\Partner\Models\Campaigns, Yandex\Market\Partner\Models\DeliveryOptions, Yandex\Market\Partner\Models\Items, Yandex\Market\Partner\Models\Orders, Yandex\Market\Partner\Models\Outlets, Yandex\Market\Partner\Models\StateReasons, Yandex\Metrica\Analytics\Models\ColumnHeaders, Yandex\Metrica\Management\Models\Accounts, Yandex\Metrica\Management\Models\Conditions, Yandex\Metrica\Management\Models\Counters, Yandex\Metrica\Management\Models\Delegates, Yandex\Metrica\Management\Models\Filters, Yandex\Metrica\Management\Models\Goals, Yandex\Metrica\Management\Models\Grants, Yandex\Metrica\Management\Models\Operations, Yandex\Metrica\Stat\Models\ComparisonData, Yandex\Metrica\Stat\Models\Data, Yandex\Metrica\Stat\Models\Dimensions, Yandex\Metrica\Stat\Models\DrillDownComparisonData, Yandex\Metrica\Stat\Models\DrillDownData. 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...
62 47
                }
63 47
            }
64
65 68
            $propertyName = $key;
66 68
            $ourPropertyName = array_search($propertyName, $this->propNameMap);
67
68 68
            if ($ourPropertyName && isset($data[$ourPropertyName])) {
69
                $propertyName = $ourPropertyName;
70
            }
71
72 68
            if (!empty($this->propNameMap)) {
73 52
                if (array_key_exists($key, $this->propNameMap)) {
74 47
                    $propertyName = $this->propNameMap[$key];
75 47
                }
76 52
            }
77
78 68
            if (property_exists($this, $propertyName)) {
79 65
                if (isset($this->mappingClasses[$propertyName])) {
80 55
                    $this->{$propertyName} = new $this->mappingClasses[$propertyName]($val);
81 55
                } else {
82 65
                    $this->{$propertyName} = $val;
83
                }
84 65
            }
85 96
        }
86 96
        return $this;
87
    }
88
89
    /**
90
     * Set from json
91
     *
92
     * @param string $json
93
     * @return $this
94
     */
95 1
    public function fromJson($json)
96
    {
97 1
        $this->fromArray(json_decode($json, true));
98 1
        return $this;
99
    }
100
101
    /**
102
     * Get array from object
103
     *
104
     * @return array
105
     */
106 21
    public function toArray()
107
    {
108 21
        return $this->toArrayRecursive($this);
109
    }
110
111
    /**
112
     * Get array from object
113
     *
114
     * @return string
115
     */
116 1
    public function toJson()
117
    {
118 1
        return json_encode($this->toArrayRecursive($this));
119
    }
120
121
    /**
122
     * Get array from object
123
     *
124
     * @param array|object $data
125
     * @return array
126
     */
127 19
    protected function toArrayRecursive($data)
128
    {
129 19
        if (is_array($data) || is_object($data)) {
130 19
            $result = [];
131 19
            foreach ($data as $key => $value) {
132 19
                if ($key === "mappingClasses" || $key === "propNameMap") {
133 19
                    continue;
134
                }
135 19
                $propNameMap = $key;
136 19
                $obj         = $this;
137 19
                if (is_object($data)) {
138 19
                    $obj = $data;
139 19
                }
140
141 19
                if (property_exists($obj, $propNameMap)) {
142 19
                    $ourPropertyName = array_search($propNameMap, $obj->propNameMap);
143
144 19
                    if ($ourPropertyName) {
145 16
                        $propNameMap = $ourPropertyName;
146 16
                    }
147 19
                }
148
149 19
                if (is_object($value) && method_exists($value, "getAll")) {
150 6
                    if (method_exists($obj, 'toArrayRecursive')) {
151 6
                        $result[$propNameMap] = $obj->toArrayRecursive($value->getAll());
152 6
                    }
153 19
                } elseif ($value !== null) {
154 10
                    if (method_exists($obj, 'toArrayRecursive')) {
155 10
                        $result[$propNameMap] = $obj->toArrayRecursive($value);
156 10
                    }
157 10
                }
158 19
            }
159 19
            return $result;
160
        }
161 10
        return $data;
162
    }
163
}
164