Completed
Push — master ( 952faf...f2d1b4 )
by Alec
02:39
created

Picklock::assertParam()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 2
nc 2
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 3
rs 10
c 0
b 0
f 0
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 METHOD = 'method';
23
    public const PROPERTY = 'property';
24
25
    /**
26
     * Calls a private or protected method of an object.
27
     *
28
     * @psalm-suppress InvalidScope
29
     *
30
     * @param mixed $objectOrClass
31
     * @param string $methodName
32
     * @param mixed ...$args
33
     *
34
     * @return mixed
35
     */
36 3
    public static function callMethod($objectOrClass, string $methodName, ...$args)
37
    {
38 3
        $objectOrClass = self::getObject($objectOrClass);
39
        $closure =
40
            /**
41
             * @param string $methodName
42
             * @param array $args
43
             * @return mixed
44
             */
45
            function (string $methodName, ...$args) {
46 2
                if (\method_exists($this, $methodName)) {
47 2
                    return $this->$methodName(...$args);
48
                }
49 2
                throw new \RuntimeException(
50 2
                    Picklock::errorMessage($this, $methodName, true)
51
                );
52 2
            };
53
        return
54 2
            $closure->call($objectOrClass, $methodName, ...$args);
55
    }
56
57
    /**
58
     * @psalm-suppress TypeCoercion
59
     * @psalm-suppress InvalidStringClass
60
     *
61
     * @param object|string $objectOrClass
62
     *
63
     * @return object
64
     */
65 6
    protected static function getObject($objectOrClass): object
66
    {
67 6
        self::assertParam($objectOrClass);
68
69 4
        if (\is_string($objectOrClass)) {
70
            try {
71 2
                $objectOrClass = new $objectOrClass();
72 2
            } catch (\Error $e) {
73
                try {
74 2
                    $class = new \ReflectionClass($objectOrClass);
75 2
                    $objectOrClass = $class->newInstanceWithoutConstructor();
76
                // @codeCoverageIgnoreStart
77
                } catch (\ReflectionException $exception) {
78
                    throw new \RuntimeException(
79
                        '[' . typeOf($exception) . '] ' . $exception->getMessage(),
80
                        (int)$exception->getCode(),
81
                        $exception
82
                    );
83
                }
84
                // @codeCoverageIgnoreEnd
85
            }
86
        }
87 4
        return $objectOrClass;
88
    }
89
90
    /**
91
     * @param mixed $objectOrClass
92
     */
93 6
    protected static function assertParam($objectOrClass): void
94
    {
95 6
        if (!\is_string($objectOrClass) && !\is_object($objectOrClass)) {
96 2
            throw new \InvalidArgumentException('Param 1 should be object or a class name.');
97
        }
98 4
    }
99
100
    /**
101
     * Creates an error message.
102
     *
103
     * @param object $object
104
     * @param string $part
105
     * @param bool $forMethod
106
     *
107
     * @return string
108
     */
109 4
    public static function errorMessage(object $object, string $part, bool $forMethod): string
110
    {
111
        return
112 4
            sprintf(
113 4
                static::EXCEPTION_TEMPLATE,
114 4
                \get_class($object),
115 4
                $part,
116 4
                $forMethod ? static::METHOD : static::PROPERTY
117
            );
118
    }
119
120
    /**
121
     * Gets a value of a private or protected property of an object.
122
     *
123
     * @psalm-suppress InvalidScope
124
     * @psalm-suppress PossiblyInvalidFunctionCall
125
     * @psalm-suppress TypeCoercion
126
     *
127
     * @param mixed $objectOrClass
128
     * @param string $propertyName
129
     *
130
     * @return mixed
131
     */
132 3
    public static function getValue($objectOrClass, string $propertyName)
133
    {
134 3
        $objectOrClass = self::getObject($objectOrClass);
135
        $closure =
136
            /**
137
             * @return mixed
138
             */
139
            function () use ($propertyName) {
140 2
                if (\property_exists($this, $propertyName)) {
141 2
                    $class = new \ReflectionClass(typeOf($this));
142 2
                    $property = $class->getProperty($propertyName);
143 2
                    $property->setAccessible(true);
144 2
                    return $property->getValue($this);
145
                }
146 2
                throw new \RuntimeException(
147 2
                    Picklock::errorMessage($this, $propertyName, false)
148
                );
149 2
            };
150
        return
151 2
            $closure->bindTo($objectOrClass, $objectOrClass)();
152
    }
153
}
154