Completed
Pull Request — master (#48)
by Vladimir
02:35
created

JailedDocument::offsetSet()   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 2
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 55
            !($object instanceof \ArrayAccess) &&
44
            !($object instanceof \IteratorAggregate))
45 55
        {
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 11
        {
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 8
        {
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)
0 ignored issues
show
Coding Style introduced by
function coreInstanceOf() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
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