Completed
Push — master ( 88b8c9...f7af16 )
by Michael
02:28
created

CollectionTrait::collectionApiHasMethod()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
namespace Michaels\Manager\Traits;
3
4
use Arrayzy\ArrayImitator;
5
use Michaels\Manager\Helpers;
6
7
/**
8
 * Access Deeply nested manager items through magic methods
9
 *
10
 * MUST be used with ManagesItemsTrait
11
 *
12
 * @implements Michaels\Manager\Contracts\ChainsNestedItemsInterface
13
 * @package Michaels\Manager
14
 */
15
trait CollectionTrait
16
{
17
    use DependsOnManagesItemsTrait;
18
19
    /* Traits cannot declare constants, so we mimic constants with static properties */
20
    public static $RETURN_ARRAY = "_return_array";
21
    public static $RETURN_COLLECTION = "_return_collection";
22
    public static $MODIFY_MANIFEST = "_modify_manifest";
23
24
    /**
25
     * Configuration: do we want to return Collections from get() and getAll()?
26
     * @var bool
27
     */
28
    public $useCollections = true;
29
30
    /**
31
     * Converts an array to a collection if value is arrayable and config is set
32
     * @param $value
33
     * @return static
34
     */
35
    public function toCollection($value)
36
    {
37
        if ($this->wantsCollections() && Helpers::isArrayable($value)) {
38
            return new ArrayImitator(Helpers::getArrayableItems($value));
39
        }
40
41
        return $value;
42
    }
43
44
    /**
45
     * Does this instance want collections returned from get() and getAll()?
46
     * @return bool
47
     */
48
    public function wantsCollections()
49
    {
50
        return ($this->useCollections === true);
51
    }
52
53
    /**
54
     * Invokes when calling a method on the Collection API
55
     *
56
     * This method simply decides how to handle the method call.
57
     *   1. The class is using the ChainsNestedItemsTrait and Collection API does NOT contain the method
58
     *      Let `ChainsNestedItemsTrait` do its thing
59
     *   2. The Collection API DOES contain the method
60
     *      Pass the method call along to the third party Collection API
61
     *   3. The method does not exist on the class or in the Collection API
62
     *      Throw an Exception
63
     * @param string $method Name of the method
64
     * @param array $arguments Arguments passed along
65
     * @return mixed
66
     * @throws \BadMethodCallException
67
     */
68
    public function __call($method, $arguments)
69
    {
70
        /* Decide how to handle this method call */
71
        if (!$this->collectionApiHasMethod($method)) {
72
            // Are we using the Nested Items trait?
73
            if ($this->usingNestedItemsTrait()) {
74
                // Yes, so we simply add it to the chain
75
                return $this->addToChain($method); // in ChainsNestedItemsTrait
0 ignored issues
show
Documentation Bug introduced by
The method addToChain does not exist on object<Michaels\Manager\Traits\CollectionTrait>? 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...
76
                // No, so we are calling a method that simply does not exist
77
            } else {
78
                throw new \BadMethodCallException(
79
                    "Call to undefined method. `$method` does not exist in "
80
                    . get_called_class() . " and it is not part of the Collection API"
81
                );
82
            }
83
        }
84
85
        /* Since we are calling a Collection API method, pass it along */
86
        return $this->passToCollectionApi($method, $arguments);
87
    }
88
89
    /**
90
     * Checks to see if the Collection API contains a specific method
91
     * @param string $method name
92
     * @return bool
93
     */
94
    protected function collectionApiHasMethod($method)
95
    {
96
        return method_exists(new ArrayImitator(), $method);
97
    }
98
99
    /**
100
     * Checks to see if the current Manager class is using `ChainsNestedItemsTrait`
101
     * @return bool
102
     */
103
    protected function usingNestedItemsTrait()
104
    {
105
        return in_array('Michaels\Manager\Traits\ChainsNestedItemsTrait', class_uses($this));
106
    }
107
108
    /**
109
     * Passes the method call along to the Collection API (currently Arrayzy)
110
     * Also checks for any flags that determine how return data should be formatted
111
     *
112
     * @param string $method name
113
     * @param array $arguments to be passed along (including return type flags if exists)
114
     * @return mixed
115
     */
116
    protected function passToCollectionApi($method, $arguments)
117
    {
118
        /* Setup the arguments */
119
        $subject = array_shift($arguments);
120
        $collectionInstance = $this->toCollection($this->get($subject));
121
122
        // Is the last argument one of our flags?
123
        if (in_array(end($arguments), [
124
            static::$RETURN_ARRAY,
125
            static::$RETURN_COLLECTION,
126
            static::$MODIFY_MANIFEST,
127
        ])) {
128
            // Yes, pop it off and set it
129
            $flag = array_pop($arguments);
130
131
        } else {
132
            // No, leave the arguments alone and flag as an ARRAY by default
133
            $flag = static::$RETURN_ARRAY;
134
        }
135
136
        /* Perform the Action */
137
        return $this->callCollectionMethod($method, $arguments, $collectionInstance, $flag, $subject);
138
    }
139
140
    /**
141
     * Calls the actual method on the Collection Instance (currently Arrayzy)
142
     *
143
     * @param string $method name
144
     * @param array $arguments to be passed along
145
     * @param object $instance of the Collection
146
     * @param string $flag corresponding to the properties above
147
     * @param string $subject Alias of data in Manager
148
     * @return mixed
149
     */
150
    protected function callCollectionMethod($method, $arguments, $instance, $flag, $subject)
151
    {
152
        $value = call_user_func_array([$instance, $method], $arguments);
153
154
        switch ($flag) {
155
            case (static::$RETURN_COLLECTION):
156
                return $value;
157
158
            case (static::$MODIFY_MANIFEST):
159
                $this->set($subject, $value->toArray());
160
                return $this;
161
162
            default:
163
            case (static::$RETURN_ARRAY):
0 ignored issues
show
Unused Code introduced by
case static::$RETURN_ARR...turn $value->toArray(); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
164
                return $value->toArray();
165
        }
166
    }
167
}
168