Completed
Push — master ( 0ba1b3...937b8a )
by Vladimir
02:47
created

JailedDocument::offsetUnset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @copyright 2017 Vladimir Jimenez
5
 * @license   https://github.com/allejo/stakx/blob/master/LICENSE.md MIT
6
 */
7
8
namespace allejo\stakx\Document;
9
10
/**
11
 * Class JailObject.
12
 *
13
 * A wrapper object to only allow certain functions on the white list to be called. This is used in order to limit which
14
 * functions a user can call from Twig templates to prevent unexpected behavior.
15
 */
16
class JailedDocument implements \ArrayAccess, \IteratorAggregate
17
{
18
    /**
19
     * @var string[]
20
     */
21
    private $whiteListFunctions;
22
23
    /**
24
     * @var string[]
25
     */
26
    private $jailedFunctions;
27
28
    /**
29
     * @var JailedDocumentInterface
30
     */
31
    private $object;
32
33
    /**
34
     * JailObject constructor.
35
     *
36
     * @param JailedDocumentInterface $object             The object that will be jailed
37
     * @param array                   $whiteListFunctions A list of function names that can be called
38
     * @param array                   $jailedFunctions
39
     */
40 55
    public function __construct(&$object, array $whiteListFunctions, array $jailedFunctions = array())
41
    {
42 55
        if (!($object instanceof JailedDocumentInterface) &&
43
            !($object instanceof \ArrayAccess) &&
44
            !($object instanceof \IteratorAggregate))
45
        {
46
            throw new \InvalidArgumentException('Must implement the ArrayAccess and Jailable interfaces');
47
        }
48
49 55
        $this->object = &$object;
50 55
        $this->whiteListFunctions = $whiteListFunctions;
51 55
        $this->jailedFunctions = $jailedFunctions;
52 55
    }
53
54 11
    public function __call($name, $arguments)
55
    {
56
        // White listed functions will always be getter functions, so somehow get the name of a possible getter function
57
        // name.
58 11
        $lcName = lcfirst($name);
59 11
        $getFxnCall = ($lcName[0] === 'g' && strpos($lcName, 'get') === 0) ? $lcName : sprintf('get%s', ucfirst($name));
60
61
        // Check if our function call is a jailed call, meaning the function should be mapped to special "jailed"
62
        // jailed version of the function call.
63 11
        if (array_key_exists($getFxnCall, $this->jailedFunctions))
64
        {
65 3
            return call_user_func_array(array($this->object, $this->jailedFunctions[$getFxnCall]), $arguments);
66
        }
67
68
        // Otherwise, test to see if the function call is in our white list and call it
69 8
        if (in_array($getFxnCall, $this->whiteListFunctions))
70
        {
71 7
            return call_user_func_array(array($this->object, $getFxnCall), $arguments);
72
        }
73
74 1
        throw new \BadMethodCallException();
75
    }
76
77 4
    public function coreInstanceOf($class)
78
    {
79 4
        return is_subclass_of($this->object, $class);
80
    }
81
82
    //
83
    // ArrayAccess Implementation
84
    //
85
86
    /**
87
     * {@inheritdoc}
88
     */
89 22
    public function offsetExists($offset)
90
    {
91 22
        return $this->object->offsetExists($offset);
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     */
97 32
    public function offsetGet($offset)
98
    {
99 32
        return $this->object->offsetGet($offset);
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105
    public function offsetSet($offset, $value)
106
    {
107
        return $this->object->offsetSet($offset, $value);
108
    }
109
110
    /**
111
     * {@inheritdoc}
112
     */
113
    public function offsetUnset($offset)
114
    {
115
        return $this->object->offsetUnset($offset);
116
    }
117
118
    //
119
    // IteratorAggregate implementation
120
    //
121
122
    /**
123
     * {@inheritdoc}
124
     */
125 19
    public function getIterator()
126
    {
127 19
        return $this->object->getIterator();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface allejo\stakx\Document\JailedDocumentInterface as the method getIterator() does only exist in the following implementations of said interface: allejo\stakx\Document\ContentItem, allejo\stakx\Document\DataItem.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
128
    }
129
}
130