Picklock   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 134
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 13
eloc 47
dl 0
loc 134
ccs 37
cts 37
cp 1
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A getObject() 0 23 4
A errorMessage() 0 8 2
A getValue() 0 20 2
A assertParam() 0 4 3
A callMethod() 0 19 2
1
<?php declare(strict_types=1);
2
3
namespace AlecRabbit\Helpers\Classes;
4
5
use function AlecRabbit\typeOf;
6
7
/**
8
 * Class Picklock
9
 *
10
 * @link https://gitlab.com/m0rtis/picklock
11
 * @license Apache License 2.0
12
 * @author Anton Fomichev aka m0rtis - [email protected]
13
 *
14
 * @package AlecRabbit\Helpers\Objects
15
 * @author AlecRabbit
16
 *
17
 * @internal
18
 */
19
final class Picklock
20
{
21
    public const EXCEPTION_TEMPLATE = 'Class [%s] does not have `%s` %s';
22
    public const INVALID_ARGUMENT_EXCEPTION_STRING = 'Param 1 should be object or a class-string.';
23
    public const METHOD = 'method';
24
    public const PROPERTY = 'property';
25
26
    /**
27
     * Calls a private or protected method of an object.
28
     *
29
     * @psalm-suppress InvalidScope
30
     *
31
     * @param mixed $objectOrClass
32
     * @param string $methodName
33
     * @param mixed ...$args
34
     *
35
     * @return mixed
36
     */
37 3
    public static function callMethod($objectOrClass, string $methodName, ...$args)
38
    {
39 3
        $objectOrClass = self::getObject($objectOrClass);
40
        $closure =
41
            /**
42
             * @param string $methodName
43
             * @param array $args
44
             * @return mixed
45
             */
46
            function (string $methodName, ...$args) {
47 2
                if (\method_exists($this, $methodName)) {
48 2
                    return $this->$methodName(...$args);
49
                }
50 2
                throw new \RuntimeException(
51 2
                    Picklock::errorMessage($this, $methodName, true)
52
                );
53 2
            };
54
        return
55 2
            $closure->call($objectOrClass, $methodName, ...$args);
56
    }
57
58
    /**
59
     * @psalm-suppress TypeCoercion
60
     * @psalm-suppress InvalidStringClass
61
     *
62
     * @param object|string $objectOrClass
63
     *
64
     * @return object
65
     */
66 6
    protected static function getObject($objectOrClass): object
67
    {
68 6
        self::assertParam($objectOrClass);
69
70 4
        if (\is_string($objectOrClass)) {
71
            try {
72 2
                $objectOrClass = new $objectOrClass();
73 2
            } catch (\Error $e) {
74
                try {
75 2
                    $class = new \ReflectionClass($objectOrClass);
76 2
                    $objectOrClass = $class->newInstanceWithoutConstructor();
77
                    // @codeCoverageIgnoreStart
78
                } catch (\ReflectionException $exception) {
79
                    throw new \RuntimeException(
80
                        '[' . typeOf($exception) . '] ' . $exception->getMessage(),
81
                        (int)$exception->getCode(),
82
                        $exception
83
                    );
84
                }
85
                // @codeCoverageIgnoreEnd
86
            }
87
        }
88 4
        return $objectOrClass;
89
    }
90
91
    /**
92
     * @param mixed $objectOrClass
93
     */
94 6
    protected static function assertParam($objectOrClass): void
95
    {
96 6
        if (!\is_string($objectOrClass) && !\is_object($objectOrClass)) {
97 2
            throw new \InvalidArgumentException(self::INVALID_ARGUMENT_EXCEPTION_STRING);
98
        }
99 4
    }
100
101
    /**
102
     * Creates an error message.
103
     *
104
     * @param object $object
105
     * @param string $part
106
     * @param bool $forMethod
107
     *
108
     * @return string
109
     */
110 4
    public static function errorMessage(object $object, string $part, bool $forMethod): string
111
    {
112
        return
113 4
            sprintf(
114 4
                static::EXCEPTION_TEMPLATE,
115 4
                \get_class($object),
116 4
                $part,
117 4
                $forMethod ? static::METHOD : static::PROPERTY
118
            );
119
    }
120
121
    /**
122
     * Gets a value of a private or protected property of an object.
123
     *
124
     * @psalm-suppress InvalidScope
125
     * @psalm-suppress PossiblyInvalidFunctionCall
126
     * @psalm-suppress TypeCoercion
127
     *
128
     * @param mixed $objectOrClass
129
     * @param string $propertyName
130
     *
131
     * @return mixed
132
     */
133 3
    public static function getValue($objectOrClass, string $propertyName)
134
    {
135 3
        $objectOrClass = self::getObject($objectOrClass);
136
        $closure =
137
            /**
138
             * @return mixed
139
             */
140
            function () use ($propertyName) {
141 2
                if (\property_exists($this, $propertyName)) {
142 2
                    $class = new \ReflectionClass(typeOf($this));
143 2
                    $property = $class->getProperty($propertyName);
144 2
                    $property->setAccessible(true);
145 2
                    return $property->getValue($this);
146
                }
147 2
                throw new \RuntimeException(
148 2
                    Picklock::errorMessage($this, $propertyName, false)
149
                );
150 2
            };
151
        return
152 2
            $closure->bindTo($objectOrClass, $objectOrClass)();
153
    }
154
}
155