mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-09 03:06:55 +00:00
197 lines
6.5 KiB
PHP
197 lines
6.5 KiB
PHP
<?php
|
|
|
|
/*
|
|
*
|
|
* ____ _ _ __ __ _ __ __ ____
|
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* @author PocketMine Team
|
|
* @link http://www.pocketmine.net/
|
|
*
|
|
*
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace pocketmine\utils;
|
|
|
|
use function array_map;
|
|
use function assert;
|
|
use function count;
|
|
use function get_class;
|
|
use function mb_strtoupper;
|
|
use function preg_match;
|
|
|
|
/**
|
|
* This trait allows a class to simulate object class constants, since PHP doesn't currently support this.
|
|
* These faux constants are exposed in static class methods, which are handled using __callStatic().
|
|
*
|
|
* Classes using this trait need to include \@method tags in their class docblock for every faux constant.
|
|
* Alternatively, just put \@generate-registry-docblock in the docblock and run build/generate-registry-annotations.php
|
|
*/
|
|
trait RegistryTrait{
|
|
/**
|
|
* @var object[]|null
|
|
* @phpstan-var array<string, object>|null
|
|
*/
|
|
private static $members = null;
|
|
|
|
/**
|
|
* @var OverloadedRegistryMember[]
|
|
* @phpstan-var array<string, OverloadedRegistryMember>
|
|
*/
|
|
private static $overloadedMembers = [];
|
|
|
|
private static function verifyName(string $name) : void{
|
|
if(preg_match('/^(?!\d)[A-Za-z\d_]+$/u', $name) === 0){
|
|
throw new \InvalidArgumentException("Invalid member name \"$name\", should only contain letters, numbers and underscores, and must not start with a number");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds the given object to the registry.
|
|
*
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
private static function _registryRegister(string $name, object $member) : void{
|
|
if(self::$members === null){
|
|
throw new AssumptionFailedError("Cannot register members outside of " . self::class . "::setup()");
|
|
}
|
|
self::verifyName($name);
|
|
$upperName = mb_strtoupper($name);
|
|
if(isset(self::$members[$upperName]) || isset(self::$overloadedMembers[$upperName])){
|
|
throw new \InvalidArgumentException("\"$upperName\" is already reserved");
|
|
}
|
|
self::$members[$upperName] = $member;
|
|
}
|
|
|
|
/**
|
|
* @phpstan-template TEnum of \UnitEnum
|
|
* @phpstan-param class-string<TEnum> $enumClass
|
|
* @phpstan-param class-string $returnClass
|
|
*/
|
|
private static function registerOverloaded(string $baseName, string $enumClass, string $returnClass) : void{
|
|
self::verifyName($baseName);
|
|
$upperName = mb_strtoupper($baseName);
|
|
if(isset(self::$members[$upperName]) || isset(self::$overloadedMembers[$upperName])){
|
|
throw new \InvalidArgumentException("\"$upperName\" is already reserved");
|
|
}
|
|
$enumToMemberMap = [];
|
|
foreach($enumClass::cases() as $case){
|
|
$memberName = mb_strtoupper($case->name . "_" . $baseName);
|
|
if(!isset(self::$members[$memberName])){
|
|
throw new \LogicException("\"$memberName\" needs to be registered to define overloaded member with enum $enumClass");
|
|
}
|
|
if(!self::$members[$memberName] instanceof $returnClass){
|
|
throw new \LogicException("\"$memberName\" doesn't satisfy the desired type $returnClass");
|
|
}
|
|
$enumToMemberMap[$case->name] = $memberName;
|
|
}
|
|
self::$overloadedMembers[$upperName] = new OverloadedRegistryMember($enumClass, $returnClass, $enumToMemberMap);
|
|
}
|
|
|
|
/**
|
|
* Inserts default entries into the registry.
|
|
*
|
|
* (This ought to be private, but traits suck too much for that.)
|
|
*/
|
|
abstract protected static function setup() : void;
|
|
|
|
/**
|
|
* @internal Lazy-inits the enum if necessary.
|
|
*
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
protected static function checkInit() : void{
|
|
if(self::$members === null){
|
|
self::$members = [];
|
|
self::setup();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws \InvalidArgumentException
|
|
*/
|
|
private static function _registryFromString(string $name) : object{
|
|
self::checkInit();
|
|
if(self::$members === null){
|
|
throw new AssumptionFailedError(self::class . "::checkInit() did not initialize self::\$members correctly");
|
|
}
|
|
$upperName = mb_strtoupper($name);
|
|
if(!isset(self::$members[$upperName])){
|
|
throw new \InvalidArgumentException("No such registry member: " . self::class . "::" . $upperName);
|
|
}
|
|
return self::preprocessMember(self::$members[$upperName]);
|
|
}
|
|
|
|
protected static function preprocessMember(object $member) : object{
|
|
return $member;
|
|
}
|
|
|
|
/**
|
|
* @param string $name
|
|
* @param mixed[] $arguments
|
|
* @phpstan-param list<mixed> $arguments
|
|
*
|
|
* @return object
|
|
*/
|
|
public static function __callStatic($name, $arguments){
|
|
if(count($arguments) === 0){
|
|
//fast path
|
|
if(self::$members !== null && isset(self::$members[$name])){
|
|
return self::preprocessMember(self::$members[$name]);
|
|
}
|
|
|
|
//fallback
|
|
try{
|
|
return self::_registryFromString($name);
|
|
}catch(\InvalidArgumentException $e){
|
|
throw new \Error($e->getMessage(), 0, $e);
|
|
}
|
|
}elseif(count($arguments) === 1 && $arguments[0] instanceof \UnitEnum){
|
|
$enum = $arguments[0];
|
|
self::checkInit();
|
|
|
|
$overloadInfo = self::$overloadedMembers[$name] ?? self::$overloadedMembers[mb_strtoupper($name)] ?? null;
|
|
|
|
if($overloadInfo !== null){
|
|
if($enum::class !== $overloadInfo->enumClass){
|
|
throw new \Error("Wrong enum type for overloaded registry member " . self::class . "::" . mb_strtoupper($name) . "($overloadInfo->enumClass)");
|
|
}
|
|
$memberName = $overloadInfo->enumToMemberMap[$enum->name];
|
|
assert(self::$members !== null);
|
|
return self::preprocessMember(self::$members[$memberName]);
|
|
}
|
|
|
|
throw new \Error("No such overloaded registry member: " . self::class . "::" . mb_strtoupper($name) . "(" . get_class($enum) . ")");
|
|
}else{
|
|
throw new \LogicException("Incorrect arguments passed to overloaded registry member: " . self::class . "::" . mb_strtoupper($name));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return object[]
|
|
* @phpstan-return array<string, object>
|
|
*/
|
|
private static function _registryGetAll() : array{
|
|
self::checkInit();
|
|
return array_map(self::preprocessMember(...), self::$members ?? throw new AssumptionFailedError(self::class . "::checkInit() did not initialize self::\$members correctly"));
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
* @phpstan-return array<string, OverloadedRegistryMember>
|
|
*/
|
|
public static function getAllOverloaded() : array{
|
|
return self::$overloadedMembers;
|
|
}
|
|
}
|