Proxy   B
last analyzed

Complexity

Total Complexity 47

Size/Duplication

Total Lines 196
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 92.45%

Importance

Changes 0
Metric Value
wmc 47
lcom 1
cbo 2
dl 0
loc 196
ccs 98
cts 106
cp 0.9245
rs 8.64
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A __toString() 0 3 2
A __get() 0 6 2
A __call() 0 7 2
A find() 0 4 1
A getElementById() 0 3 1
A offsetGet() 0 9 2
A _isDomProperty() 0 9 1
A _parseName() 0 18 4
A _getTargetProperty() 0 16 4
B _proxyResult() 0 19 7
A __set() 0 13 4
B _domCall() 0 21 8
A registerNamespace() 0 6 3
A offsetSet() 0 9 3
A offsetUnset() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like Proxy 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 Proxy, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * This file is part of the Ariadne Component Library.
4
 *
5
 * (c) Muze <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace arc\xml;
12
13
/**
14
 * This class is a proxy for both the SimpleXMLElement and DOMElement
15
 * properties and methods.
16
 * @property \SimpleXMLElement nodeValue
17
 */
18
class Proxy extends \ArrayObject implements DOMElement, SimpleXMLElement {
19
20 1
    use \arc\traits\Proxy {
21
        \arc\traits\Proxy::__construct as private ProxyConstruct;
22
        \arc\traits\Proxy::__call as private ProxyCall;
23
    }
24
25
    private $parser = null;
26
27 18
    public function __construct( $node, $parser) {
28 18
        $this->ProxyConstruct( $node );
29 18
        $this->parser = $parser;
30 18
    }
31
32 2
    public function __toString() {
33 2
        return isset($this->target) ? (string) $this->target->asXML() : '';
34
    }
35
36 10
    private function _isDomProperty( $name ) {
37
        $domProperties = [
38 10
            'tagName', 'nodeType', 'parentNode',
39
            'firstChild', 'lastChild', 'previousSibling', 'nextSibling',
40
            'ownerDocument', 'namespaceURI', 'prefix',
41
            'localName', 'baseURI', 'textContent'
42
        ];
43 10
        return in_array( $name, $domProperties );
44
    }
45
46 12
    private function _parseName( $name ) {
47 12
        $ns     = '';
48 12
        $name   = trim($name);
49 12
        $prefix = false;
50 12
        if ( $name[0] == '{' ) {
51 2
            list($ns, $name) = explode('}', $name);
52 2
            $ns    = substr($ns, 1);
53 12
        } else if ( strpos($name, ':') !== false ) {
54 4
            list ($ns, $name) = explode(':', $name);
55 4
            if ( isset($this->parser->namespaces[$ns]) ) {
56 4
                $prefix = $ns;
57 4
                $ns     = $this->parser->namespaces[$ns];
58
            } else {
59
                $prefix = $this->lookupPrefix($ns);
0 ignored issues
show
Documentation Bug introduced by
The method lookupPrefix does not exist on object<arc\xml\Proxy>? 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...
60
            }
61
        }
62 12
        return [ $ns, $name, $prefix ];
63
    }
64
65 10
    private function _getTargetProperty($name) {
66 10
        $value  = null;
67
        list( $uri, $name, $prefix ) 
0 ignored issues
show
Unused Code introduced by
The assignment to $prefix is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
68 10
                = $this->_parseName($name);
69 10
        if ( $uri ) {
70 2
            $value = $this->target->children($uri)->{$name};
71 10
        } else if ( !$this->_isDomProperty($name) ) {
72 10
            $value = $this->target->{$name};
73
        } else {
74 4
            $dom = dom_import_simplexml($this->target);
75 4
            if ( isset($dom) ) {
76 4
                $value = $dom->{$name};
77
            }
78
        }
79 10
        return $value;
80
    }
81
82 18
    private function _proxyResult( $value ) {
83 18
        if ( $value instanceof \DOMElement ) {
84 4
            $value = simplexml_import_dom($value);
85 18
        } else if ( $value instanceof \DOMNodeList ) {
86
            $array = [];
87
            for ( $i=0, $l=$value->length; $i<$l; $i ++ ) {
88
                $array[$i] = $value[$i];
89
            }
90
            $value = $array;
91
        }
92 18
        if ( $value instanceof \SimpleXMLElement ) {
93 18
            $value = new static( $value, $this->parser );
94 16
        } else if ( is_array($value) ) {
95 8
            foreach ( $value as $key => $subvalue ) {
96 8
                $value[$key] = $this->_proxyResult( $subvalue );
97
            }
98
        }
99 18
        return $value;
100
    }
101
102 16
    public function __get( $name) {
103 16
        if ($name == 'nodeValue') {
104 12
            return $this->target;
105
        }
106 10
        return $this->_proxyResult( $this->_getTargetProperty($name) );
107
    }
108
109 4
    public function __set( $name, $value ) {
110 4
        if ($name == 'nodeValue') {
111
            $this->target = $value;
112
        } else {
113 4
            list($uri, $name, $prefix) = $this->_parseName($name);
114 4
            if ( $uri && !$this->isDefaultNamespace($uri) ) {
0 ignored issues
show
Documentation Bug introduced by
The method isDefaultNamespace does not exist on object<arc\xml\Proxy>? 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...
115 2
                $el = $this->ownerDocument->createElementNS($uri, $prefix.':'.$name, $value);
0 ignored issues
show
Documentation introduced by
The property ownerDocument does not exist on object<arc\xml\Proxy>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
116 2
                $this->appendChild($el);
0 ignored issues
show
Documentation Bug introduced by
The method appendChild does not exist on object<arc\xml\Proxy>? 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...
117
            } else {
118 2
                $this->target->{$name} = $value;
119
            }
120
        }
121 4
    }
122
123 8
    private function _domCall( $name, $args ) {
124 8
        $dom = dom_import_simplexml($this->target);
125 8
        foreach ( $args as $index => $arg ) {
126 8
            if ( $arg instanceof \arc\xml\Proxy ) {
127 2
                $args[$index] = dom_import_simplexml( $arg->nodeValue );
128 6
            } else if ( $arg instanceof \SimpleXMLElement ) {
129
                $args[$index] = dom_import_simplexml( $arg );
130
            }
131
        }
132
        $importMethods = [
133 8
            'appendChild', 'insertBefore', 'replaceChild'
134
        ];
135 8
        if ( in_array( $name, $importMethods ) ) {
136 4
            if ( isset($args[0]) && $args[0] instanceof \DOMNode ) {
137 4
                if ( $args[0]->ownerDocument !== $this->ownerDocument ) {
0 ignored issues
show
Documentation introduced by
The property ownerDocument does not exist on object<arc\xml\Proxy>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
138 2
                    $args[0] = $this->ownerDocument->importNode( $args[0], true);
0 ignored issues
show
Documentation introduced by
The property ownerDocument does not exist on object<arc\xml\Proxy>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
139
                }
140
            }
141
        }
142 8
        return call_user_func_array( [ $dom, $name], $args );
143
    }
144
145 8
    public function __call( $name, $args ) {
146 8
        if ( !method_exists( $this->target, $name ) ) {
147 8
            return $this->_proxyResult( $this->_domCall( $name, $args ) );
148
        } else {
149 6
            return $this->_proxyResult( $this->ProxyCall( $name, $args ) );
150
        }
151
    }
152
153
    /**
154
     * Search through the XML DOM with a single CSS selector
155
     * @param string $query the CSS selector, most CSS 2 selectors work
156
     * @return Proxy
157
     */
158 8
    public function find( $query) {
159 8
        $xpath = \arc\xml::css2Xpath( $query );
160 8
        return $this->_proxyResult( $this->target->xpath( $xpath ) );
161
    }
162
163
    /**
164
     * Searches through the subtree for an element with the given id and returns it
165
     * @param string $id
166
     * @return Proxy
167
     */
168 2
    public function getElementById( $id ) {
169 2
        return current($this->find('#'.$id));
170
    }
171
172
    /**
173
     * Register a namespace alias and URI to use in xpath and find
174
     * @param string $prefix the alias for this namespace
175
     * @param string $ns the URI for this namespace
176
     */
177 4
    public function registerNamespace( $prefix, $ns ) {
178 4
        if ( $this->target && $this->target instanceof \SimpleXMLElement ) {
179 4
            $this->target->registerXPathNamespace($prefix, $ns);
180
        }
181 4
        $this->parser->namespaces[$prefix] = $ns;
182 4
    }
183
184 6
    public function offsetGet( $offset )
185
    {
186 6
        list( $uri, $name, $prefix ) = $this->_parseName($offset);
0 ignored issues
show
Unused Code introduced by
The assignment to $prefix is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
187 6
        if ( $uri ) {
188 2
            return (string) $this->attributes($uri)[$name];
0 ignored issues
show
Documentation Bug introduced by
The method attributes does not exist on object<arc\xml\Proxy>? 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...
189
        } else {
190 6
            return (string) $this->target[$offset];
191
        }
192
    }
193
194 4
    public function offsetSet( $offset, $value )
195
    {
196 4
        list( $uri, $name, $prefix ) = $this->_parseName($offset);
197 4
        if ( $uri && !$this->isDefaultNamespace($uri) ) {
0 ignored issues
show
Documentation Bug introduced by
The method isDefaultNamespace does not exist on object<arc\xml\Proxy>? 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...
198 2
            $this->setAttributeNS($uri, $prefix.':'.$name, $value);
0 ignored issues
show
Documentation Bug introduced by
The method setAttributeNS does not exist on object<arc\xml\Proxy>? 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...
199
        } else {
200 2
            $this->target[$name] = $value;
201
        }
202 4
    }
203
204 2
    public function offsetUnset( $offset )
205
    {
206 2
        list( $uri, $name, $prefix ) = $this->_parseName($offset);
0 ignored issues
show
Unused Code introduced by
The assignment to $prefix is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
207 2
        if ( $uri ) {
208 2
            unset( $this->target->attributes($uri)->{$name} );
209
        } else {
210
            unset( $this->target[$offset] );
211
        }
212 2
    }
213
}
214