1<?php 2 3namespace dokuwiki\Remote; 4 5 6use dokuwiki\Remote\OpenApiDoc\DocBlockMethod; 7use InvalidArgumentException; 8use ReflectionException; 9use ReflectionFunction; 10use ReflectionMethod; 11use RuntimeException; 12 13class ApiCall 14{ 15 /** @var callable The method to be called for this endpoint */ 16 protected $method; 17 18 /** @var bool Whether this call can be called without authentication */ 19 protected bool $isPublic = false; 20 21 /** @var string The category this call belongs to */ 22 protected string $category; 23 24 /** @var DocBlockMethod The meta data of this call as parsed from its doc block */ 25 protected $docs; 26 27 /** 28 * Make the given method available as an API call 29 * 30 * @param string|array $method Either [object,'method'] or 'function' 31 * @param string $category The category this call belongs to 32 */ 33 public function __construct($method, $category = '') 34 { 35 if (!is_callable($method)) { 36 throw new InvalidArgumentException('Method is not callable'); 37 } 38 39 $this->method = $method; 40 $this->category = $category; 41 } 42 43 /** 44 * Call the method 45 * 46 * Important: access/authentication checks need to be done before calling this! 47 * 48 * @param array $args 49 * @return mixed 50 */ 51 public function __invoke($args) 52 { 53 if (!array_is_list($args)) { 54 $args = $this->namedArgsToPositional($args); 55 } 56 return call_user_func_array($this->method, $args); 57 } 58 59 /** 60 * Access the method documentation 61 * 62 * This lazy loads the docs only when needed 63 * 64 * @return DocBlockMethod 65 */ 66 public function getDocs() 67 { 68 if ($this->docs === null) { 69 try { 70 if (is_array($this->method)) { 71 $reflect = new ReflectionMethod($this->method[0], $this->method[1]); 72 } else { 73 $reflect = new ReflectionFunction($this->method); 74 } 75 $this->docs = new DocBlockMethod($reflect); 76 } catch (ReflectionException $e) { 77 throw new RuntimeException('Failed to parse API method documentation', 0, $e); 78 } 79 80 } 81 return $this->docs; 82 } 83 84 85 /** 86 * @return bool 87 */ 88 public function isPublic(): bool 89 { 90 return $this->isPublic; 91 } 92 93 /** 94 * @param bool $isPublic 95 * @return $this 96 */ 97 public function setPublic(bool $isPublic = true): self 98 { 99 $this->isPublic = $isPublic; 100 return $this; 101 } 102 103 104 /** 105 * @return array 106 */ 107 public function getArgs(): array 108 { 109 return $this->getDocs()->getParameters(); 110 } 111 112 /** 113 * @return array 114 */ 115 public function getReturn(): array 116 { 117 return $this->getDocs()->getReturn(); 118 } 119 120 /** 121 * @return string 122 */ 123 public function getSummary(): string 124 { 125 return $this->getDocs()->getSummary(); 126 } 127 128 /** 129 * @return string 130 */ 131 public function getDescription(): string 132 { 133 return $this->getDocs()->getDescription(); 134 } 135 136 /** 137 * @return string 138 */ 139 public function getCategory(): string 140 { 141 return $this->category; 142 } 143 144 /** 145 * Converts named arguments to positional arguments 146 * 147 * @fixme with PHP 8 we can use named arguments directly using the spread operator 148 * @param array $params 149 * @return array 150 */ 151 protected function namedArgsToPositional($params) 152 { 153 $args = []; 154 155 foreach (array_keys($this->getDocs()->getParameters()) as $arg) { 156 if (isset($params[$arg])) { 157 $args[] = $params[$arg]; 158 } else { 159 $args[] = null; 160 } 161 } 162 163 return $args; 164 } 165 166} 167