Passed
Push — master ( 8e9a4b...0b4ca3 )
by Sebastian
03:12
created

ClassHelper::requireObjectInstanceOf()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 13
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 13
rs 9.6111
cc 5
nc 3
nop 3
1
<?php
2
/**
3
 * @package Application Utils
4
 * @subpackage ClassFinder
5
 * @see \AppUtils\ClassHelper
6
 */
7
8
declare(strict_types=1);
9
10
namespace AppUtils;
11
12
use AppUtils\ClassHelper\ClassLoaderNotFoundException;
13
use AppUtils\ClassHelper\ClassNotExistsException;
14
use AppUtils\ClassHelper\ClassNotImplementsException;
15
use Composer\Autoload\ClassLoader;
16
17
/**
18
 * Helper class to simplify working with dynamic class loading,
19
 * in a static analysis-tool-friendly way. PHPStan and co will
20
 * recognize the correct class types given class strings.
21
 *
22
 * @package Application Utils
23
 * @subpackage ClassFinder
24
 * @author Sebastian Mordziol <[email protected]>
25
 */
26
class ClassHelper
27
{
28
    private static ?ClassLoader $classLoader = null;
29
30
    public const ERROR_CANNOT_RESOLVE_CLASS_NAME = 111001;
31
32
    /**
33
     * Attempts to detect the name of a class, switching between
34
     * the older class naming scheme with underscores (Long_Class_Name)
35
     * and namespaces.
36
     *
37
     * @param string $legacyName
38
     * @return string|null The detected class name, or NULL otherwise.
39
     */
40
    public static function resolveClassName(string $legacyName) : ?string
41
    {
42
        // Handle cases where we have a mix of styles because of
43
        // get_class() used to build a class name.
44
        $legacyName = str_replace('\\', '_', $legacyName);
45
46
        if(class_exists($legacyName))
47
        {
48
            return $legacyName;
49
        }
50
51
        $nameNS = str_replace('_', '\\', $legacyName);
52
53
        if(class_exists($nameNS))
54
        {
55
            return ltrim($nameNS, '\\');
56
        }
57
58
        return null;
59
    }
60
61
    /**
62
     * Like {@see ClassHelper::resolveClassName()}, but throws an exception
63
     * if the class can not be found.
64
     *
65
     * @param string $legacyName
66
     * @return string
67
     * @throws ClassNotExistsException
68
     */
69
    public static function requireResolvedClass(string $legacyName) : string
70
    {
71
        $class = self::resolveClassName($legacyName);
72
73
        if($class !== null)
74
        {
75
            return $class;
76
        }
77
78
        throw new ClassNotExistsException(
79
            $legacyName,
80
            self::ERROR_CANNOT_RESOLVE_CLASS_NAME
81
        );
82
    }
83
84
    /**
85
     * Throws an exception if the target class can not be found.
86
     *
87
     * @param string $className
88
     * @return void
89
     * @throws ClassNotExistsException
90
     */
91
    public static function requireClassExists(string $className) : void
92
    {
93
        if(class_exists($className))
94
        {
95
            return;
96
        }
97
98
        throw new ClassNotExistsException($className);
99
    }
100
101
    /**
102
     * Requires the target class name to exist, and extend
103
     * or implement the specified class/interface. If it does
104
     * not, an exception is thrown.
105
     *
106
     * @param class-string $targetClass
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
107
     * @param class-string $extendsClass
108
     * @return void
109
     *
110
     * @throws ClassNotImplementsException
111
     * @throws ClassNotExistsException
112
     */
113
    public static function requireClassInstanceOf(string $targetClass, string $extendsClass) : void
114
    {
115
        self::requireClassExists($targetClass);
116
        self::requireClassExists($extendsClass);
117
118
        if(is_a($targetClass, $extendsClass, true))
119
        {
120
            return;
121
        }
122
123
        throw new ClassNotImplementsException($extendsClass, $targetClass);
124
    }
125
126
    /**
127
     * If the target object is not an instance of the target class
128
     * or interface, throws an exception.
129
     *
130
     * @template ClassInstanceType
131
     * @param class-string<ClassInstanceType> $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<ClassInstanceType> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<ClassInstanceType>.
Loading history...
132
     * @param object $object
133
     * @param int $errorCode
134
     * @return ClassInstanceType
135
     *
136
     * @throws ClassNotExistsException
137
     * @throws ClassNotImplementsException
138
     */
139
    public static function requireObjectInstanceOf(string $class, object $object, int $errorCode=0)
140
    {
141
        if(!class_exists($class) && !interface_exists($class) && !trait_exists($class))
142
        {
143
            throw new ClassNotExistsException($class, $errorCode);
144
        }
145
146
        if(is_a($object, $class, true))
147
        {
148
            return $object;
149
        }
150
151
        throw new ClassNotImplementsException($class, $object, $errorCode);
152
    }
153
154
    /**
155
     * Retrieves an instance of the Composer class loader of
156
     * the current project. This assumes the usual structure
157
     * with this library being stored in the `vendor` folder.
158
     *
159
     * NOTE: Also works when working on a local copy of the
160
     * Git package.
161
     *
162
     * @return ClassLoader
163
     * @throws ClassLoaderNotFoundException
164
     */
165
    public static function getClassLoader() : ClassLoader
166
    {
167
        if(isset(self::$classLoader)) {
168
            return self::$classLoader;
169
        }
170
171
        // Paths are either the folder structure when the
172
        // package has been installed as a dependency via
173
        // composer, or a local installation of the git package.
174
        $paths = array(
175
            __DIR__.'/../../../autoload.php',
176
            __DIR__.'/../vendor/autoload.php'
177
        );
178
179
        $autoloadFile = null;
180
181
        foreach($paths as $path)
182
        {
183
            if(file_exists($path)) {
184
                $autoloadFile = $path;
185
            }
186
        }
187
188
        if($autoloadFile === null) {
189
            throw new ClassLoaderNotFoundException($paths);
190
        }
191
192
        $loader = require $autoloadFile;
193
194
        if (!$loader instanceof ClassLoader)
195
        {
196
            throw new ClassLoaderNotFoundException($paths);
197
        }
198
199
        self::$classLoader = $loader;
200
201
        return self::$classLoader;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::classLoader returns the type null which is incompatible with the type-hinted return Composer\Autoload\ClassLoader.
Loading history...
202
    }
203
}
204