Completed
Push — master ( b035c8...ddcf44 )
by Paweł
02:10
created

AbstractItem::__construct()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
nc 4
nop 0
dl 0
loc 22
rs 8.6346
c 0
b 0
f 0
ccs 14
cts 14
cp 1
crap 7
1
<?php
2
declare(strict_types=1);
3
4
namespace Wszetko\Sitemap\Items;
5
6
use ReflectionClass;
7
use ReflectionProperty;
8
use Wszetko\Sitemap\Interfaces\DataType;
9
use Wszetko\Sitemap\Interfaces\Item;
10
use Wszetko\Sitemap\Items\DataTypes\ArrayType;
11
use Wszetko\Sitemap\Traits\IsAssoc;
12
13
/**
14
 * Class AbstractItem
15
 *
16
 * @package Wszetko\Sitemap\Items
17
 */
18
abstract class AbstractItem implements Item
19
{
20
    use IsAssoc;
21
22
    /**
23
     * AbstractItem constructor.
24
     *
25
     * @throws \ReflectionException
26
     */
27 58
    public function __construct()
28
    {
29 58
        $class = new ReflectionClass($this);
30 58
        $properties = $class->getProperties(ReflectionProperty::IS_PROTECTED);
31
32 58
        foreach ($properties as $property) {
33 57
            $data = $this->grabData($property);
34
35 57
            if (!empty($data['type']) &&
36 57
                class_exists($data['type']) &&
37 57
                in_array('Wszetko\Sitemap\Interfaces\DataType', class_implements($data['type'])))
38
            {
39 57
                if (!empty($data['dataType']) && class_exists($data['dataType'])) {
40 41
                    $this->{$property->getName()} = new ArrayType($property->getName(), $data['dataType']);
41 41
                    $this->{$property->getName()}->getBaseDataType()->addAttributes($data['attributes']);
42
                } else {
43 54
                    $this->{$property->getName()} = new $data['type']($property->getName());
44 57
                    $this->{$property->getName()}->addAttributes($data['attributes']);
45
                }
46
            }
47
        }
48 58
    }
49
50 57
    private function grabData(ReflectionProperty $property): array
51
    {
52 57
        preg_match_all('/@var\s+(?\'type\'[^\s]+)|@dataType\s+(?\'dataType\'[^\s]+)|@attribute\s+(?\'attribute\'[^\s]+)|@attributeDataType\s+(?\'attributeDataType\'[^\s]+)/m', $property->getDocComment(), $matches);
53
54
        $results = [
55 57
            'type' => null,
56
            'dataType' => null,
57
            'attributes' => []
58
        ];
59
60 57
        foreach ($matches['type'] as $match) {
61 57
            if (!empty($match)) {
62 57
                $results['type'] = $match;
63 57
                break;
64
            }
65
        }
66
67 57
        foreach ($matches['dataType'] as $match) {
68 57
            if (!empty($match)) {
69 41
                $results['dataType'] = $match;
70 57
                break;
71
            }
72
        }
73
74 57
        foreach ($matches['attribute'] as $key => $match) {
75 57
            if (!empty($match) && !empty($matches['attributeDataType'][$key + 1])) {
76 57
                $results['attributes'][$match] = $matches['attributeDataType'][$key + 1];
77
            }
78
        }
79
80 57
        return $results;
81
    }
82
83 4
    public function toArray(): array
84
    {
85 4
        if (static::NAMESPACE_NAME && static::ELEMENT_NAME) {
86
            $array = [
87 4
                '_namespace' => static::NAMESPACE_NAME,
88 4
                '_element' => static::ELEMENT_NAME,
89
            ];
90
        }
91
92 4
        $array[static::ELEMENT_NAME] = [];
0 ignored issues
show
Bug introduced by
The variable $array does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
93
94 4
        foreach (get_object_vars($this) as $property => $value) {
95 3
            if (is_object($this->$property)) {
96 3
                $method = 'get' . ucfirst($property);
97 3
                preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $property, $matches);
98 3
                $property = $matches[0];
99
100 3
                foreach ($property as &$match) {
101 3
                    $match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match);
102
                }
103
104 3
                $property = implode('_', $property);
105 3
                $data = $this->$method();
106
107 3
                if ($data) {
108 3
                    if (is_array($data)) {
109
//                        $array[static::ELEMENT_NAME][$property] = [];
110
111 1
                        if ($this->isAssoc($data)) {
112 1
                            $item = array_key_first($data);
113 1
                            $array[static::ELEMENT_NAME][$property]['_value'] = $item;
114 1
                            foreach ($data[$item] as $attr => $val) {
115 1
                                $array[static::ELEMENT_NAME][$property]['_attributes'][$attr] = $val;
116
                            }
117
                        } else {
118 1
                            foreach ($data as $element) {
119 1
                                $array[static::ELEMENT_NAME][$property][] = $element;
120
                            }
121
                        }
122
123
                    } else {
124 3
                        $array[static::ELEMENT_NAME][$property] = $data;
125
                    }
126
                }
127
            }
128
        }
129
130 4
        return $array;
131
    }
132
133
    /**
134
     * @param $name
135
     * @param $arguments
136
     *
137
     * @return mixed
138
     */
139 57
    public function __call($name, $arguments)
140
    {
141 57
        $operation = substr($name, 0 ,3);
142 57
        $property = lcfirst(substr($name, 3));
143
144 57
        if (property_exists($this, $property) &&
145 57
            in_array($operation, ['add', 'set', 'get']) &&
146 57
            ($this->$property instanceof DataType)) {
147 57
            switch ($operation) {
148 57
                case 'add':
149 8
                    $this->$property->addValue($arguments[0], array_slice($arguments, 1));
150 8
                    return $this;
151
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
152 56
                case 'set':
153 54
                    $this->$property->setValue($arguments[0], array_slice($arguments, 1));
154 53
                    return $this;
155
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
156 45
                case 'get':
157 45
                    if (method_exists($this, 'getDomain') &&
158 45
                        method_exists($this->$property, 'setDomain') &&
159 45
                        $this->getDomain() !== null
0 ignored issues
show
Documentation Bug introduced by
The method getDomain does not exist on object<Wszetko\Sitemap\Items\AbstractItem>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
160
                        ) {
161 13
                        $this->$property->setDomain($this->getDomain());
0 ignored issues
show
Documentation Bug introduced by
The method getDomain does not exist on object<Wszetko\Sitemap\Items\AbstractItem>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
162
                    }
163
164 45
                    return $this->$property->getValue();
165
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
166
            }
167
        }
168
    }
169
}