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