Completed
Push — 5.1 ( c7eca6...1f8916 )
by Jarek
05:49
created

Mutator::mutate()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 14
rs 9.4285
cc 3
eloc 7
nc 4
nop 2
1
<?php
2
3
namespace Sofa\Eloquence\Mutator;
4
5
use ReflectionException;
6
use ReflectionClass;
7
use ReflectionMethod;
8
use Illuminate\Support\Traits\Macroable;
9
use Sofa\Eloquence\Contracts\Mutator as MutatorContract;
10
11
class Mutator implements MutatorContract
12
{
13
    use Macroable;
14
15
    /**
16
     * Mutate value using provided methods.
17
     *
18
     * @param  mixed $value
19
     * @param  string|array $callables
20
     * @return mixed
21
     *
22
     * @throws \LogicException
23
     */
24
    public function mutate($value, $callables)
25
    {
26
        if (!is_array($callables)) {
27
            $callables = explode('|', $callables);
28
        }
29
30
        foreach ($callables as $callable) {
31
            list($callable, $args) = $this->parse(trim($callable));
32
33
            $value = call_user_func_array($callable, array_merge([$value], $args));
34
        }
35
36
        return $value;
37
    }
38
39
    /**
40
     * Parse provided mutator functions.
41
     *
42
     * @param  string $callable
43
     * @return array
44
     *
45
     * @throws \Sofa\Eloquence\Mutator\InvalidCallableException
46
     */
47
    protected function parse($callable)
48
    {
49
        list($callable, $args) = $this->parseArgs($callable);
50
51
        if ($this->isClassMethod($callable)) {
52
            $callable = $this->parseClassMethod($callable);
53
54
        } elseif ($this->isMutatorMethod($callable)) {
55
            $callable = [$this, $callable];
56
57
        } elseif (!function_exists($callable)) {
58
            throw new InvalidCallableException("Function [{$callable}] not found.");
59
        }
60
61
        return [$callable, $args];
62
    }
63
64
    /**
65
     * Determine whether callable is a class method.
66
     *
67
     * @param  string  $callable
68
     * @return boolean
69
     */
70
    protected function isClassMethod($callable)
71
    {
72
        return strpos($callable, '@') !== false;
73
    }
74
75
    /**
76
     * Determine whether callable is available on this instance.
77
     *
78
     * @param  string  $callable
79
     * @return boolean
80
     */
81
    protected function isMutatorMethod($callable)
82
    {
83
        return method_exists($this, $callable) || static::hasMacro($callable);
84
    }
85
86
    /**
87
     * Split provided string into callable and arguments.
88
     *
89
     * @param  string $callable
90
     * @return array
91
     */
92
    protected function parseArgs($callable)
93
    {
94
        $args = [];
95
96
        if (strpos($callable, ':') !== false) {
97
            list($callable, $argsString) = explode(':', $callable);
98
99
            $args = explode(',', $argsString);
100
        }
101
102
        return [$callable, $args];
103
    }
104
105
    /**
106
     * Extract and validate class method.
107
     *
108
     * @param  string   $userCallable
109
     * @return callable
110
     *
111
     * @throws \Sofa\Eloquence\Mutator\InvalidCallableException
112
     */
113
    protected function parseClassMethod($userCallable)
114
    {
115
        list($class) = explode('@', $userCallable);
116
117
        $callable = str_replace('@', '::', $userCallable);
118
119
        try {
120
            $method = new ReflectionMethod($callable);
121
122
            $class = new ReflectionClass($class);
123
        } catch (ReflectionException $e) {
124
            throw new InvalidCallableException($e->getMessage());
125
        }
126
127
        return ($method->isStatic()) ? $callable : $this->getInstanceMethod($class, $method);
128
    }
129
130
    /**
131
     * Get instance callable.
132
     *
133
     * @param  \ReflectionMethod  $method
134
     * @return callable
135
     *
136
     * @throws \Sofa\Eloquence\Mutator\InvalidCallableException
137
     */
138
    protected function getInstanceMethod(ReflectionClass $class, ReflectionMethod $method)
139
    {
140
        if (!$method->isPublic()) {
141
            throw new InvalidCallableException("Instance method [{$class}@{$method->getName()}] is not public.");
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
142
        }
143
144
        if (!$this->canInstantiate($class)) {
145
            throw new InvalidCallableException("Can't instantiate class [{$class->getName()}].");
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
146
        }
147
148
        return [$class->newInstance(), $method->getName()];
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
149
    }
150
151
    /**
152
     * Determine whether instance can be instantiated.
153
     *
154
     * @param  \ReflectionClass  $class
155
     * @return boolean
156
     */
157
    protected function canInstantiate(ReflectionClass $class)
158
    {
159
        if (!$class->isInstantiable()) {
160
            return false;
161
        }
162
163
        $constructor = $class->getConstructor();
164
165
        return is_null($constructor) || 0 === $constructor->getNumberOfRequiredParameters();
166
    }
167
}
168