| Code Coverage | ||||||||||
| Lines | Functions and Methods | Classes and Traits | ||||||||
| Total |  | 100.00% | 48 / 48 |  | 100.00% | 7 / 7 | CRAP |  | 100.00% | 1 / 1 | 
| Loader |  | 100.00% | 48 / 48 |  | 100.00% | 7 / 7 | 21 |  | 100.00% | 1 / 1 | 
| started |  | 100.00% | 1 / 1 |  | 100.00% | 1 / 1 | 1 | |||
| init |  | 100.00% | 3 / 3 |  | 100.00% | 1 / 1 | 2 | |||
| __construct |  | 100.00% | 12 / 12 |  | 100.00% | 1 / 1 | 4 | |||
| autoload |  | 100.00% | 5 / 5 |  | 100.00% | 1 / 1 | 3 | |||
| callStaticConstructor |  | 100.00% | 15 / 15 |  | 100.00% | 1 / 1 | 7 | |||
| overrideSplLoaders |  | 100.00% | 10 / 10 |  | 100.00% | 1 / 1 | 2 | |||
| checkLoadedClasses |  | 100.00% | 2 / 2 |  | 100.00% | 1 / 1 | 2 | |||
| 1 | <?php | 
| 2 | |
| 3 | declare(strict_types=1); | 
| 4 | |
| 5 | /** | 
| 6 | * Copyright (c) 2019-2023 NxtLvl Software Solutions. | 
| 7 | * | 
| 8 | * Freely available to use under the terms of the MIT license. | 
| 9 | */ | 
| 10 | |
| 11 | namespace NxtLvlSoftware\StaticConstructors; | 
| 12 | |
| 13 | use InvalidArgumentException; | 
| 14 | use NxtLvlSoftware\StaticConstructors\Policy\Class\PhpStyleConstructorPolicy; | 
| 15 | use NxtLvlSoftware\StaticConstructors\Policy\Class\SameNameAsClassPolicy; | 
| 16 | use NxtLvlSoftware\StaticConstructors\Policy\Method\BaseRequirementMethodPolicy; | 
| 17 | use NxtLvlSoftware\StaticConstructors\Policy\Method\NoArgumentsMethodPolicy; | 
| 18 | use NxtLvlSoftware\StaticConstructors\Policy\Method\Visibility\PrivateVisibilityPolicy; | 
| 19 | use ReflectionClass; | 
| 20 | use function class_exists; | 
| 21 | use function get_declared_classes; | 
| 22 | use function spl_autoload_functions; | 
| 23 | use function spl_autoload_register; | 
| 24 | use function spl_autoload_unregister; | 
| 25 | |
| 26 | /** | 
| 27 | * Singleton responsible for calling static constructors on classes that | 
| 28 | * provide them. | 
| 29 | * | 
| 30 | * Uses {@link \NxtLvlSoftware\StaticConstructors\Policy\Class\StaticConstructorClassPolicy} | 
| 31 | * and {@link \NxtLvlSoftware\StaticConstructors\Policy\Method\StaticConstructorMethodPolicy} | 
| 32 | * to determine valid classes and methods. | 
| 33 | */ | 
| 34 | final class Loader { | 
| 35 | |
| 36 | /** | 
| 37 | * The default class policies for finding static constructor methods. | 
| 38 | */ | 
| 39 | public const DEFAULT_CLASS_POLICIES = [ | 
| 40 | SameNameAsClassPolicy::class, | 
| 41 | PhpStyleConstructorPolicy::class, | 
| 42 | ]; | 
| 43 | |
| 44 | /** | 
| 45 | * The default method policies for determining validity of static constructor | 
| 46 | * candidate methods. | 
| 47 | */ | 
| 48 | public const DEFAULT_METHOD_POLICIES = [ | 
| 49 | NoArgumentsMethodPolicy::class, | 
| 50 | PrivateVisibilityPolicy::class, | 
| 51 | ]; | 
| 52 | |
| 53 | private static self|null $instance = null; | 
| 54 | |
| 55 | /** | 
| 56 | * Check if {@link \NxtLvlSoftware\StaticConstructors\Loader::init()} has been called. | 
| 57 | */ | 
| 58 | public static function started(): bool { | 
| 59 | return self::$instance !== null; | 
| 60 | } | 
| 61 | |
| 62 | /** | 
| 63 | * Start the loader if an instance does not already exist. | 
| 64 | * | 
| 65 | * @param list<class-string<\NxtLvlSoftware\StaticConstructors\Policy\Class\StaticConstructorClassPolicy>> $classPolicies Policy class names for determining if a class has a valid static constructor method defined. | 
| 66 | * @param list<class-string<\NxtLvlSoftware\StaticConstructors\Policy\Method\StaticConstructorMethodPolicy>> $methodPolicies Policy class names for determining validity of candidate static constructor methods. | 
| 67 | * @param bool $checkLoadedClasses Should the loader check for a static constructor on classes that are already declared in the runtime? | 
| 68 | */ | 
| 69 | public static function init( | 
| 70 | array $classPolicies = self::DEFAULT_CLASS_POLICIES, | 
| 71 | array $methodPolicies = self::DEFAULT_METHOD_POLICIES, | 
| 72 | bool $checkLoadedClasses = true | 
| 73 | ): void { | 
| 74 | if (self::started()) { | 
| 75 | return; | 
| 76 | } | 
| 77 | |
| 78 | new self($classPolicies, $methodPolicies, $checkLoadedClasses); | 
| 79 | } | 
| 80 | |
| 81 | /** @var list<callable(class-string): void> */ | 
| 82 | private array $proxiedLoaders = []; | 
| 83 | |
| 84 | /** | 
| 85 | * The entry to static class constructors. | 
| 86 | * | 
| 87 | * This class can only exist as a singleton so that we only override | 
| 88 | * the existing autoloader's if this is the first instance constructed. | 
| 89 | * | 
| 90 | * @param list<class-string<\NxtLvlSoftware\StaticConstructors\Policy\Class\StaticConstructorClassPolicy>> $classPolicies | 
| 91 | * @param list<class-string<\NxtLvlSoftware\StaticConstructors\Policy\Method\StaticConstructorMethodPolicy>> $methodPolicies | 
| 92 | */ | 
| 93 | private function __construct( | 
| 94 | private readonly array $classPolicies, | 
| 95 | private readonly array $methodPolicies, | 
| 96 | bool $checkLoadedClasses | 
| 97 | ) { | 
| 98 | $policyClasses = [ | 
| 99 | ...$classPolicies, | 
| 100 | BaseRequirementMethodPolicy::class, | 
| 101 | ...$this->methodPolicies | 
| 102 | ]; // force load policy classes | 
| 103 | foreach ($policyClasses as $class) { | 
| 104 | if (!class_exists($class)) { | 
| 105 | throw new InvalidArgumentException('Provided policy class that could not be found. Class: ' . $class); | 
| 106 | } | 
| 107 | } | 
| 108 | |
| 109 | self::$instance = $this; | 
| 110 | |
| 111 | $this->overrideSplLoaders(); | 
| 112 | if ($checkLoadedClasses) { | 
| 113 | $this->checkLoadedClasses(); | 
| 114 | } | 
| 115 | } | 
| 116 | |
| 117 | /** | 
| 118 | * Our custom autoload function. We loop over the registered loaders | 
| 119 | * to load the class then call the static constructor on the class | 
| 120 | * if it was loaded. | 
| 121 | * | 
| 122 | * @param class-string $className | 
| 123 | */ | 
| 124 | public function autoload(string $className): void { | 
| 125 | foreach ($this->proxiedLoaders as $func) { | 
| 126 | $func($className); | 
| 127 | if (class_exists($className, false)) { | 
| 128 | break; | 
| 129 | } | 
| 130 | } | 
| 131 | |
| 132 | $this->callStaticConstructor($className); | 
| 133 | } | 
| 134 | |
| 135 | /** | 
| 136 | * Look for a suitable static constructor on a class and call it. | 
| 137 | * | 
| 138 | * @param class-string $className | 
| 139 | */ | 
| 140 | private function callStaticConstructor(string $className): void { | 
| 141 | /** @var \ReflectionMethod|null $method */ | 
| 142 | $method = null; | 
| 143 | $reflection = new ReflectionClass($className); | 
| 144 | foreach ($this->classPolicies as $classPolicy) { | 
| 145 | $method = $classPolicy::methodFor($reflection); | 
| 146 | if ($method === null || !(BaseRequirementMethodPolicy::meetsRequirements($method))) { | 
| 147 | $method = null; | 
| 148 | continue; | 
| 149 | } | 
| 150 | |
| 151 | foreach ($this->methodPolicies as $methodPolicy) { | 
| 152 | if (!($methodPolicy::meetsRequirements($method))) { | 
| 153 | $method = null; | 
| 154 | continue 2; | 
| 155 | } | 
| 156 | } | 
| 157 | break; // valid | 
| 158 | } | 
| 159 | if ($method === null) { | 
| 160 | return; | 
| 161 | } | 
| 162 | |
| 163 | $method->invoke(null); | 
| 164 | } | 
| 165 | |
| 166 | /** | 
| 167 | * Store all the currently registered autoload functions and register | 
| 168 | * this class as the primary autoloader. | 
| 169 | */ | 
| 170 | private function overrideSplLoaders(): void { | 
| 171 | foreach (spl_autoload_functions() as $func) { | 
| 172 | $this->proxiedLoaders[] = $func; | 
| 173 | spl_autoload_unregister($func); | 
| 174 | } | 
| 175 | |
| 176 | spl_autoload_register( | 
| 177 | function (string $className) { | 
| 178 | /** @var class-string $className */ | 
| 179 | $this->autoload($className); | 
| 180 | }, | 
| 181 | true, | 
| 182 | true | 
| 183 | ); | 
| 184 | } | 
| 185 | |
| 186 | /** | 
| 187 | * Check classes already declared in the runtime for static constructor methods | 
| 188 | * and call them. | 
| 189 | */ | 
| 190 | private function checkLoadedClasses(): void { | 
| 191 | foreach (get_declared_classes() as $className) { | 
| 192 | $this->callStaticConstructor($className); | 
| 193 | } | 
| 194 | } | 
| 195 | |
| 196 | } |