diff --git a/README.md b/README.md index d8f8c67..acf0dda 100644 --- a/README.md +++ b/README.md @@ -27,17 +27,17 @@ Flags can also be scoped to a class by creating a new `FunctionFlags` instance. ```php use FunctionFlags/FunctionFlags -$my_flag = new FunctionFlags("MY_FLAG"); -$other_flag = new FunctionFlags("OTHER_FLAG"); +$flags1 = new FunctionFlags(["FOO", "BAR"]); +$flags2 = new FunctionFlags(["BAR", "BIZ"]); -// Returns true if MY_FLAG is passed and present in $my_flag +// Returns true if FOO is passed and present in $flags1 function foo(int $flags = null): bool { - return $my_flag->isset(MY_FLAG); + return $flags2->isset(FOO); } -foo(MY_FLAG); // true -foo(OTHER_FLAG|MY_FLAG); // true -foo(OTHER_FLAG); // false +foo(FOO); // true +foo(FOO|BIZ); // true +foo(BIZ); // false foo(); // false ``` @@ -89,4 +89,4 @@ Instanced|Description --|-- `new FunctionFlags(string\|array\|null)`|Flag(s) to define as string or array of string. This method must be called before using the flag(s). `FunctionFlags->define(string\|array)`|Flag(s) to define as string or array of string. This method must be called before using the flag(s). -`FunctionFlags->isset(constant)`|The constant you wish to check is set on your function or method call. \ No newline at end of file +`FunctionFlags->isset(constant)`|The constant you wish to check is set on your function or method call. diff --git a/src/FunctionFlags.php b/src/FunctionFlags.php index 3aa77ef..d3345ae 100644 --- a/src/FunctionFlags.php +++ b/src/FunctionFlags.php @@ -1,16 +1,71 @@ flags = []; + + // Define flags that were initialized with this class + if (!empty($flags)) { + $this->inst_define($flags); + } + } + + // We're using the PHP __call() and __callStatic() magic functions here in order to allow + // methods to be called with the same name downstream. inst_define() and static_define() can + // be called using FunctionFlags::define() and FunctionFlags->define(). + + // Call static method on this class + public static function __callStatic(string $method, array $args): mixed { + // Static methods use this method prefix + $method_prefixed = "static_" . $method; + + // Check that method exists on this class and is in whitelist of public static methods + if (!in_array($method_prefixed, get_class_methods(__CLASS__)) && !in_array($method_prefixed, (__CLASS__)::$static_public_whitelist)) { + throw new \BadMethodCallException("Method '${method}' does not exist"); + } + + return (__CLASS__)::{$method_prefixed}(...$args); + } + + // Call instanced method on this class + public function __call(string $method, array $args): mixed { + // Instanced methods use this method prefix + $method_prefixed = "inst_" . $method; + + // Check that method exists on this class and is in whitelist of public instanced methods + if (!in_array($method_prefixed, get_class_methods(__CLASS__)) && !in_array($method_prefixed, (__CLASS__)::$inst_public_whitelist)) { + throw new \BadMethodCallException("Method '${method}' does not exist"); + } + + return $this->{$method_prefixed}(...$args); + } + + /* ---- */ // Reserve a constant address space private static function addr_reserve(): int { // Set initial value when first flag is defined if (empty($_ENV[__CLASS__])) { - $_ENV[__CLASS__] = 0; + $_ENV[__CLASS__] = 1; } // Increment counter @@ -21,18 +76,21 @@ // Get flags from caller closest to this method on the call stack private static function get_flags_from_caller(): int|null { - // Get call stack - $stack = debug_backtrace(0, (__CLASS__)::$trace_limit); + // Get call stack in reverse order + $stack = array_reverse(debug_backtrace(0, (__CLASS__)::$BACKTRACE_LIMIT)); - // Extract class names from callstack in reverse order and find the first occurance of this class in the backtrace - $idx = array_search(__CLASS__, array_reverse(array_column($stack, "class"))); - // Failed to locate this class in a full backtrace + // Find first occurance of this class name in call stack + $idx = array_search(__CLASS__, array_column($stack, "class")); + // Failed to locate this class if ($idx === false) { - throw new Exception("Failed to retrieve flags from initator callable"); + throw new Exception("Failed to retrieve flags from initator callable; Perhaps increase FunctionFlags::\$BACKTRACE_LIMIT"); + } elseif ($idx === 0) { + // No parent callable. Method was probably called on its own + return null; } - // Get args array from stack entry by index - $args = $stack[$idx]["args"]; + // Get args array from initial caller by simply stepping back one entry in the reverse array + $args = $stack[$idx - 1]["args"]; // Return null if no arguments provided or not a valid int flag return !empty($args) && is_int(end($args)) ? end($args) : null; @@ -41,19 +99,57 @@ /* ---- */ // Define new constants - public static function define(string|array $flags) { + private static function static_define(string|array $flags): array { // Convert to array $flags = is_array($flags) ? $flags : [$flags]; + $reserved = []; + // Define constant for each flag with unique address foreach ($flags as $flag) { - define($flag, (__CLASS__)::addr_reserve()); + // Constant already defined with that name + if (defined($flag)) { + // Pass existing address + $reserved[] = constant($flag); + continue; + } + + // Reserve new address + $addr = (__CLASS__)::addr_reserve(); + $reserved[] = $addr; + + define($flag, $addr); } + + // Return reserved addresses + return $reserved; } // Check if a flag is set with bitwise AND of all flags - public static function isset(int $flag): bool|null { - $flags = FunctionFlags::get_flags_from_caller(); - return $flags ? $flags & $flag : null; + private static function static_isset(int $flag): bool { + $flags = (__CLASS__)::get_flags_from_caller(); + return $flags ? $flags & $flag : false; + } + + /* ---- */ + + // Define flag(s) for $this instance + private function inst_define(string|array $flags) { + // Convert to array + $flags = is_array($flags) ? $flags : [$flags]; + + // Append flag(s) to instance memory + $this->flags = array_merge($this->flags, $this::static_define($flags)); + } + + // Check if flag is set and within $this scope + private function inst_isset(int $flag): bool { + // Return false if the flag is not in scope of $this instance + if (!in_array($flag, $this->flags)) { + return false; + } + + // Filter flags that belong to this scope + return $this::static_isset($flag); } }